What’s New in Tidy3d?#

Run this notebook in your browser using Binder.

This notebook will walk through the changes between the Tdy3D version released in March 2022 and the version used before that.

If you are a new user and not familiar with our original version, this tutorial can be useful, but you may rather see one of our other ones for a more direct walkthrough of the features.

[1]:
# First, lets import the main packages we'll need
import numpy as np
import matplotlib.pylab as plt
import tidy3d as td

Global Changes#

Here we will discuss some of the general changes that apply to all Tidy3d importable components.

Topics covered will include:

  • Defining Tidy3d components.

  • Saving and loading from file.

  • Getting help / debugging.

  • logging

Background#

All Tidy3d components are defined using the pydantic package. As such, each tidy3d object corresponds to a data structure that has rigidly defined allowable types and values, which get validated automatically whenever you inialize an object.

This has several advantges, including.

  • Catching bugs in the tidy3d components as early as possible.

  • Automatically generated schema for tidy3d simulation specifications.

  • Simple and reliable IO.

Defining Tidy3d Components#

It also requires a bit more due-diligence on the user side:

  • All tidy3d components must use keyword arguments in their definitions, eg. Medium(2.0) becomes Medium(permittivity=2.0) to be explicit.

[2]:
# wrong way
try:
    td.Medium(2.0)
except Exception as e:
    td.log.info(e)

# correct way
m = td.Medium(permittivity=2.0)
[15:28:35] INFO     __init__() takes exactly 1 positional    <ipython-input-2-467958394b33>:5
                    argument (2 given)                                                       

Saving and Loading Tidy3d Components#

All tidy3d components can be saved to file as json or yaml format using the instance.to_file(path) and class.from_file(path) methods.

For example, let’s save and load a td.Box instance.

[3]:
my_box = td.Box(center=(1,2,3), size=(2,2,3))

my_box.to_file('data/box.json')

# note, `from_file` is a @classmethod so need to call it from `td.Box` not `my_box`.
your_box = td.Box.from_file('data/box.json')

print(my_box)
print(your_box)
print(my_box == your_box)
center=(1.0, 2.0, 3.0) type='Box' size=(2.0, 2.0, 3.0)
center=(1.0, 2.0, 3.0) type='Box' size=(2.0, 2.0, 3.0)
True

Getting Help#

Sometimes you might want to get some information about a component without needing to look at the documentation. For this, each tidy3d component has a .help() method that will print out information about the stored data inside of the component.

Here’s an example.

[4]:
monitor = td.FieldMonitor(size=(2,2,0), freqs=[200e12], name='monitor')

monitor.help()
╭──────────────────── <class 'tidy3d.components.monitor.FieldMonitor'> ─────────────────────╮
 :class:`Monitor` that records electromagnetic fields in the frequency domain.             
                                                                                           
 ╭───────────────────────────────────────────────────────────────────────────────────────╮ 
  FieldMonitor(center=(0.0, 0.0, 0.0), type='FieldMonitor', size=(2.0, 2.0, 0.0),        
  name='monitor', freqs=[200000000000000.0], fields=['Ex', 'Ey', 'Ez', 'Hx', 'Hy',       
  'Hz'])                                                                                 
 ╰───────────────────────────────────────────────────────────────────────────────────────╯ 
                                                                                           
 bounding_box = Box(center=(0.0, 0.0, 0.0), type='Box', size=(2.0, 2.0, 0.0))              
       bounds = ((-1.0, -1.0, 0.0), (1.0, 1.0, 0.0))                                       
       center = (0.0, 0.0, 0.0)                                                            
       fields = ['Ex', 'Ey', 'Ez', 'Hx', 'Hy', 'Hz']                                       
        freqs = [200000000000000.0]                                                        
     geometry = Box(center=(0.0, 0.0, 0.0), type='Box', size=(2.0, 2.0, 0.0))              
         name = 'monitor'                                                                  
  plot_params = PlotParams(alpha=0.4, edgecolor='orange', facecolor='orange', fill=True,   
                hatch=None, linewidth=3.0, type='PlotParams')                              
         size = (2.0, 2.0, 0.0)                                                            
         type = 'FieldMonitor'                                                             
╰───────────────────────────────────────────────────────────────────────────────────────────╯

Changes to Core Components#

Here we will discuss the changes to the core package, specifically changes to how the core components (sources, monitors, etc) are defined.

Topics covered will include:

  • Mediums.

  • Geometries.

  • Structures.

  • Sources.

  • Monitors.

  • Modes.

  • PML / Absorbing boundaries.

  • Simulations.

Mediums#

As before, mediums define the optical properties of the materials within the simulation.

PEC#

The simplest medium is td.PEC, which just signifies a perfect electrical conductor (no E field allowed within).

[5]:
pec_medium = td.PEC

Non-dispersive mediums#

Non-dispersive mediums are defined using the Medium object and can be specified by either permittivity and conductivity (optional) values, or from n, k refractive index values.

[6]:
lossless_dielectric = td.Medium(permittivity=4.0)
lossy_dielectric = td.Medium(permittivity=4.0, conductivity=1.0)
lossy_dielectric_from_nk = td.Medium.from_nk(n=2.0, k=0.1, freq=150e12)

Anisotropic mediums#

Tidy3d currently only supports diagonally anisotropic media without dispersion.

This kind of medium can be simply defined by specifing three Medium objects for the xx, yy, zz components of the permittivity / conductivity tensor.

[7]:
anisotropic_medium = td.AnisotropicMedium(
    xx=lossless_dielectric,
    yy=lossy_dielectric,
    zz=lossy_dielectric_from_nk
)

Dispersive mediums#

Dispersive mediums can be defined in three ways:

  • Imported from our material_library.

  • Defined directly by specifying the parameters in the various supplied dispersive models.

  • Fitted to optical n-k data using the dispersion fitting tool plugin (more info later).

[8]:
# material library
silver_variants = td.material_library['Ag']
print('variants for silver include: ', list(silver_variants.keys()))
silver = silver_variants['JohnsonChristy1972']

# models
lorentz_model = td.Lorentz(eps_inf=2.0, coeffs=[(1,2,3), (4,5,6)])
sellmeier_model = td.Sellmeier(coeffs=[(1,2), (3,4)])
variants for silver include:  ['Rakic1998', 'JohnsonChristy1972']

Medium Methods#

The complex-valued permittivity of a medium at a given frequency can be sampled using the .eps_model(freq) method.

And the n, k values can be plotted over a frequency range using the .plot(freqs) method.

[9]:
freqs_hz = 1e12 * np.linspace(50, 200, 1001)
print(f'complex relative permittivity at freqs_hz = \n\t {lossy_dielectric.eps_model(freqs_hz)}\n')

ax = lossy_dielectric_from_nk.plot(freqs_hz)
complex relative permittivity at freqs_hz =
         [4.+359.50208038j 4.+358.42679998j 4.+357.35793278j ... 4. +90.0105359j
 4. +89.94297733j 4. +89.87552009j]

<Figure size 432x288 with 1 Axes>
../_images/notebooks_WhatsNew_18_2.png

Geometries#

The new version of Tidy3D introduces Geometry objects, which do the heavy lifting for any components with some spatial extent.

There are only 4 primitive geometries:

Note that GdsSlab was removed, but GDS cells can still be loaded as Polyslab objects using PolySlab.from_gds() classmethod. Please refer to the tutorial notebook on GDS importing for more details.

Geometry objects have many useful methods.

[10]:
s1 = td.Sphere(radius=1, center=(0,0,0))
s2 = td.Box(center=(1, 1, 1), size=(1, 1, 1))

# do two geometric object intersect?
print(s1.intersects(s2))

# does the object intersect a plane?
print(s1.intersects_plane(z=10))

# get polygons that intersect sphere at plane x=0
print(s1.intersections(x=0))

# get bounds (rmin, rmax) of geometry
print(s1.bounds)

# get td.Box() for bounding box of geometry
print(s1.bounding_box.help())

# evaluate whether point(s) are inside of geometry
print(s1.inside(x=0, y=1, z=1))
print(s1.inside(x=np.linspace(-1, 1, 5), y=np.zeros(5), z=np.ones(5)))

# plot the geometry at a cross sectional plane
ax = s1.plot(y=0)
True
False
[<shapely.geometry.polygon.Polygon object at 0x7fb11d643520>]
((-1.0, -1.0, -1.0), (1.0, 1.0, 1.0))
╭──────────────────────── <class 'tidy3d.components.geometry.Box'> ─────────────────────────╮
 Rectangular prism.                                                                        
 Also base class for :class:`Simulation`, :class:`Monitor`, and :class:`Source`.           
                                                                                           
 ╭───────────────────────────────────────────────────────────────────────────────────────╮ 
  Box(center=(0.0, 0.0, 0.0), type='Box', size=(2.0, 2.0, 2.0))                          
 ╰───────────────────────────────────────────────────────────────────────────────────────╯ 
                                                                                           
 bounding_box = Box(center=(0.0, 0.0, 0.0), type='Box', size=(2.0, 2.0, 2.0))              
       bounds = ((-1.0, -1.0, -1.0), (1.0, 1.0, 1.0))                                      
       center = (0.0, 0.0, 0.0)                                                            
     geometry = Box(center=(0.0, 0.0, 0.0), type='Box', size=(2.0, 2.0, 2.0))              
  plot_params = PlotParams(alpha=1.0, edgecolor=None, facecolor=None, fill=True,           
                hatch=None, linewidth=1.0, type='PlotParams')                              
         size = (2.0, 2.0, 2.0)                                                            
         type = 'Box'                                                                      
╰───────────────────────────────────────────────────────────────────────────────────────────╯
None
False
[False False  True False False]
<Figure size 432x288 with 1 Axes>
../_images/notebooks_WhatsNew_20_4.png

Note, because simulations, monitors, and sources all are defined spatially, they inherit from Box and contain these methods as well, which can come in handy when doing validation.

Structures#

The new version of tidy3d redefines the notion of Structure as something that simply contains a Geometry and a Medium. Therefore, the call structure is a bit different.

[11]:
# old way
try:
    dielectric_box = td.Structure(
        center=(0,0,0),
        size=(1,1,1),
        medium=td.Medium(permittivity=2.0))
except Exception as e:
    td.log.info(e)

# new way
dielectric_box = td.Structure(
    geometry=td.Box(
        center=(0,0,0),
        size=(1,1,1)
    ),
    medium=td.Medium(permittivity=2.0)
)
           INFO     3 validation errors for Structure       <ipython-input-11-83782651abc4>:8
                    geometry                                                                 
                      field required                                                         
                    (type=value_error.missing)                                               
                    center                                                                   
                      extra fields not permitted                                             
                    (type=value_error.extra)                                                 
                    size                                                                     
                      extra fields not permitted                                             
                    (type=value_error.extra)                                                 

Sources#

Sources work more or less similarly to the old version, with a few minor API changes.

  1. PointDipole and VolumeSource were combined into a single VolumeSource object. For a dipole, specify the size=(0,0,0).

  2. Instead of specifying injection axis, plane wave, mode source, and gaussian beam sources must have a planar geometry (one size=0 element) and direction ('+' or '-') specifying the direction along the normal axis to send the fields.

[12]:
# note the change in kwarg values
gaussian = td.GaussianPulse(freq0=150e12, fwidth=10e12)

# z polarized dipole at origin
dipole = td.UniformCurrentSource(
    center=(0,0,0),
    size=(0,0,0),
    source_time=gaussian,
    polarization='Ez'
)

# z polarized plane wave propagating in -x
plane_wave = td.PlaneWave(
    center=(0,0,0),
    size=(0, td.inf, td.inf),
    source_time=gaussian,
    pol_angle=np.pi/2,
    direction='-'
)

Monitors#

Monitors have received some major changes in the new version. Before, monitors were split up according to whether they measured values in the time or frequency domain.

  • TimeMonitor

  • FreqMonitor

The contents of the store argument told the solver what kind of data to load into the monitor data.

In the new version, each monitor stores a single type of data and we have expanded the number of monitors.

The following monitors measure their corresponding values in the frequency-domain

And the following measure their values in the time-domain

This splitting up of monitor types means less accounting about what values are stored, and each monitor type has a corresponding data type in the simulation data.

Otherwise, monitors function very similarly, with a few minor API changes.

Note: all monitors must be named (have a name argument supplied). The data returned by the server will be indexed by the monitor name.

[13]:
# measures Ex, Ey, Hz fields on the plane at frequency 150THz
mon1 = td.FieldMonitor(
    center=(1,0,0),
    size=(td.inf, td.inf, 0),
    fields=['Ex', 'Ey', 'Hz'],
    freqs=[150e12],
    name='fields_at_150THz'
)

# measures time dependence of flux through a plane every 5 time steps between a window of (start, stop)
mon2 = td.FluxTimeMonitor(
    center=(1,0,0),
    size=(td.inf, td.inf, 0),
    start=1e-13,
    stop=3e-13,
    interval=5,
    name='flux_over_time',
)

Modes#

Mode objects have also gone through major revisions.

In the previous versions of Tidy3D, there were convenience functions for viewing mode profiles (Simulation.viz_modes), and ultimately the mode information needed to be set manually using Simulation.set_mode().

In the new version, we introduce a ModeSpec object that stores all of the specifiction needed for the mode solver to know which modes to inject or measure in the ModeSource and ModeMonitor objects.

For example:

[14]:
# default mode solver spec (returns first mode)
fundamental = td.ModeSpec()

# tell the mode solver to return 4 modes
first_4_modes = td.ModeSpec(num_modes=4)

# have mode solver return 4 modes around the target effective index
complicated = td.ModeSpec(num_modes=4, target_neff=2.0)

Using the mode specifications, we can make modal sources or monitors similar to before.

[15]:
# inject the fundamental mode
mode_source = td.ModeSource(
    center=(0, 0, -1),
    size=(td.inf, td.inf, 0),
    source_time=gaussian,
    mode_spec=fundamental,
    mode_index=0,
    direction='+'
)

# do modal decomposition and return amplitude data for the first 4 modes
mode_mon = td.ModeMonitor(
    center=(0, 0, +1),
    size=(td.inf, td.inf, 0),
    freqs=freqs_hz,
    mode_spec=first_4_modes,
    name='modes'
)

The td.plugins.ModeSolver is designed to help users come up with the correct ModeSpec for their problem, at which point it can be used directly in ModeSource and ModeMonitor objects without setting it explicitly using a Simulation method. For more details, refer to the mode solver tutorial notebook.

Absorbing Boundaries#

Absorbing boundaries are defined a bit differently in the new version, there are three types of boundaries

  • td.PML() defines a standard PML, with an adjustable number of layers.

  • td.StablePML() defines a PML with ‘stable’ profile, which can reduce divergence at the expense of more layers.

  • td.Absorber() defines adiabatically increasing conductivity values at the edges of the simultion, which can dramatically improve stability of simulations involving dispersive materials, again at the expense of more layers.

As before, these layers add to the simulation size defined in Simulation.

Also as before, it is important to extend any structures all the way through the PML if they are meant to be touching the simulation boundary on that side.

To define a sequence of PML lyers on the x, y, z sides of the simulation, one my define a tuple contining either the pml layer values or None if no PML is to be added on that side.

Periodic boundaries are always used on each side of the simulation, so if the PML is None on one side, the simulation will be periodic.

[16]:
# standard absorber on x, PML with 20 layers on y, no PML on z (periodic BC)
pml_layers = [td.Absorber(), td.PML(num_layers=20), None]

Simulations#

Finally, as before, Simulation objects contain all of the specifications needed to run the Tidy3D simulation and contain all of the previous components.

Again, there are some minor API changes, but overall they look very similar.

  • Simulation accepts an optional medium parameter, specifying the background medium (air by default).

  • mesh_step and resolution were removed in favor of a grid_spec, which specifies how the grid is to be generated along each of the three directions. These are discussed in more detail here.

[17]:
sim = td.Simulation(
    size=(4, 4, 4),
    grid_spec=td.GridSpec.uniform(dl=.2),
    run_time=1e-12,
    pml_layers=[None, td.PML(), None],
    structures=[dielectric_box],
    sources=[dipole],
    monitors=[mon1, mon2],
)

A defined Simulation also provides several useful methods in addition to the ones inhereted from Box.

[18]:
# get permittivity at yee cell centers in a volume defined by a box.
sim.epsilon(td.Box(size=(1,0,1)), 'centers')

# get a `td.Grid` containing all information about spatial locations in the FDTD / yee grid
print(sim.grid.centers)

# plot the simulation cross section
f, (ax1, ax2) = plt.subplots(1, 2, tight_layout=True, figsize=(10, 4))

# plot the structures, PML, sources, mediums
ax1 = sim.plot(x=0, ax=ax1)

# same thing but plot structure in grayscale using permittivity value
ax1 = sim.plot_eps(x=0, ax=ax2)

# add the FDTD grid boundaries
ax2 = sim.plot_grid(x=0, ax=ax2)
x=array([-1.9, -1.7, -1.5, -1.3, -1.1, -0.9, -0.7, -0.5, -0.3, -0.1,  0.1,
        0.3,  0.5,  0.7,  0.9,  1.1,  1.3,  1.5,  1.7,  1.9]) y=array([-4.3, -4.1, -3.9, -3.7, -3.5, -3.3, -3.1, -2.9, -2.7, -2.5, -2.3,
       -2.1, -1.9, -1.7, -1.5, -1.3, -1.1, -0.9, -0.7, -0.5, -0.3, -0.1,
        0.1,  0.3,  0.5,  0.7,  0.9,  1.1,  1.3,  1.5,  1.7,  1.9,  2.1,
        2.3,  2.5,  2.7,  2.9,  3.1,  3.3,  3.5,  3.7,  3.9,  4.1,  4.3]) z=array([-1.9, -1.7, -1.5, -1.3, -1.1, -0.9, -0.7, -0.5, -0.3, -0.1,  0.1,
        0.3,  0.5,  0.7,  0.9,  1.1,  1.3,  1.5,  1.7,  1.9]) type='Coords'
<Figure size 720x288 with 3 Axes>
../_images/notebooks_WhatsNew_37_2.png

Changes to Simulation Submission#

Here we will discuss changes made to the process for submitting, managing, monitoring, and loading simulations from our server.

Topics covered will include: * tidy3d.web functions. * working with tidy3d.web.Job and tidy3d.web.Batch convenience containers.

[19]:
import tidy3d.web as web

Web interface#

The new web interface provides the same functions as the original version with a few major changes.

Usually, the most convenient way to run a single simulation in one line is with web.run(), which simply performs all of the necessary steps one by one.

Note, in the new version, the output of the simultion is a separate data object called a SimulationData. Whereas before it was a Simulation object with the data loaded inside of it. We will discuss this is more detail in the following section.

[20]:
sim_data = web.run(sim, task_name='web_demo', path='data/data.hdf5')
[15:28:36] INFO     Using Tidy3D credentials from stored file                      auth.py:74
[15:28:38] INFO     Uploaded task 'web_demo' with task_id                       webapi.py:120
                    '0c792705-77cd-41d9-b07e-716a119a9914'.                                  
[15:28:42] INFO     Maximum flex unit cost: 0.20                                webapi.py:253
           INFO     status = queued                                             webapi.py:262
[15:28:53] INFO     status = preprocess                                         webapi.py:274
[15:29:07] INFO     starting up solver                                          webapi.py:278
[15:29:30] INFO     running solver                                              webapi.py:284
[15:29:31] INFO     status = postprocess                                        webapi.py:301
[15:29:44] INFO     status = success                                            webapi.py:307
[15:29:46] INFO     downloading file "monitor_data.hdf5" to "data/data.hdf5"    webapi.py:537
           INFO     loading SimulationData from data/data.hdf5                  webapi.py:369
           WARNING  Simulation final field decay value of 0.0267 is greater     webapi.py:375
                    than the simulation shutoff threshold of 1e-05. Consider                 
                    simulation again with large run_time duration for more                   
                    accurate results.                                                        

Containers#

The new version also contains the convenience containers Job and Batch for managing single and multiple tasks without needing to account for the task_id and other metadata.

They follow the same basic API as the web. functions, except Batch objects return generators that can be iterated through to give SimulationData for each task, rather than returning it one by one. This cuts down on memory for several large jobs.

While we wont cover all of the details here, for more information, see the tutorial on the Web API or look at the examples in the other notebooks.

Changes to Output Data#

Here we will discuss changes made to the output data from a simulation.

Topics covered will include:

  • SimulationData objects.

  • Obtaining information about a completed FDTD simulation.

  • Selecting data by monitor or field value.

  • Post-processing and visualizing data.

Simulation Data#

As mentioned, tidy3d data is now separated from the Simulation object that led to its creation.

We call the data container for a single task a SimulationData object.

In addition to storing the data for each of the individual monitors in the simulation, it has it’s own useful functionality.

[21]:
# print the log, which is stored as an attribute rather than as its own file
print(sim_data.log)

# get a copy of the original Simulation, so it also doesnt need to be stored separately
sim_data.simulation.help()
Simulation domain Nx, Ny, Nz: [20, 44, 20]
Applied symmetries: (0, 0, 0)
Number of computational grid points: 1.7600e+04.
Using subpixel averaging: True
Number of time steps: 2.8860e+03
Automatic shutoff factor: 1.00e-05
Time step (s): 3.4665e-16

Compute source modes time (s):     0.0074
Compute monitor modes time (s):    0.0200
Rest of setup time (s):            2.9106

Starting solver...
- Time step    115 / time 3.99e-14s (  4 % done), field decay: 1.00e+00
- Time step    230 / time 7.97e-14s (  8 % done), field decay: 1.00e+00
- Time step    346 / time 1.20e-13s ( 12 % done), field decay: 4.50e-01
- Time step    461 / time 1.60e-13s ( 16 % done), field decay: 4.12e-01
- Time step    577 / time 2.00e-13s ( 20 % done), field decay: 3.37e-01
- Time step    692 / time 2.40e-13s ( 24 % done), field decay: 1.06e-01
- Time step    808 / time 2.80e-13s ( 28 % done), field decay: 1.09e-02
- Time step    923 / time 3.20e-13s ( 32 % done), field decay: 6.20e-02
- Time step   1038 / time 3.60e-13s ( 36 % done), field decay: 1.64e-01
- Time step   1154 / time 4.00e-13s ( 40 % done), field decay: 1.49e-01
- Time step   1269 / time 4.40e-13s ( 44 % done), field decay: 6.08e-02
- Time step   1385 / time 4.80e-13s ( 48 % done), field decay: 1.62e-02
- Time step   1500 / time 5.20e-13s ( 52 % done), field decay: 2.49e-02
- Time step   1616 / time 5.60e-13s ( 56 % done), field decay: 5.67e-02
- Time step   1731 / time 6.00e-13s ( 60 % done), field decay: 8.48e-02
- Time step   1847 / time 6.40e-13s ( 64 % done), field decay: 6.30e-02
- Time step   1962 / time 6.80e-13s ( 68 % done), field decay: 1.34e-02
- Time step   2077 / time 7.20e-13s ( 72 % done), field decay: 8.45e-03
- Time step   2193 / time 7.60e-13s ( 76 % done), field decay: 2.59e-02
- Time step   2308 / time 8.00e-13s ( 80 % done), field decay: 4.81e-02
- Time step   2424 / time 8.40e-13s ( 84 % done), field decay: 3.91e-02
- Time step   2539 / time 8.80e-13s ( 88 % done), field decay: 1.22e-02
- Time step   2655 / time 9.20e-13s ( 92 % done), field decay: 3.15e-03
- Time step   2770 / time 9.60e-13s ( 96 % done), field decay: 1.15e-02
- Time step   2885 / time 1.00e-12s (100 % done), field decay: 2.67e-02

Solver time (s):                   0.4506
Post-processing time (s):          0.0174

╭──────────────────── <class 'tidy3d.components.simulation.Simulation'> ────────────────────╮
 Contains all information about Tidy3d simulation.                                         
                                                                                           
 ╭───────────────────────────────────────────────────────────────────────────────────────╮ 
  Simulation(center=(0.0, 0.0, 0.0), type='Simulation', size=(4.0, 4.0, 4.0),            
  run_time=1e-12, grid_size=None, medium=Medium(name=None, frequency_range=None,         
  type='Medium', permittivity=1.0, conductivity=0.0), symmetry=(0, 0, 0),                
  structures=[Structure(geometry=Box(center=(0.0, 0.0, 0.0), type='Box', size=(1.0,      
  1.0, 1.0)), medium=Medium(name=None, frequency_range=None, type='Medium',              
  permittivity=2.0, conductivity=0.0), name=None, type='Structure')],                    
  sources=[UniformCurrentSource(center=(0.0, 0.0, 0.0), type='UniformCurrentSource',     
  size=(0.0, 0.0, 0.0), source_time=GaussianPulse(amplitude=1.0, phase=0.0,              
  type='GaussianPulse', freq0=150000000000000.0, fwidth=10000000000000.0, offset=5.0),   
  name=None, polarization='Ez')], monitors=[FieldMonitor(center=(1.0, 0.0, 0.0),         
  type='FieldMonitor', size=(inf, inf, 0.0), name='fields_at_150THz',                    
  freqs=[150000000000000.0], fields=['Ex', 'Ey', 'Hz']), FluxTimeMonitor(center=(1.0,    
  0.0, 0.0), type='FluxTimeMonitor', size=(inf, inf, 0.0), name='flux_over_time',        
  start=1e-13, stop=3e-13, interval=5)],                                                 
  grid_spec=GridSpec(grid_x=UniformGrid(type='UniformGrid', dl=0.2),                     
  grid_y=UniformGrid(type='UniformGrid', dl=0.2),                                        
  grid_z=UniformGrid(type='UniformGrid', dl=0.2), wavelength=None,                       
  override_structures=[], type='GridSpec'), pml_layers=(PML(num_layers=0,                
  parameters=PMLParams(sigma_order=3, sigma_min=0.0, sigma_max=1.5, type='PMLParams',    
  kappa_order=3, kappa_min=1.0, kappa_max=3.0, alpha_order=1, alpha_min=0.0,             
  alpha_max=0.0), type='PML'), PML(num_layers=12, parameters=PMLParams(sigma_order=3,    
  sigma_min=0.0, sigma_max=1.5, type='PMLParams', kappa_order=3, kappa_min=1.0,          
  kappa_max=3.0, alpha_order=1, alpha_min=0.0, alpha_max=0.0), type='PML'),              
  PML(num_layers=0, parameters=PMLParams(sigma_order=3, sigma_min=0.0, sigma_max=1.5,    
  type='PMLParams', kappa_order=3, kappa_min=1.0, kappa_max=3.0, alpha_order=1,          
  alpha_min=0.0, alpha_max=0.0), type='PML')), shutoff=1e-05, subpixel=True,             
  courant=0.9, version='1.3.3')                                                          
 ╰───────────────────────────────────────────────────────────────────────────────────────╯ 
                                                                                           
 background_structure = Structure(geometry=Box(center=(0.0, 0.0, 0.0), type='Box',         
                        size=(inf, inf, inf)), medium=Medium(name=None,                    
                        frequency_range=None, type='Medium', permittivity=1.0,             
                        conductivity=0.0), name=None, type='Structure')                    
         bounding_box = Box(center=(0.0, 0.0, 0.0), type='Box', size=(4.0, 4.0, 4.0))      
               bounds = ((-2.0, -2.0, -2.0), (2.0, 2.0, 2.0))                              
           bounds_pml = ((-2.0, -4.3999999999999995, -2.0), (2.0, 4.399999999999997, 2.0)) 
               center = (0.0, 0.0, 0.0)                                                    
              courant = 0.9                                                                
                   dt = 3.4664997560661523e-16                                             
      frequency_range = (110000000000000.0, 190000000000000.0)                             
             geometry = Box(center=(0.0, 0.0, 0.0), type='Box', size=(4.0, 4.0, 4.0))      
                 grid = Grid(boundaries=Coords(x=array([-2. , -1.8, -1.6, -1.4, -1.2, -1.  
                        , -0.8, -0.6, -0.4, -0.2,  0. ,                                    
                                0.2,  0.4,  0.6,  0.8,  1. ,  1.2,  1.4,  1.6,  1.8,  2.   
                        ]), y=array([-4.4, -4.2, -4. , -3.8, -3.6, -3.4, -3.2, -3. , -2.8, 
                        -2.6, -2.4,                                                        
                               -2.2, -2. , -1.8, -1.6, -1.4, -1.2, -1. , -0.8, -0.6, -0.4, 
                        -0.2,                                                              
                                0. ,  0.2,  0.4,  0.6,  0.8,  1. ,  1.2,  1.4,  1.6,  1.8, 
                        2. ,                                                               
                                2.2,  2.4,  2.6,  2.8,  3. ,  3.2,  3.4,  3.6,  3.8,  4. , 
                        4.2,                                                               
                                4.4]), z=array([-2. , -1.8, -1.6, -1.4, -1.2, -1. , -0.8,  
                        -0.6, -0.4, -0.2,  0. ,                                            
                                0.2,  0.4,  0.6,  0.8,  1. ,  1.2,  1.4,  1.6,  1.8,  2.   
                        ]), type='Coords'), type='Grid')                                   
            grid_size = None                                                               
            grid_spec = GridSpec(grid_x=UniformGrid(type='UniformGrid', dl=0.2),           
                        grid_y=UniformGrid(type='UniformGrid', dl=0.2),                    
                        grid_z=UniformGrid(type='UniformGrid', dl=0.2), wavelength=None,   
                        override_structures=[], type='GridSpec')                           
               medium = Medium(name=None, frequency_range=None, type='Medium',             
                        permittivity=1.0, conductivity=0.0)                                
           medium_map = {                                                                  
                            Medium(name=None, frequency_range=None, type='Medium',         
                        permittivity=1.0, conductivity=0.0): 0,                            
                            Medium(name=None, frequency_range=None, type='Medium',         
                        permittivity=2.0, conductivity=0.0): 1                             
                        }                                                                  
              mediums = [                                                                  
                            Medium(name=None, frequency_range=None, type='Medium',         
                        permittivity=1.0, conductivity=0.0),                               
                            Medium(name=None, frequency_range=None, type='Medium',         
                        permittivity=2.0, conductivity=0.0)                                
                        ]                                                                  
             monitors = [                                                                  
                            FieldMonitor(center=(1.0, 0.0, 0.0), type='FieldMonitor',      
                        size=(inf, inf, 0.0), name='fields_at_150THz',                     
                        freqs=[150000000000000.0], fields=['Ex', 'Ey', 'Hz']),             
                            FluxTimeMonitor(center=(1.0, 0.0, 0.0),                        
                        type='FluxTimeMonitor', size=(inf, inf, 0.0),                      
                        name='flux_over_time', start=1e-13, stop=3e-13, interval=5)        
                        ]                                                                  
            num_cells = 17600                                                              
       num_pml_layers = [(0, 0), (12, 12), (0, 0)]                                         
       num_time_steps = 2886                                                               
          plot_params = PlotParams(alpha=1.0, edgecolor=None, facecolor=None, fill=True,   
                        hatch=None, linewidth=1.0, type='PlotParams')                      
           pml_layers = (                                                                  
                            PML(num_layers=0, parameters=PMLParams(sigma_order=3,          
                        sigma_min=0.0, sigma_max=1.5, type='PMLParams', kappa_order=3,     
                        kappa_min=1.0, kappa_max=3.0, alpha_order=1, alpha_min=0.0,        
                        alpha_max=0.0), type='PML'),                                       
                            PML(num_layers=12, parameters=PMLParams(sigma_order=3,         
                        sigma_min=0.0, sigma_max=1.5, type='PMLParams', kappa_order=3,     
                        kappa_min=1.0, kappa_max=3.0, alpha_order=1, alpha_min=0.0,        
                        alpha_max=0.0), type='PML'),                                       
                            PML(num_layers=0, parameters=PMLParams(sigma_order=3,          
                        sigma_min=0.0, sigma_max=1.5, type='PMLParams', kappa_order=3,     
                        kappa_min=1.0, kappa_max=3.0, alpha_order=1, alpha_min=0.0,        
                        alpha_max=0.0), type='PML')                                        
                        )                                                                  
      pml_thicknesses = [(0.0, 0.0), (2.3999999999999995, 2.399999999999997), (0.0, 0.0)]  
             run_time = 1e-12                                                              
              shutoff = 1e-05                                                              
                 size = (4.0, 4.0, 4.0)                                                    
              sources = [                                                                  
                            UniformCurrentSource(center=(0.0, 0.0, 0.0),                   
                        type='UniformCurrentSource', size=(0.0, 0.0, 0.0),                 
                        source_time=GaussianPulse(amplitude=1.0, phase=0.0,                
                        type='GaussianPulse', freq0=150000000000000.0,                     
                        fwidth=10000000000000.0, offset=5.0), name=None,                   
                        polarization='Ez')                                                 
                        ]                                                                  
           structures = [                                                                  
                            Structure(geometry=Box(center=(0.0, 0.0, 0.0), type='Box',     
                        size=(1.0, 1.0, 1.0)), medium=Medium(name=None,                    
                        frequency_range=None, type='Medium', permittivity=2.0,             
                        conductivity=0.0), name=None, type='Structure')                    
                        ]                                                                  
             subpixel = True                                                               
             symmetry = (0, 0, 0)                                                          
                tmesh = array([0.00000000e+00, 3.46649976e-16, 6.93299951e-16, ...,        
                               9.99391880e-13, 9.99738530e-13, 1.00008518e-12])            
                 type = 'Simulation'                                                       
              version = '1.3.3'                                                            
          wvl_mat_min = TypeError("'float' object is not iterable")                        
╰───────────────────────────────────────────────────────────────────────────────────────────╯

Monitor Data#

Data for each monitor is stored as its own td.MonitorData instance.

Whereas before we needed to access data using sim.data(monitor), now we can access the data by monitor.name from the SimulationData using square brackets.

The data are stored as xarray objects, which means they work similar to numpy arrays but provide many additional useful features. For more details refer to the tutorial on data visualization.

[22]:
flux_data = sim_data['flux_over_time']
print(flux_data)
flux_data.plot()
plt.title('flux over time')
plt.show()
<xarray.Tidy3dDataArray (t: 116)>
array([-2.15813375, -8.17918396, -2.74231744, -5.12412024, -3.0594871 ,
       -2.94512367, -2.61196923, -2.14577341, -1.48715341, -2.43813848,
       -0.3859551 , -2.95172143, -0.16872147, -2.86486626, -1.20589399,
       -2.01621675, -3.01739383, -1.03034031, -4.51303625, -0.83561403,
       -4.70758295, -1.91629374, -3.40828371, -3.86219049, -1.36479115,
       -5.57364321,  0.24417202, -5.99068451,  0.61912823, -4.81595278,
       -0.14640301, -2.70037746, -1.21354902, -0.76352823, -1.67400932,
        0.18268737, -1.23005843,  0.12730998, -0.36737138, -0.27692056,
        0.08848048, -0.33784801, -0.33518171,  0.07565808, -1.36679149,
        0.43307164, -2.20570326,  0.01363976, -2.16998649, -1.43209052,
       -1.26371312, -3.36432719, -0.21326661, -4.74903488,  0.06615268,
       -4.76003647, -0.82969278, -3.36539364, -2.461514  , -1.35794592,
       -3.83629322,  0.19249399, -4.1029067 ,  0.65614706, -3.13524342,
        0.20413303, -1.57781339, -0.43164939, -0.34158111, -0.58532822,
        0.05117986, -0.1740827 , -0.22357836,  0.28670913, -0.54129732,
        0.15958069, -0.41256219, -0.74212062,  0.07716301, -1.94803286,
        0.32569033, -2.66100621, -0.27549225, -2.36936808, -1.78516507,
       -1.26307356, -3.55839515, -0.12437747, -4.63411713,  0.23280694,
       -4.38553286, -0.4460862 , -2.95110726, -1.67819619, -1.1284039 ,
       -2.60636115,  0.18240905, -2.62228107,  0.56630421, -1.78029919,
        0.2849682 , -0.7037819 , -0.04148811, -0.07994813,  0.01434147,
       -0.14745156,  0.34495941, -0.57045138,  0.43609178, -0.77845436,
       -0.15304914, -0.48346823, -1.35461915,  0.04824419, -2.56167173,
        0.19274445])
Coordinates:
  * t        (t) float64 1.002e-13 1.019e-13 1.036e-13 ... 2.978e-13 2.995e-13
Attributes:
    units:      W
    long_name:  flux
<Figure size 432x288 with 1 Axes>
../_images/notebooks_WhatsNew_46_2.png

For field data, we can further index by fields specified in the monitor, as follows.

[23]:
Ey = sim_data['fields_at_150THz'].Ey

Ey.real.interp(z=0).plot(x='x', y='y', robust=True)
plt.title('real{Ey(x, y)}')
plt.show()
<Figure size 432x288 with 2 Axes>
../_images/notebooks_WhatsNew_48_1.png

Finally, SimulationData provides a method for potting field data with structure overlay, similar to sim.viz_fields2D().

[24]:
sim_data.plot_field('fields_at_150THz', 'Ey', val='real', z=0, freq=150e12)
plt.show()
<Figure size 432x288 with 2 Axes>
../_images/notebooks_WhatsNew_50_1.png

Plugins#

Here we will discuss the plugins that support and extend functionalities of Tidy3D, including:

These plugins are designed to import and make use of Tidy3D components described above, but the Tidy3D components have no dependence on the plugins by design. In this sense, they can be considered “external” packages that are useful for defining simulation components.

We won’t go into the details in this notebook as each of the plugins has its own example tutorial notebook.

Dispersion Fitting#

We provide a tool for fitting optical data to create dispersive mediums.

Given a file or arrays containing wavelength, n, and (optionally) k data, this tool will fit the data to a pole-residue model with some constraints and parameters.

After fitting, the user can visualize and inspect the results.

This process can be repeated until the user is satisfied, at which point the tool can return a dispersive medium for use in the Simulation.

Mode Solver#

The mode solver is a similar tool for coming up with ModeSpec objects for a given source or monitor.

The tool takes a reference simulation containing some waveguide or other structure definitions to feed to the core solver. It also requires a Box specifying the plane on which to solve the modes for.

Then, the user can iteratively send different ModeSpec objects, solve for the results, visulize, and repeat the process until satisfied.

The resulting ModeSpec can be saved directly, or used in conjunction with the mode solver settings to return a ModeSource or [ModeMonitor]((https://docs.simulation.cloud/projects/tidy3d/en/latest/_autosummary/tidy3d.ModeMonitor.html).

Near2Far#

Finally, the near field to far field transformation tool is used to transform FieldMonitor data to far field data or scattering cross section data.

The user specifies a the frequency-domain field data to use as near field source, these fields are converted to equivalent surface currents, and a computation is performed to give the radiation vectors emanating from the monitor location.

Then, the user can obtain the field patterns or scattered power as a function of position or angle using the various projection methods.

Conclusion#

We hope this gives a useful overview of the main changes in the revamped version of Tidy3D.

We highly recommend you check out the various tutorial notebooks if you have more specific questions or want to dive deeper in any of the topics.

[ ]: