The Tidy3D client that is used for designing simulations and analyzing the results is free and open source. We only bill the run time of the solver on our server, taking only the compute time into account (as opposed to overhead, e.g., during uploading). When a task is uploaded to our servers, we will print the maximum incurred cost in FlexCredit. This cost is also displayed in the online interface for that task. This value is determined by the cost associated with simulating the entire time stepping specified. This cost will be pro-rated if early shutoff is detected and the simulation is completed before the time stepping period. For more questions or to purchase FlexCredit, please contact us at support@flexcompute.com
.
FlexCredit is Flexcompute’s way of measuring computing power. It’s a unit we created to make it easier to understand and buy computing power.
One FlexCredit is equivalent to about 50 hours of CPU core time when using the traditional FDTD method. That’s the equivalent of running a computer processor core for two days straight, just for one FlexCredit! If you have 60 FlexCredits, it’s like having a 4-core CPU (which is a pretty powerful computer) running non-stop, 24 hours a day, for a full month.
This makes it simple for our customers and employees. Instead of trying to communicate in terms of CPU hours or cores, they just need to know how many FlexCredits they need for their project. It’s like buying time on a supercomputer but in a more straightforward, more understandable way.
One FlexCredit is equivalent to about 50 hours of CPU core time when using the traditional FDTD method. That’s the equivalent of running a computer processor core for two days straight, just for one FlexCredit!
If you have 60 FlexCredits, it’s like having a 4-core CPU (which is a pretty powerful computer) running non-stop, 24 hours a day, for an entire month.
Tidy3D comes with a feature-rich graphical user interface (GUI) that offers many tools to create and run electromagnetic simulations with ease and intuitiveness. With Tidy3D GUI, you can quickly analyze simulation results, conduct parameter sweeps, perform mode analysis, access simulation information, and manage your account. Additionally, a complete Python notebook development environment is included, allowing you to utilize the flexibility of Tidy3D Python without installing the client interface.
Yes. Suppose you are new to Tidy3D and would like to experience ultrafast electromagnetic simulations. In that case, you can apply for a free trial, which allows you to test many small to medium-sized simulations. The free trial aims to familiarize you with Tidy3D and evaluate its capabilities for your project. During the trial period, we provide full technical support to answer any questions you might have about using Tidy3D.
tidy3d
, run
If you want to try Tidy3D, you don’t need to install Python. First, you’ll need to sign up for a free user account. Then you can try both, the feature-rich Tidy3D GUI interface and a pre-installed Tidy3D Python notebook development environment.
Submitting and monitoring jobs and downloading the results are all done through our web API. After a successful run, all data for all monitors can be downloaded in a single .hdf5
file using tidy3d.web.load()
, and the raw data can be loaded into a SimulationData object.
From the SimulationData object, one can grab and plot the data for each monitor with square bracket indexing, inspect the original Simulation object, and view the log from the solver run. For more details, see this beginer tutorial and this advanced tutorial.
run
method assim_data
.job
object using tidy3d.web.Job
, you can upload it to our servers with web.upload(simulation, task_name="task_name", verbose=verbose)
and it will not run until you explicitly tell it to do so with web.start(job.task_id)
. To monitor the simulation's progress and wait for its completion, use web.monitor(job.task_id, verbose=verbose)
. After running the simulation, you can load the results using sim_data = web.load(job.task_id, path="out/simulation.hdf5", verbose=verbose)
. In this notebook, you will find detailed information on how to run simulations using the web API.web.run()
method shows the simulation progress by default. When uploading a simulation to the server without running it, you can use the web.monitor(task_id)
, job.monitor()
, or batch.monitor()
methods to display the progress of your simulation(s). You can find detailed information on submitting simulation to the server in this tutorial.After the simulation is complete, you can load the results into a SimulationData object by its task_id
using:
The web.load() method is very convenient to load and postprocess results from simulations created using Tidy3D GUI.
The job
container has a convenient method to save and load the results of a job that has already finished without needing to know the task_id
, as below:
To access the original Simulation object that created the simulation data you can use
sim_data.to_file(fname='path/to/file.hdf5')
to save a SimulationData object to a HDF5 file, and sim_data = SimulationData.from_file(fname='path/to/file.hdf5')
to load a SimulationData object from a HDF5 file.obj
is an instance of ObjClass
, save and load it with obj.to_file(fname='path/to/file.json')
and obj = ObjClass.from_file(fname='path/to/file.json')
, respectively.To get all the data in a Tidy3D object obj
as a dictionary, you should use the command obj.dict()
.
We can get the cost estimate of running the task before running it. This prevents us from accidentally running large jobs we set up by mistake. The estimated cost is the maximum cost corresponding to running all the time steps. To do so, run a code like below:
See this notebook to obtain other details about submitting simulations to the server.
To obtain the cost of a simulation, you can use the function tidy3d.web.real_cost(task_id)
. In the example below, a job is created, and its cost is estimated. After running the simulation, the real cost can be obtained. It is important to note that the real cost may not be available immediately after the simulation is finished.
The cost of a simulation is primarily affected by the number of grid points and time steps. To reduce the simulation cost, you can take specific actions. However, it’s essential to gather relevant information about the simulation first to help you in this process.
In Tidy3D simulations, field symmetries can significantly reduce computational time and FlexCredit cost, sometimes by factors of 1/2, 1/4, or even 1/8. Therefore, symmetry is preferred whenever applicable. However, it is crucial to set up the symmetry correctly to avoid inaccurate results. For a more detailed explanation of symmetry, please refer to the dedicated tutorial.
To reduce the number of grid points (and time steps) in a simulation, you can adjust the GridSpec specifications. You have the option to choose between AutoGrid, UniformGrid, or CustomGrid for each simulation direction. Starting with the default object AutoGrid is generally a good strategy to discretize the entire simulation domain. You can then fine-tune the mesh by increasing grid resolution for directions or regions with smaller geometric features or high field gradients. You can also relax the discretization along directions of invariant geometry, such as the propagation direction of channel waveguides. Another way to enhance simulation accuracy while keeping the grid points small is by defining an override structure.
By default, Tidy3D periodically checks the total field intensity left in the simulation and compares that to the maximum total field intensity recorded at previous times. If it is found that the ratio of these two values is smaller than \(10^{-5}\), the simulation is terminated as the fields remaining in the simulation are deemed negligible. The shutoff value can be controlled using the tidy3d.Simulation.shutoff
parameter, or completely turned off by setting it to zero. In most cases, the default behavior ensures that results are correct while avoiding unnecessarily long run times. The Flex Unit cost of the simulation is also proportionally scaled down when early termination is encountered.
When running simulations, it’s important to use appropriate boundary conditions to absorb incoming waves and minimize reflection accurately. The tidy3d.PML boundary condition is generally the best choice, as it can absorb waves from all angles with minimal reflection. However, in some instances where an angled structure or dispersive materials are present within the PML, you may need to use the tidy3d.Absorber instead. While the absorber performs a similar function to the PML, it has a slightly higher reflection rate and requires more computation, resulting in higher simulation costs.
See this notebook for more details on setting up boundary conditions.
To keep track of the details of a simulation, a log file is created that contains information about the simulation size, symmetries, number of computational grid points, time steps, shut-off condition, and the time taken for simulation setup and running. If you need to print out the log file of a simulation, you can use the command print(sim_data.log)
.
We generally assume the following physical units in component definitions:
- Length: micron (μm, $10^{-6}$ meters)
- Time: Second ($s$)
- Frequency: Hertz ($Hz$)
- Electric conductivity: Siemens per micron ($S/μm$)
Thus, the user should be careful, for example, to use the speed of light in μm/s when converting between wavelength and frequency. The built-in speed of light C_0 has a unit of μm/s.
For example:
Currently, only linear evolution is supported, and so the output fields have an arbitrary normalization proportional to the amplitude of the current sources, which is also in arbitrary units. In the API Reference, the units are explicitly stated where applicable.
Output quantities are also returned in physical units, with the same base units as above. For time-domain outputs as well as frequency-domain outputs when the source spectrum is normalized out (default), the following units are used:
- Electric field: Volt per micron ($V/μm$)
- Magnetic field: Ampere per micron ($A/μm$)
- Flux: Watt ($W$)
- Poynting vector: Watt per micron squared ($W/μm^{2}$)
- Modal amplitude: Square root of watt ($W^{1/2}$)
If the source normalization is not applied, the electric field, magnetic field, and modal amplitudes are divided by Hz, while the flux and Poynting vector are divided by $Hz^{2}$.
tidy3d.Simulation(size=[size_x, size_y, 0])
). Additionally, specify a tidy3D.Periodic boundary condition in that direction. For an example of running a 2D simulation in Tidy3D, see the 2D ring resonator notebook.Depending on the size of the simulation task submitted, our cloud always tries to dynamically allocate the optimal amount of computational resources to run this task. When the server is busy, the resources could become limited, so a smaller amount of resources are assigned to run the task, making the simulation time slightly longer than usual. However, this should be relatively rare as we constantly monitor the status of our server and ensure ample hardware resources are available at all times.
The frequency-domain response obtained in the FDTD simulation only accurately represents the continuous-wave response of the system if the fields at the beginning and at the end of the time stepping are (very close to) zero. So, you should run the simulation for enough time to allow the electromagnetic fields to decay to negligible values within the simulation domain.
When dealing with light propagation in a NON-RESONANT device, like a simple optical waveguide, a good initial guess to simulation run_time
would be a few times the largest domain dimension ($L$) multiplied by the waveguide mode group index ($n_g$), divided by the speed of light in a vacuum ($c_0$), plus the source_time
.
tidy3d.Simulation.shutoff
parameter, or completely turned off by setting it to zero. In most cases, the default behavior ensures that results are correct while avoiding unnecessarily long run times. The Flex Unit cost of the simulation is also proportionally scaled down when early termination is encountered.This repo offers a limited ability to convert .lsf
project files to Tidy3D skeleton files in Python. Not every command in the lsf
file is covered. The lsf
project files often have default values/conventions that are not specified, so the created Tidy3D script will often need additional specification. Always be sure to check over the created Tidy3D script to see if any values are missing or if any objects have not been parsed.
tidy3d.web.Batch.run()
to upload, run, and get the simulations results in a tidy3d.web.BatchData object. For example:After running the simulations, you can get the results from the tidy3d.web.BatchData object directly, using for example sim_data_1 = batch_results["sim_1"]
. Or iterating over it in a loop, as below:
In this notebook you will find a detailed example of how to run parameter sweeps.v
tidy3d.web.Batch.run()
to upload, run, and get the simulations results in a tidy3d.web.BatchData object. For example:After running the simulations, you can get the results from the tidy3d.web.BatchData object directly, using for example sim_data_1 = batch_results["sim_1"]
. Or iterating over it in a loop, as below:
In this notebook, you will find a detailed example of how to run parameter sweeps.
When a batch is created, a batch.hdf5
file will be created automatically. Users can use this file to collect all the simulation results from the batch. First, load the batch.hdf5
file by
Then download and load all simulations results into a BatchData object by
Then, you can further extract the result for each simulation from batch_results
.
sim_data_1 = batch_results["sim_1"]
. Or iterating over it in a loop, as below:So, you will get access to the tidy3d.SimulationData instances to perform your postprocessing.
to_file(path)
and from_file(path)
methods that will export and load their metadata as JSON files. This is especially useful for loading batches for long analysis after they have run. For example, one can save the batch information to file and load the batch later if one needs to disconnect from the service while the jobs are running.
Sometimes, a simulation is numerically unstable and can result in divergence. All known cases where this may happen are related to PML boundaries and/or dispersive media. Below is a checklist of things to consider.
plugins.StableDispersionFitter
.By default, Tidy3D periodically checks the total field intensity left in the simulation, and compares that to the maximum total field intensity recorded at previous times. If it is found that the ratio of these two values is smaller than \(10^{-5}\), the simulation is terminated as the fields remaining in the simulation are deemed negligible. The shutoff value can be controlled using the Simulation.shutoff
parameter or completely turned off by setting it to zero. In most cases, the default behavior ensures that results are correct while avoiding unnecessarily long run times. The Flex Unit cost of the simulation is also proportionally scaled down when early termination is encountered.
When early termination happens, you may sometimes get a warning that the fields remaining in the simulation at the end of the run have not decayed down to the pre-defined shutoff value. This should usually be avoided (that is to say, Simulation.run_time
should be increased), but there are some cases in which it may be inevitable. The important thing to understand is that in such simulations, frequency-domain results cannot always be trusted. The frequency-domain response obtained in the FDTD simulation only accurately represents the continuous-wave response of the system if the fields at the beginning and at the end of the time stepping are (very
close to) zero. That said, there could be non-negligible fields in the simulation. Yet, the data recorded in a given monitor can still be accurate if the leftover fields are no longer passing through the monitor volume. From the point of view of that monitor, fields have already fully decayed. However, there is no way to automatically check this. The accuracy of frequency-domain monitors when fields have not fully decayed is also discussed in one of our FDTD 101
videos.
The primary use case in which you may want to ignore this warning is when you have high-Q modes in your simulation that would require an extremely long run time to decay. In that case, you can use the ResonanceFinder plugin to analyze the modes, as well as field monitors with apodization to capture the modal profiles. The only thing to note is that the normalization of these modal profiles would be arbitrary and would depend on the exact run time and apodization definition. An example of such a use case is presented in our high-Q photonic crystal cavity case study.
Structures can indeed be larger than the simulation domain in Tidy3D. In such cases, Tidy3D will automatically truncate the geometry that goes beyond the domain boundaries. For best results, structures that intersect with absorbing boundaries or simulation edges should extend all the way through. In many such cases, an “infinite” size td.inf
can be used to define the size along that dimension.
You may notice in Tidy3D versions 1.5 and above that it is no longer possible to modify instances of Tidy3D components after they are created. Making Tidy3D components immutable like this was an intentional design decision intended to make Tidy3D safer and more performant.
For example, Tidy3D contains several "validators" on input data. If models are mutated, we can't always guarantee that the resulting instance will still satisfy our validations, and the simulation may be invalid.
Furthermore, making the objects immutable allows us to cache the results of many expensive operations. For example, we can now compute and store the simulation grid without worrying about the value becoming stale later, which significantly speeds up plotting and other operations.
If you have a Tidy3D component that you want to recreate with a new set of parameters, instead of obj.param1 = param1_new
, you can call obj_new = obj.copy(update=dict(param1=param1_new))
. Note that you may also pass more key value pairs to the dictionary in update
. Also, note you can use a convenience method obj_new = obj.updated_copy(param1=param1_new)
, which is just a shortcut to the obj.copy()
call above.
Dispersive materials are supported in Tidy3D, and we provide an extensive material library with pre-defined materials. Standard dispersive material models can also be defined. If you need help inputting a custom material, let us know!
It is important to keep in mind that dispersive materials are inevitably slower to simulate than their dispersion-less counterparts, with complexity increasing with the number of poles included in the dispersion model. For simulations with a narrow range of frequencies of interest, it may sometimes be faster to define the material through its real and imaginary refractive index at the center frequency. This can be done by defining directly a value for the real part of the relative permittivity $\mathrm{Re}(\epsilon_r)$ and electric conductivity $\sigma$ of a Medium, or through a real part $n$ and imaginary part $k$. The relationship between the two equivalent models is $\mathrm{Re}(\epsilon_r) = n^2 - k^2$, $\mathrm{Im}(\epsilon_r) = 2nk$, and $\sigma = 2 \pi f \epsilon_0 \mathrm{Im}(\epsilon_r)$.
In the case of (almost) lossless dielectrics, the dispersion could be negligible in a broad frequency window, but generally, it is importat to keep in mind that such a material definition is best suited for single-frequency results.
For lossless, weakly dispersive materials, the best way to incorporate the dispersion without doing complicated fits and without slowing the simulation down significantly is to provide the value of the refractive index dispersion $\mathrm{d}n/\mathrm{d}\lambda$ in Sellmeier.from_dispersion(). The value is assumed to be at the central frequency or wavelength (whichever is provided), and a one-pole model for the material is generated. These values are, for example, readily available from the refractive index database.
Yes, users can import their own tabulated material data and fit it using one of Tidy3D’s dispersion fitting tools. The FastDispersionFitter tool performs an optimization to find a medium defined as a dispersive PoleResidue model that minimizes the RMS error between the model results and the data. The user can provide data through one of the following methods:
wvl_um
, n_data
, and optionally k_data
.from_file
utility function. The data file has columns for wavelength ($μm$), the real part of the refractive index ($n$), and the imaginary part of the refractive index ($k$). $k$ data is optional. Note: from_file
uses np.loadtxt
under the hood, so additional keyword arguments for parsing the file follow the same format as np.loadtxt
.from_url
utility function. URL can come from refractiveindex.This notebook provides detailed instructions and examples of using the fitter.
To create a lossy material including conductivity, use the tidy3d.Medium object and set the conductivity
parameter. For example:
To create a material from the real ($n$) and imaginary ($k$) parts of refractive index, use the tidy3d.Medium.from_nk(). For example:
Negative $k$ value corresponds to a gain medium. It is only allowed when the parameter
allow_gain
is set toTrue
.
You can import your own tabulated material data and fit it using one of Tidy3D’s dispersion fitting tools. The FastDispersionFitter tool performs an optimization to find a medium defined as a dispersive PoleResidue model that minimizes the RMS error between the model results and the data. The user can provide data through one of the following methods:
wvl_um
, n_data
, and optionally k_data
.from_file
utility function. The data file has columns for wavelength ($μm$), the real part of the refractive index ($n$), and the imaginary part of the refractive index ($k$). $k$ data is optional. Note: from_file
uses np.loadtxt
under the hood, so additional keyword arguments for parsing the file follow the same format as np.loadtxt
.from_url
utility function. URL can come from refractiveindex.This notebook provides detailed instructions and examples on using the fitter.
To create a dispersive material from model parameters, you only need to instantiate the medium object and provide its parameters. For example, debye_medium = td.Debye(eps_inf=2.0, coeffs=[(1,2),(3,4)])
.
To create fully anisotropic mediums including all 9 components of the permittivity and conductivity tensors, you can use the tidy3d.FullyAnisotropicMedium object. The provided permittivity tensor and the symmetric part of the conductivity tensor must have coinciding main directions. However, a non-symmetric conductivity tensor can be used to model magneto-optic effects. Note that dispersive properties and subpixel averaging are currently not supported for fully anisotropic materials.
Alternatively, you can create a diagonally anisotropic material, using the tidy3d.AnisotropicMedium(xx=medium_xx, yy=medium_yy, zz=medium_zz) object, and then include three medium objects defining the diagonal elements of the permittivity tensor. In this case, the medium objects can be of type Medium, PoleResidue, Sellmeier, Lorentz, Debye, or Drude. For example:
allow_gain=True
in any medium, e.g. tidy3d.Medium(permittivity=2.0, conductivity=-1.0, allow_gain=True)
.To export a spatially varying medium dataset to a HDF5 file you should use the to_hdf5(filename)
method. In the example below, we illustrate how to do that after creating a tidy3d.CustomMedium.
The key of the dictionary is the abbreviated material name. Some materials have multiple variant models, in which case the second key is the “variant” name.
You can create a 2D material using the tidy3d.Medium2D object. This is especially helpful for building very thin materials, like metal layers.
You can create a graphene medium using tidy3d.Graphene, which defines a parametric surface conductivity model for graphene. For example:
To create nonlinear material, you should specify a tidy3d.NonlinearSusceptibility to the nonlinear_spec
parameter of any medium. For example:
chi3
is the nonlinear susceptibility, and numiters
is the number of iterations for solving nonlinear constitutive relation.In Tidy3D, complex structures can be imported from GDSII files via the third-party gdstk package, which you can install running pip install gdstk
. To load the geometry from a GDSII file, you should select the cell with the geometry you want. It is usually easier to verify that we can find the correct one by name first, for example:
Then you can construct Tidy3D geometries from the GDS cell just loaded, along with other information such as the axis, sidewall angle, and bounds of the "slab" using tidy3d.Geometry.from_gds(). When loading GDS cell as the cross section of the device, we can tune reference_plane
to set the cross-section to lie at bottom
, middle
, or top
of the generated geometry with respect to the axis. E.g. if axis=1
, bottom
refers to the negative side of the y-axis, and top
refers to the positive side of the y-axis. Additionally, we can optionally dilate or erode the cross section by setting dilation
. A negative dilation
corresponds to erosion. Note, we have to keep track of the gds_layer
and gds_dtype
used to define the GDS cell earlier, so we can load the right components.
You can find more details on importing GDSII files in these notebooks: Importing GDS files; Defining self-intersecting polygons.
To use the STL import functionality, you must install Tidy3D as pip install "tidy3d[trimesh]"
, which will install optional dependencies for processing surface meshes. Then you can use the tidy3d.TriangleMesh.from_stl() function. In the following example, we will import a simple box geometry from a STL file.
See this example for a complete reference on importing STL files.
In Tidy3D, you can export structures to GDSII file via the third-party gdstk package, which you can install running pip install gdstk
. The example below creates a simple geometry and then exports it to GDSII:
The method .to_gds_file()
is another option to export a geometry to GDSII. For example:
You can find more details on exporting structures to GDSII files in the notebook Importing GDS files.
You can create a box geometry using the tidy3d.Box object. You can specify the center
and size
parameters, as below:
Or you can use the tidy3d.Box.from_bounds() method, where you should define the rmin
and rmax
coordinates of the lower and upper box corners. For example:
You can create a sphere using the tidy3d.Sphere object and specifying the center
and radius
parameters, as below:
You can create a cylinder using the tidy3d.Cylinder object. In the example below we create a cylinder 2 $\mu$m in length, oriented along the z
-axis, with a 0.5 $\mu$m radius, and positioned at (-1,1,0). To obtain a conical shape, set the parameters sidewall_angle
and reference_plane
.
Use the tidy3d.PolySlab object to create an extruded polygon with an optional sidewall angle along the axis
direction. The polygon geometry is defined by the vertices
parameter, which receives a list of (d1, d2) coordinates defining the geometry of the polygon face at the reference_plane
. The slab_bounds
parametere defines the minimum and maximum positions of the slab along the axis
dimension. Set the sidewall_angle
with respect to the reference_plane
to create slanted sidewalls. In addition, you can dilate or erode the polygon by setting positive or negative values to dilation
parameter.
A geometry group is a convenient way to gather multiple geometry objects into one collection. It can significantly improve performance when all the geometries in the group are assigned to the same medium. To create a geometry group, use the tidy3d.GeometryGroup object and set the geometries
parameter as below:
You can combine multiple geometries using the tidy3d.ClipOperation object to perform ‘union’, ‘intersection’, ‘difference’, and ‘symmetric_difference’ operations. For example:
When two structures overlap, the last ones in the structures
list will override the permittivities of the previous structures. This notebook illustrates how to use this rule to create a photonic crystal slab. The holes
geometry with a refractive index of 1 overrides the slab
permittivities in regions where they overlap, creating the air holes.
In Tidy3D, all geometries can be translated, rotated, and scaled. These methods create a new copy of the original geometry with a transformation applied. For example, you can start with a tidy3d.Box
centered at the origin and create a copy of it rotated around the z
-axis:
See this example for more information.
In Tidy3D, all geometries can be translated, rotated, and scaled. These methods create a new copy of the original geometry with a transformation applied. For example, you can start with a tidy3d.Box
centered at the origin and create a copy of it translated in the x
-direction by 2 $\mu m$:
See this example for more information.
In Tidy3D, all geometries can be translated, rotated, and scaled. These methods create a new copy of the original geometry with a transformation applied. For example, you can start with a tidy3d.Box
centered at the origin and create a copy of it scaled by a factor of 2 in all directions:
See this example for more information.
In Tidy3D, all geometries can be translated, rotated, and scaled. These methods create a new copy of the original geometry with a transformation applied. For example, you can start with a tidy3d.Box
centered at the origin and create a copy of it rotated around the z
-axis:
Transformed geometries can be further transformed. Composing transformations is as simple as cascading the method calls. In the following example, we create an ellipsoidal prism by scaling a primitive tidy3d.Cylinder
and rotating it.
A tidy3d.Transformed object contains an inner geometry and a transformation, written as a 4 x 4 matrix and applied to the (homogeneous) coordinates of the inner geometry. It is possible to define a Transformed
object directly from the inner geometry and the transformation. To help create the most usual transformation matrices, the Transformed
class has 3 static methods for translation, rotation, and scaling that can be used and combined (the @ operator can be used for matrix multiplication with numpy
arrays).
See this example for more information.
You can use the tidy3d.ClipOperation object to combine multiple geometries through ‘union’, ‘intersection’, ‘difference’, and ‘symmetric_difference’ operations. Simply define the geometry_a
and the geometry_b
and assign them to the clip object. For example:
Tidy3D offers four primitive geometric shapes: Box, Cylinder, Sphere, and PolySlab. An extensive array of intricate geometrical configurations can be defined from these fundamental building blocks by manipulating their properties and hierarchical arrangements. For instance, our tutorials on the Luneburg lens waveguide size converter and the Fresnel lens showcase the creation of curved surfaces through the strategic layering of cylindrical elements.
Beyond the in-built primitives, Tidy3D accommodates external geometrical specifications in either the GDS or STL file formats, allowing users to import custom geometries created in their preferred design tools. Additionally, compatibility with external libraries like gdstk and shapely opens the gateway to even more complex geometric possibilities. Using gdstk to create various waveguide structures has been demonstrated in various examples, such as thepolarization splitter and rotator based on 90-degree bends](https://www.flexcompute.com/tidy3d/examples/notebooks/90BendPolarizationSplitterRotator/?__hstc=197414576.85a08fc595b47d0b94ebfa20ba44cd6d.1696006513341.1701896316776.1701901226721.28&__hssc=197414576.3.1701901226721&__hsfp=3209960735). Additionally, you can create complex geometries using the Trimesh library, as demonstrated in this tutorial.
To define complex geometries using the Trimesh library, you must install Tidy3D as pip install "tidy3d[trimesh]"
, which will install optional dependencies needed for processing surface meshes. The Trimesh library provides some built-in geometries such as ring (annulus), box, capsule, cone, cylinder, and so on. Let’s create a ring as an example.
To use this geometry in a Tidy3D simulation, you need to convert the mesh into a tidy3d.TriangleMesh geometry. Use the from_trimesh()
method to conviently convert the mesh to a Tidy3D geometry. From there, you can further define the Tidy3D structure and put it into a simulation.
This example shows how to create many different complex geometries using Trimesh.
The gdstk library offers a convenient and flexible way to construct commonly used photonic integrated circuit (PIC) components, such as straight waveguides, linear tapers, rings, race tracks, s-bends, circular bends, and directional couplers. This notebook contains pre-defined functions used to construct commonly used PIC components. Users can directly copy these pre-defined functions to their script and use them to build their simulations. More importantly, users can learn the workflow from these examples and create their own Tidy3D structures using the same principles.
Tidy3D provides four basic geometric shapes, namely, Box, Cylinder, Sphere, and PolySlab. These shapes can be used to create various periodic structures used in photonic crystals and other photonic devices such as square or hexagonal arrays of cylinders, slabs with square or hexagonal arrays of holes, rectangular grating, L and H cavities, wood pile, and FCC/BCC crystals. For this purpose, you can organize these geometries in a tidy3d.GeometryGroup. This notebook contains functions that can be used to build these popular periodic structures with ease. Moreover, users can learn from these examples and create their own periodic structures using the same principles.
Tidy3D’s broadband source feature is designed to produce the most accurate results in the frequency range of (freq0 - 1.5 * fwidth, freq0 + 1.5 * fwidth)
. Therefore, it is necessary to define the source center frequency freq0
and bandwidth fwidth
to properly cover the desired application frequency range. For example, if the user wants to adjust the source bandwidth to cover a wavelength range between wl_min
and wl_max
, the source bandwidth can be defined as: fwidth = alpha * (C_0/wl_max - C_0/wl_min)
, where alpha
is a constant typically chosen between 1/3 and 1/2 to ensure accurate results.
You can set the source frequency and bandwidth through the source_time
parameter, which accepts a tidy3d.GaussianPulse object. In the example below, we create a tidy3d.PointDipole source to radiate power at a center wavelength of 1.55 $\mu$m over a bandwidth of 100 nm.
The tidy3d.GaussianPulse object has the built-in functions plot_spectrum
and plot
that allow users to visualize the source spectrum and time-dependence, respectively. For example:
When defining a source, you must specify a source time profile, typically Gaussian. For example, we can define a plane wave as
Here, the source time is a Gaussian pulse with central frequency freq0
and frequency width 0.5 * freqw
. To visualize the spectrum it gives, we can use the plot_spectrum
method by
Here, we need to specify the sampled time instances. To ensure the source spectrum is plotted correctly, we need to ensure the time sampling is sufficiently fine and the end time is sufficiently long compared to the pulse width.
In many cases, Tidy3D simulations can be run, and well-normalized results can be obtained without normalizing/empty runs. This is because care is taken internally to normalize the injected power, as well as the output results, in a meaningful way. To understand this, there are two separate normalizations that happen, outlined below. Both are discussed with respect to frequency-domain results, as those are the most commonly used.
Every source has a spectrum associated to its particular time dependence that is imprinted on the fields injected in the simulation. Usually, this is somewhat arbitrary, and it is most convenient to take it out of the frequency-domain results. By default, after a run, Tidy3D normalizes all frequency-domain results by the spectrum of the first
source in the list of sources in the simulation. This choice can be modified using the Simulation.normalize_index
attribute, or normalization can be turned off by setting that to None
. Results can even be renormalized after the simulation run using SimulationData.renormalize(). If multiple sources are used, but they all have the same time dependence, the default normalization is still meaningful. However, if different sources have a different time dependence, then it may not be
possible to obtain well-normalized results without a normalizing run.
This type of normalization is applied directly to the frequency-domain results. The custom pulse amplitude and phase defined in SourceTime.amplitude
and SourceTime.phase
, respectively, are not normalized out. This gives the user control over a (complex) prefactor that can be applied to scale any source. Additionally, the power injected by each type of source may have some special normalization, as outlined below.
Source power normalization is applied depending on the source type. In the cases where normalization is applied, the actual injected power may differ slightly from what is described below due to finite grid effects. The normalization should become exact with sufficiently high resolution. That said, in most cases the error is negligible even at default resolution.
The injected power values described below assume that the source spectrum normalization has also been applied.
PointDipole: Normalization is such that the power injected by the source in a homogeneous material of refractive index $n$ at frequency $\omega = 2\pi f$ is given by
\[\frac{\omega^2}{12\pi}\frac{\mu_0 n}{c}.\]UniformCurrentSource: No extra normalization applied.
CustomFieldSource: No extra normalization applied.
ModeSource,
PlaneWave,
GaussianBeam,
AstigmaticGaussianBeam: Normalized to inject 1W power at every frequency. If supplied SourceTime.num_freqs
is 1
, this normalization is only exact at the central frequency of the associated SourceTime pulse but should still be very close to 1W at nearby frequencies too. Increasing num_freqs
can be used to make sure the normalization works well for a broadband source. The correct usage for a PlaneWave source is to span the whole simulation domain for a simulation with periodic (or Bloch) boundaries, in which case the normalization of this technically infinite source is equivalent to 1W per unit cell. For the other sources which have a finite extent, the normalization is correct provided that the source profile decays by the boundaries of the source plane. Verifying that this is the case is always advised, as otherwise results may be spurious beyond just the normalization (numerical artifacts will be present at the source boundary).
TFSFSource
: Normalized to inject $1W/μm^{2}$ in the direction of the source injection axis. This is convenient for computing scattering and absorption cross-sections without the need for additional normalization. Note that for angled incidence, a factor of $1/\cos(\theta)$ needs to be applied to convert to the power carried by the plane wave in the propagation direction, which is at an angle $\theta$ with respect to the injection axis. Note also that when the source spans the entire simulation domain with periodic or Bloch boundaries, the conversion between the normalization of a TFSFSource
and a PlaneWave is just the area of the simulation domain in the plane normal to the injection axis.
The tidy3d.PointDipole is a zero-dimensional uniform current source. The example below illustrates how to define tidy3d.PointDipole within a simulation.
Use the center
parameter to set the dipole position, then adjust the source_time
dependence using tidy3d.GaussianPulse. The source polarization
defines the direction and type of the current component. Finally, the parameter interpolate
handles reverse interpolation of zero-size dimensions of the source. If False
, the source data is snapped to the nearest Yee grid point. If True
, equivalent source data is applied on the surrounding Yee grid points to emulate placement at the specified location using linear interpolation.
See this notebook to an example on setting up a tidy3d.PointDipole source.
The tidy3d.PointDipole source is normalized such that the power injected in a homogeneous material of refractive index $n$ at frequency $\omega = 2\pi f$ is given by
$\frac{\omega^2}{12\pi}\frac{\mu_0 n}{c}$.
To calculate the radiated power of a dipole in the presence of dispersive, lossy, or non-homogeneous materials, you can use a tidy3d.FluxMonitor box. Refer to this notebook for an example.
The tidy3d.UniformCurrentSource is a rectangular volume source with uniform time dependence. The example below illustrates how to define a tidy3d.UniformCurrentSource within a simulation.
Use the center
and size
parameters to set the source position and volume, then adjust the source_time
dependence using tidy3d.GaussianPulse. The source polarization
defines the direction and type of the current component. Finally, the parameter interpolate
handles reverse interpolation of zero-size dimensions of the source. If False
, the source data is snapped to the nearest Yee grid point. If True
, equivalent source data is applied on the surrounding Yee grid points to emulate placement at the specified location using linear interpolation. Note that making size=(0, 0, 0)
is equivalent to including a tidy3d.PointDipole source.
The tidy3d.PlaneWave is a uniform current distribution on an infinite extent plane. The example below illustrates how to define a tidy3d.PlaneWave within a simulation.
Use the center
and size
parameters to set the source position and dimension, then adjust the source_time
dependence using tidy3d.GaussianPulse. The direction
parameter specifies propagation in the positive or negative direction of the injection axis. You can change the light polarization using pol_angle
, and adjust the propagation axis direction with angle_theta
and angle_phi
to control the polar and azimuth angles.
This example illustrates setting up a tidy3d.PlaneWave source at normal and off-normal incidences.
The tidy3d.ModeSource injects a current source in the simulation to excite a modal profile in a finite extent plane. It is commonly used to excite specific waveguide modes in photonic integrated circuits. To illustrate how to set up a tidy3d.ModeSource, let’s consider the case of injecting the first-order transverse electric (TE) mode in a silicon-on-insulator (SOI) waveguide operating at 1.55 $\mu$m.
You should use the center
and size
parameters to define a source plane surrounding the waveguide, then adjust the source_time
dependence using tidy3d.GaussianPulse. The direction='+'
parameter specifies propagation in the positive waveguide axis. The tidy3d.ModeSpec object includes all the specifications of a mode solver, which calculates the optical modes given the material distribution within the source plane. The modes calculated by the mode solver are sorted by their effective indices in descending order. So, we have set the initial mode solver guess to the core refractive index (target_neff=3.47
) and chosen filter_pol='te'
to make sure it will return first in the list of the TE waveguide modes, starting from the fundamental one. Finally, to inject the first-order TE mode in the waveguide, we setmode_index=1
`.
This example illustrates setting up a tidy3d.ModeSource source.
To inject a specific optical mode in the waveguide, you can use the tidy3d.ModeSource source. Let’s consider the case of injecting the first-order transverse electric (TE) mode in a silicon-on-insulator (SOI) waveguide operating at 1.55 $\mu$m as an example:
You should use the center
and size
parameters to define a source plane surrounding the waveguide, then adjust the source_time
dependence using tidy3d.GaussianPulse. The direction='+'
parameter specifies propagation in the positive waveguide axis. The tidy3d.ModeSpec object includes all the specifications of a mode solver, which calculates the optical modes given the material distribution within the source plane. The modes calculated by the mode solver are sorted by their effective indices in descending order. So, we have set the initial mode solver guess to the core refractive index (target_neff=3.47
) and chosen filter_pol='te'
to make sure it will return first in the list of the TE waveguide modes, starting from the fundamental one. Finally, to inject the first-order TE mode in the waveguide, we set mode_index=1
.
This example illustrates setting up a tidy3d.ModeSource source.
To inject an optical mode in a waveguide bend, you must set the bend_radius
and bend_axis
parameters of tidy3d.ModeSpec. For example:
You can find a detailed example in this notebook.
The source tidy3d.GaussianBeam is a Guassian distribution on a finite extent plane. The example below illustrates how to define the tidy3d.GaussianBeam within a simulation.
Use the center
and size
parameters to set the source position and dimension, then adjust the source_time
dependence using tidy3d.GaussianPulse. The direction
parameter specifies propagation in the positive or negative direction of the injection axis. You can change the light polarization using pol_angle
, and adjust the propagation axis direction with angle_theta
and angle_phi
to control the polar and azimuth angles. In this example, the beam’s radius at the waist position was adjusted to 1$\mu$m using the waist_radius
parameter. When waist_distance
is positive (negative), the waist is behind (front) the source plane.
See this notebook to an example on setting up a tidy3d.GaussianBeam source.
To simulate an optical fiber mode source, you can use the tidy3d.ModeSource. This object allows you to solve for the optical modes of a fiber cross-section. You can then include this in your simulation by following the steps outlined in the example.
If you prefer, you can also use the tidy3d.GaussianBeam source instead to approximate the optical mode of the fiber with a Gaussian distribution, as explained in more detail in another example.
To create a converging Gaussian beam, include a tidy3d.GaussianBeam source in the simulation, and set the waist_distance
to negative values. This way, the beam waist will lie in the front of the source plane, as illustrated in the following example
See this notebook to an example on setting up a tidy3d.GaussianBeam source.
To create a diverging Gaussian beam, include a tidy3d.GaussianBeam source in the simulation, and set them waist_distance
to positive values. This way, the beam waist will lie behind the source plane, as illustrated in the following example
See this notebook to an example on setting up a tidy3d.GaussianBeam source.
The tidy3d.AstigmaticGaussianBeam class implements the simple astigmatic Gaussian beam described in Kochkina et al., Applied Optics, vol. 52, issue 24, (2013)
. The simple astigmatic Guassian distribution allows both an elliptical intensity profile and different waist locations for the two principal axes of the ellipse. The following example illustrates how to set up a tidy3d.AstigmaticGaussianBeam.
Use the center
and size
parameters to set the source position and dimension, then adjust the source_time
dependence using tidy3d.GaussianPulse. The direction
parameter specifies propagation in the positive or negative direction of the injection axis. You can change the light polarization using pol_angle
, and adjust the propagation axis direction with angle_theta
and angle_phi
to control the polar and azimuth angles. In this example, different waist_sizes
and waist_distances
were specified in the x-
and y-
directions.
The total-field scattered-field (TFSF) source injects a plane wave in a finite region. The example below illustrates how to define the tidy3d.TFSF within a simulation.
Use the center
and size
parameters to set the source position and dimension, then adjust the source_time
dependence using tidy3d.GaussianPulse. The direction
parameter specifies propagation in the positive or negative direction of the injection axis. You can change the light polarization using pol_angle
, and adjust the propagation axis direction with angle_theta
and angle_phi
to control the polar and azimuth angles. The injection_axis
parameter specifies injection along the x
(0), y
(1), or z
(2) direction.
See this notebook to an example on setting up a tidy3d.TFSF source.
The tidy3d.CustomFieldSource source can be used to inject a specific (E
, H
) field distribution on a plane, e.g. coming from another simulation. Internally, we use the equivalence principle to compute the actual source currents (all sources in FDTD have to be converted to current sources). Because of this, the custom field source will only produce reliable results if the provided fields decay by the edges of the source plane, or if they extend through the simulation boundaries and are well-matched to those boundaries.
The example below illustrates how to define the tidy3d.CustomFieldSource using a dataset containing the E
and H
fields to describe a Gaussian field profile.
See this notebook to an example on setting up a tidy3d.CustomFieldSource source.
The tidy3d.CustomCurrentSource source can be used to inject a raw electric and magnetic current distribution within the simulation. Its syntax is very similar to that of CustomFieldSource
, except the source accepts a current_dataset
instead of a field_dataset
, and it can be volumetric or planar without requiring tangential components. This dataset still contains the E{x,y,z}
and H{x,y,z}
field components, which correspond to J
and M
components, respectively.
See this notebook to an example on setting up a tidy3d.CustomCurrentSource source.
To inject an optical mode in an angled waveguide, you must set the angle_theta
and angle_phi
parameters of tidy3d.ModeSpec. For example:
You can find a detailed example in this notebook.
Tidy3D tries to provide an illusion of continuity as much as possible, but at the level of the solver, a finite numerical grid is used, which can have some implications that advanced users may want to be aware of.
The FDTD method for electromagnetic simulations uses what is called the Yee grid, in which every field component is defined at a different spatial location, as illustrated in the figure, as well as in our FDTD video tutorial FDTD 101 videos. On the left, we show one cell of the full 3D Yee grid and where the various E
and H
field components live. On the right, we show a cross-section in the xy plane and the locations of the Ez
and Hz
field components in that plane (note that these field components are not in the same cross-section along z
but rather also offset by half a cell size). This illustrates a duality between the grids on which E
and H
fields live, which is related to the duality between the fields themselves. There is a primal grid, shown with solid lines, and a dual grid, shown with dashed lines, with the Ez
and Hz
fields living at the primal/dual vertices in the xy
-palne, respectively. In some literature on the FDTD method, the primal and dual grids may even be switched as the definitions are interchangeable. In Tidy3D, the primal grid is as defined by the solid lines in the Figure.
When computing results that involve multiple field components, like Poynting vector, flux, or total field intensity, it is important to use fields that are defined at the same locations for best numerical accuracy. The field components thus need to be interpolated, or colocated, to some common coordinates. All this is already done under the hood when using Tidy3D in-built methods to compute such quantities. When using field data directly, Tidy3D provides several conveniences to handle this. Firstly, field monitors have a colocate
option, set to True
by default, which will automatically return the field data interpolated to the primal grid vertices. The data is then ready to be used directly for computing quantities derived from any combination of the field components. The colocate
option can be turned off by advanced users, in which case each field component will have different coordinates as defined by the Yee grid. In some cases, this can lead to more accurate results, as discussed, for example, in the custom source
example. In that example, when using data generated by one simulation as a source in another, it is best to use the fields as recorded on the Yee grid.
Regardless of whether the colocate
option is on or off for a given monitor, the data can also be easily colocated after the solver run. In principle, if colocating to locations other than the primal grid in post-processing, it is more accurate to set colocate=False
in the monitor to avoid double interpolation (first to the primal grid in the
solver, then to new locations). Regardless, the following methods work for both Yee grid data and data that has already been previously colocated:
data_at_boundaries = sim_data.at_boundaries(monitor_name)
to colocate all fields of a monitor to the Yee grid cell boundaries (i.e. the primal grid vertexes).data_at_centers = sim_data.at_centers(monitor_name)
to colocate all fields of a monitor to the Yee grid cell centers (i.e. the dual grid vertexes).data_at_coords = sim_data[monitor_name].colocate(x=x_points, y=y_points, z=z_points)
to colocate all fields to a custom set of coordinates. Any or all of x
, y
, and z
can be supplied; if some are not, the original data coordinates are kept along that dimension.The FDTD and other similar numerical methods will always give approximate results for a set of finite-difference equations. The accuracy of Maxwell’s equations solution for any geometry can be arbitrarily increased by using smaller and smaller values of the space and time increments. This strategy often involves increased simulation time and memory, so it is essential to consider, for your application, the desired accuracy in results so that you can run your simulations as quickly as possible. As a gold rule of thumb, ten grid points per wavelength in the highest refractive index medium should be a good starting value for the grid resolution. However, other application specificities must be considered when defining the appropriate simulation mesh, such as very thin geometries or large electric field gradients, as usually occurs, for example, in the presence of resonances, highly confined fields, or at metal-dielectric interfaces.
Tidy3D has many features that give users a simple and flexible way to build the simulation mesh. The GridSpec object enables the user to chose between an AutoGrid, a UniformGrid, or a CustomGrid, at each of the simulation x-, y-, z-direction. An example code snippet is shown below:
More examples of setting up the simulation mesh are available on this notebook.
In general, a good strategy is to start with the default object AutoGrid to discretize the whole simulation domain and fine-tune the mesh by increasing the grid resolution at directions or regions containing smallest geometric features or high field gradients or even relaxing the discretization along directions of invariant geometry, e.g., the propagation direction of channel waveguides. The definition of an override structure is an efficient way to improve simulation accuracy while keeping the run time small.
By default, Tidy3D configures the GridSpec object to having AutoGrid, which is an advanced meshing algorithm to automatically define a nonuniform grid in all three domain directions. The resolution of this grid is specified using the desired minimum steps per wavelength in each material (min_steps_per_wvl = 10 by default
). This specification, therefore, requires a target wavelength, which can be provided directly to grid_spec
or inferred from any sources present in the simulation. Detailed examples on how to set up AutoGrid are present on this notebook.
As a gold rule of thumb, the default value of 10 grid points per wavelength should be a good starting value for min_steps_per_wvl
. However, other application-specific features must be considered when defining the appropriate simulation mesh, such as very thin geometries or large electric field gradients, as can usually occur, for example, in the presence of resonances, highly confined fields, or at metal-dielectric interfaces. Additional control over the mesh is obtained by the dl_min
parameter, which imposes a lower bound of the grid size regardless of the structures present in the simulation, including override structures with enforced=True
. This is, however, a soft bound, meaning that the actual minimal grid size might be slightly smaller. Finally, the max_scale
sets the maximum ratio between two consecutive grid steps. Different grid configurations can be chosen for each direction, as illustrated below:
The most standard way to define a simulation is to use a constant grid size in each of the three directions. This can be achieved simply using tidy3d.GridSpec.uniform(dl=...)
as shown below.
In some problems, the user may want to refine the grid mesh locally around specific geometry features, such as the gap between two very close waveguides where we expect the fields to be strongest. This can be achieved by adding override_structures
to the simulation grid_spec
. The override structures is a list of Tidy3D structures, each with an arbitrary geometry, used exclusively for the meshing. It is added on top of any physical simulation structures. There are two types of Tidy3D structures that can be added to override_structures
list. The first type defines a fictitious medium inside the override structure so that the grid size is decided by the minimum steps per wavelength in the medium. The second type is more straightforward: one can directly define the grid size along each axis inside the override structures.
The first type is identical to the Structure object that consists of a Geometry and a Medium. The grid step in the override_structure
region is decided by the minimum steps per wavelength in this medium
.
The second type is the tidy3d.MeshOverrideStructure object that consists of a Geometry, and a tuple dl
specifying the grid sizes along x
, y
, and z
-directions. We can override the grid sizes just along a few selected directions by setting the value to be None
in the dl
tuple along the other directions. E.g., if we only plan to refine the grid size along x
-direction with grid size 0.01 $\mu$m, we can apply dl=(0.01, None, None)
. In the following, we override the grid size along y
and z
to be 15.5nm.
Tidy3D includes the following boundary condition types: Periodic, PECBoundary, PMCBoundary, BlochBoundary, PML, StablePML, and Absorber.
You should use tidy3d.PML boundary condition to enclose the simulation domain with layers of a special lossy material designed to absorb incoming waves from all angles with minimal reflection. Tidy3D uses PML boundary conditions by default, but you can also set the boundaries explicitly using the all_sides()
method. For example:
See this notebook for more details on setting up boundary conditions.
You should use tidy3d.PECBoundary to enclose the simulation domain using perfect electric conductors. For example:
See this notebook for more details on setting up boundary conditions.
You should use tidy3d.PMCBoundary to enclose the simulation domain using perfect magnetic conductors. For example:
See this notebook for more details on setting up boundary conditions.
x
-direction along a periodic structure with period $L_x$, they must satisfy:x
-direction.See this notebook for more details on setting up boundary conditions.
source
, background medium
, axis
, and domain_size
information.See this notebook for more details on setting up boundary conditions.
The tidy3d.Absorber boundary condition specifies an adiabatic absorber along a single dimension. This absorber is well-suited for dispersive materials intersecting with absorbing edges of the simulation at the expense of more layers. The example below shows how to set up an absorbing boundary.
See this notebook for more details on setting up boundary conditions.
See this notebook for more details on setting up boundary conditions.
Structures can indeed be larger than the simulation domain in Tidy3D. In such cases, Tidy3D will automatically truncate the geometry that goes beyond the domain boundaries. For best results, structures that intersect with absorbing boundaries or simulation edges should extend all the way through. In many such cases, an “infinite” size tidy3d.inf can be used to define the size along that dimension.
It is essential to keep in mind that PML only absorbs propagating fields. For evanescent fields, PML can act as an amplification medium and cause a simulation to diverge. In Tidy3D, a warning will appear if the distance between a structure is smaller than half of a wavelength to prevent evanescent fields from leaking into PML. In most cases, the evanescent field will naturally die off within half a wavelength, but in some instances, a larger distance may be required.
In Tidy3D, a warning will appear if the distance between a structure and the absorbing layers is smaller than half of a wavelength to prevent evanescent fields from leaking into PML. In most cases, the evanescent field will naturally die off within half a wavelength, but in some instances, a larger distance may be required. It is important to keep in mind that PML only absorbs propagating fields. PML can act as an amplification medium for evanescent fields and cause a simulation to diverge.
You should use tidy3d.PML boundary condition to enclose the simulation domain with layers of a special lossy material designed to absorb incoming waves from all angles with minimal reflection. Tidy3D uses PML boundary conditions by default, but you can also set the boundaries explicitly using the all_sides()
method. For example:
In some cases, such as when an angled structure or dispersive materials lie within the PML, use tidy3d.Absorber instead. The absorber functions similarly to PML, absorbing the outgoing radiation to mimic the infinite space. However, the absorber has a slightly higher reflection and requires a bit more computation than PML, but it is numerically much more stable.
See this notebook for more details on setting up boundary conditions.
See this notebook for more details on setting up boundary conditions.
Periodic
and Bloch
boundary conditions are very useful for simulating periodic structures. When using Periodic
boundary conditions, the fields are registered on one edge of the simulation domain and re-injected at the opposite edge. Bloch
boundary conditions are similar, but they also apply a phase correction term to the fields. In other words, Periodic
boundary conditions can be considered a special case of Bloch
boundaries. When a normal incident plane wave is considered, there will not be any difference between them. However, if we consider a plane wave propagating at an angle, the fields from one period to the next will not be exactly periodic and will be out of phase by some amount. The Bloch
boundary condition corrects this factor. Therefore, when injecting plane waves at an angle, Bloch
boundaries should be used.
In many Tidy3D simulations, the application of field symmetries can markedly decrease computational time and FlexCredit cost, potentially achieving reductions by factors of 1/2, 1/4, or even 1/8. Therefore, we prefer to use symmetry whenever applicable. In addition, symmetry effectively filters out unwanted light polarizations or modes.
However, correctly setting up the symmetry is essential to avoid getting incorrect results. For a more extensive discussion on symmetry, please visit the dedicated tutorial.
To configure symmetry in your simulation, assign a tuple of integers to the symmetry parameter. This tuple defines the reflection symmetry across planes bisecting the simulation domain normal to the x-, y-, and z-axes. Each element of the tuple can be set to 0 for no symmetry, 1 for even symmetry (equivalent to ‘PMC’ symmetry), or -1 for odd symmetry (equivalent to ‘PEC’ symmetry). For example,
It is crucial to consider the vectorial nature of the electromagnetic fields when determining the appropriate symmetry value. For a more extensive discussion on symmetry, please visit the dedicated tutorial.
To identify the symmetry planes in your simulation, the first step is to look for reflection symmetries in the geometry as well as the sources of your setup. If your entire simulation setup has reflection symmetry with respect to the x=0
plane, then symmetry can be applied to the x
-direction. Same for the y
- and z
-directions. This only determines if symmetry exists but doesn’t tell us what type (even or odd) of symmetry should be applied.
Once we identify the existence of symmetry in a direction, we need to evaluate the source field. When symmetry exists, certain field components are necessarily zero at the plane of symmetry. PMC symmetry (even) corresponds to zero normal electric field and zero tangential magnetic field at the symmetry plane. PEC symmetry (odd) corresponds to zero tangential electric fields and zero normal magnetic fields at the symmetry plane. Another rule of thumb is to check if the electric field created by the source is perpendicular to the symmetry plane (odd symmetry) or parallel to the symmetry plane (even symmetry).
For example, a plane wave polarized in the y
-direction propagating in the z
-direction has symmetry (1, -1, 0) since the electric field is parallel to the x=0
plane and perpendicular to the y=0
plane.
For a more extensive discussion on symmetry, please visit the dedicated tutorial.
PEC symmetry (odd) corresponds to zero normal electric fields and zero tangential magnetic fields at the symmetry plane.
PMC symmetry (even) corresponds to zero tangential electric fields and zero normal magnetic fields at the symmetry plane.
Different waveguide modes process different symmetries. When we define the symmetry for the simulation, only the waveguide modes with the same symmetry can be found at the mode sources and monitors.
For example, for a rectangular strip waveguide buried in oxide with a mode source propagating in the y direction, as shown below, we can identify two symmetry planes at x=0 and z=0. All TE modes with electric fields predominantly in the x direction have symmetry (0,0,1), while all TM modes have symmetry (0,0,-1). Furthermore, even TE modes (TE0, TE2, TE4, …) have symmetry (-1,0,1) while odd TE modes (TE1, TE3, TE5, …) have symmetry (1,0,1). Even TM modes (TM0, TM2, TM4, ..) have symmetry (1,0,-1) while odd TM modes (TM1, TM3, TM5, ..) have symmetry (-1,0,-1). Therefore, by using certain symmetry, we can selectively filter out waveguide modes. Note that when the cladding and substrate materials are different or the waveguide has a nonzero sidewall angle, certain symmetry will be broken.
For a more extensive discussion on waveguide mode filtering using symmetry, please visit the dedicated tutorial.
At the moment, Tidy3D does not support continuous and discrete rotational symmetries. Only mirror symmetries are supported. For more information on using symmetry to significantly reduce simulation time and cost, please refer to the tutorial Defining and using symmetries tutorial.
You can use a Box object to define the plane where you want to solve the modes. In this example, we use a plane perpendicular to the waveguide propagation axis. Symmetries are applied if they are defined in the simulation and the mode plane center sits on the simulation center. Then, use the tidy3d.ModeSpec object to specify the number of modes (num_modes
), the initial effective index guess (target_neff
), polarization, and other characteristics of the modes you are looking for. Make group_index_step=True
to enable mode group index calculation.
Now you can create and execute the mode solver, which returns the results in a ModeSolverData object. For more details on how to set up, run, and visualize the solver results, please refer to this notebook.
If you prefer, you can run the local version of mode solver through the code mode_data=mode_solver.solve()
. This means that the solver will run on your own computer and will not require any credits. However, it's important to note that the local version will not include the group index calculation or subpixel smoothing, even if these options are specified in the simulation. As a result, the local version's results will not perfectly match the server-side ones.
num_modes
modes closest to the given target_neff
. If filter_pol==None
, they are simply sorted in order of decreasing effective index. If a polarization filter is selected, the modes are rearranged such that the first n_pol
modes in the list are the ones with the selected polarization fraction larger than or equal to 0.5, while the next num_modes - n_pol
modes are the ones where it is smaller than 0.5 (i.e. the opposite polarization fraction is larger than 0.5). Within each polarization subset, the modes are still ordered by decreasing effective index. In the example below, the TE modes are returned first in the mode list.num_modes
modes closest to the given target_neff
. If filter_pol==None
, they are simply sorted in order of decreasing effective index. If a polarization filter is selected, the modes are rearranged such that the first n_pol
modes in the list are the ones with the selected polarization fraction larger than or equal to 0.5, while the next num_modes - n_pol
modes are the ones where it is smaller than 0.5 (i.e. the opposite polarization fraction is larger than 0.5). The modes are still ordered within each polarization subset by decreasing the effective index..solve()
method. For example:To build the mode solver you need to create a simulation object and a plane where you want to calculate the modes, as well as specify the mode characteristics and frequencies of interest. The results are returned in a ModeSolverData object. For more details on how to set up, run, and visualize the solver results, please refer to this notebook.
When using the local version, the solver will run on your own computer and will not require any credits. You can run the local mode solver version using:
In both cases, the results are returned in a ModeSolverData object. For more details on how to set up, run and visualize the solver results, please refer to this notebook.
to_dataframe()
. We have considered a 500 x 220 nm silicon-on-insulator (SOI) waveguide operating at 1.55 $\mu$m.For more details on how to set up, run, and visualize the solver results, please refer to this notebook.
For more details on how to set up, run, and visualize the solver results, please refer to this notebook.
For more details on how to set up, run, and visualize the solver results, please refer to this notebook.
For more details on how to set up, run, and visualize the solver results, please refer to this notebook.
For more details on how to set up, run, and visualize the solver results, please refer to this notebook.
For more details on how to set up, run, and visualize the solver results, please refer to this notebook.
For more details on how to set up, run, and visualize the solver results, please refer to this notebook.
The TE and TM polarization fractions are defined as the field intensity along the first or the second of the two tangential axes. More precisely, if
E1
andE2
are the electric field components along the two tangential axes, the TE fraction is defined asintegrate(E1.abs**2) / integrate(E1.abs**2 + E2.abs**2)
, and theTM
fraction is equal to one minus the TE fraction. The tangential axes are defined by popping the normal axis from the list ofx, y, z
, so e.g.x
andz
for propagation in they
direction.
For more details on how to set up, run and visualize the solver results, please refer to this notebook.
The TE and TM polarization fraction using the waveguide definition. If
E1
andE2
are the electric field components along the two tangential axes andEn
is the component along the propagation direction, the TE fraction is defined as1 - integrate(En.abs**2) / integrate(E1.abs**2 + E2.abs**2 + En.abs**2)
, and theTM
fraction is defined as1 - integrate(Hn.abs**2) / integrate(H1.abs**2 + H2.abs**2 + Hn.abs**2)
, withH
denoting the magnetic field components.
For more details on how to set up, run and visualize the solver results, please refer to this notebook.
A FieldMonitor object records electromagnetic fields in the frequency domain. You can define a FieldMonitor object by
For details, please refer to the API reference.
Apodization applies a windowing function to the Fourier transform of the time-domain fields into frequency-domain ones. It can be used to truncate the beginning and/or end of the time signal, for example, to eliminate the source pulse when studying the eigenmodes of a system. Note that apodization affects the normalization of the frequency-domain fields.
To apply anodization, we first need to define an ApodizationSpec
object and then add it to the monitor. For example,
matplotlib
's FuncAnimation to create the animation.phase
parameter in the plot_field() function.apply_phase()
function to change the phase of fields obtained from frequency-domain monitors. For example:A FieldTimeMonitor object records electromagnetic fields in the time domain. You can define a FieldTimeMonitor object by
For details, please refer to the API reference.
Animations are often created from FDTD simulations to provide a more intuitive understanding of the physical phenomena being modeled. These animations can visualize the evolution of the field distribution over time, showing wave propagation, interactions, and other dynamic effects that static images cannot adequately depict.
To create a time-domain field animation, we need to capture the frames at different time instances of the simulation. This can be done by using a FieldTimeMonitor. Usually, an FDTD simulation contains a large number of time steps and grid points. Recording the field at every time step and grid point will result in a large dataset. For the purpose of making animations, this is usually unnecessary. In Tidy3D, we provide both spatial and temporal downsampling options to greatly reduce the animation data size.
Please refer to the detailed tutorial on FDTD animation creation.
Once a simulation with a FieldTimeMonitor is complete, the time-domain field at a specific time instance can be plotted by selecting the specific time instance and using the plot method from the data array. One example is
This line of code plots the z-component of the magnetic field at a time instance closest to 0.1 ps. Most likely, we will need to use the nearest interpolation here since the exact time instance might not be included in the result due to the finite time stepping. However, the difference is usually insignificant, so it is a very good approximation.
A FluxMonitor object records power flux in the frequency domain. If the monitor geometry is a 2D box, the total flux through this plane is returned, with a positive sign corresponding to power flow in the positive direction along the axis normal to the plane. If the geometry is a 3D box, the total power coming out of the box is returned by integrating the flux over all box surfaces (except the ones defined in exclude_surfaces
). You can define a FluxMonitor object by
For details, please refer to the API reference.
The Poynting vector quantifies the directional energy flux of an electromagnetic field, representing the rate of energy transfer per unit area per unit time and thus characterizing the power flow within the field.
The Poynting vector at a specific plane can be calculated by first placing a FieldMonitor object at that plane to obtain the fields. Then the Poynting vector can be calculated as $\boldsymbol{S} = \boldsymbol{E} \times \boldsymbol{H}$
For example, the z-component of the Poynting vector is calculated from $E_x$, $E_y$,$H_x$, and $H_y$ as $S_z = (E_x H_y^* - E_y H_x^*)$. For time-averaged Poynting vector, we need to multiply by a factor of 0.5.
Since the field data in Tidy3D are natively xarray.DataArray
objects, the most convenient way to perform integration is by using the integrate
method in xarray
. For example, if we want to integrate the Poynting vector on a surface parallel to the xy plane, one needs to compute the z-component of the time-averaged Poynting vector $Sz$ and then
This effectively achieves the same as putting a FluxMonitor object at the same plane and extracting the flux result from the monitor data.
A flux box can be defined by creating a FluxMonitor object with a 3D geometry. The total power coming out of the box is returned by integrating the flux over all box surfaces (except the ones defined in exclude_surfaces
).
For details, please refer to the API reference.
A FluxTimeMonitor object records power flux in the time domain. If the monitor geometry is a 2D box, the total flux through this plane is returned, with a positive sign corresponding to power flow in the positive direction along the axis normal to the plane. If the geometry is a 3D box, the total power coming out of the box is returned by integrating the flux over all box surfaces (except the ones defined in exclude_surfaces
). You can define a FluxTimeMonitor object by
For details, please refer to the API reference.
A ModeMonitor object records complex amplitudes from the modal decomposition of fields on a plane. The amplitudes are defined as mode_solver_data.dot(recorded_field) / mode_solver_data.dot(mode_solver_data)
, where recorded_field
is the field data recorded in the FDTD simulation at the monitor frequencies, and mode_solver_data
is the mode data from the mode solver at the monitor plane. This gives the power amplitude of recorded_field
carried by each mode. You can define a ModeMonitor object by
For details, please refer to the API reference.
The coupling efficiency of a specific waveguide mode can be calculated from the mode monitor data by first extracting the complex mode amplitude and then taking the square modulus.
As an example, you can reference the waveguide Y junction case study.
Very often we want to calculate the overlap integral of two modes to compute the coupling efficiency. This can be done conveniently using the outer_dot method such as
where waveguide_mode_data_1
and waveguide_mode_data_2
are ModeSolverData objects from performing the mode solving.
For advanced monitor data manipulation such as integration, please refer to the tutorial.
A ModeSolverMonitor object stores the mode field profiles returned by the mode solver in the monitor plane. You can define a ModeSolverMonitor object by
For details, please refer to the API reference.
The PermittivityMonitor
records the diagonal components of the complex-valued relative permittivity tensor in the frequency domain. You can define a PermittivityMonitor object by
For details, please refer to the API reference.
A FieldProjectionCartesianMonitor object samples electromagnetic near fields in the frequency domain and projects them on a Cartesian observation plane. The center
and size
fields defines where the monitor will be placed in order to record near fields, typically very close to the structure of interest. The near fields are then projected to far-field locations defined by x
, y
, and proj_distance
, relative to the custom_origin
. Here, x
and y
correspond to a local coordinate system where the local z axis is defined by proj_axis
: which is the axis normal to this monitor. If the distance between the near and far field locations is much larger than the size of the device, one can typically set far_field_approx
to True
, which will make use of the far-field approximation to speed up calculations. If the projection distance is comparable to the size of the device, we recommend setting far_field_approx
to False
, so that the approximations are not used, and the projection is accurate even just a few wavelengths away from the near field locations. For applications where the monitor is an open surface rather than a box that encloses the device, it is advisable to pick the size of the monitor such that the recorded near fields decay to negligible values near the edges of the monitor. You can define a FieldProjectionCartesianMonitor by
For details, please refer to the API reference.
A FieldProjectionAngleMonitor object samples electromagnetic near fields in the frequency domain and projects them at given observation angles. The center
and size
fields defines where the monitor will be placed in order to record near fields, typically very close to the structure of interest. The near fields are then projected to far-field locations defined by phi
, theta
, and proj_distance
, relative to the custom_origin
. If the distance between the near and far field locations is much larger than the size of the device, one can typically set far_field_approx
to True
, which will make use of the far-field approximation to speed up calculations. If the projection distance is comparable to the size of the device, we recommend setting far_field_approx
to False
, so that the approximations are not used, and the projection is accurate even just a few wavelengths away from the near field locations. For applications where the monitor is an open surface rather than a box that encloses the device, it is advisable to pick the size of the monitor such that the recorded near fields decay to negligible values near the edges of the monitor. You can define a FieldProjectionAngleMonitor object by
For details, please refer to the API reference.
A FieldProjectionKSpaceMonitor object samples electromagnetic near fields in the frequency domain and projects them on an observation plane defined in k-space. The center
and size
fields defines where the monitor will be placed in order to record near fields, typically very close to the structure of interest. The near fields are then projected to far-field locations defined in k-space by ux
, uy
, and proj_distance
, relative to the custom_origin
. Here, ux
and uy
are associated with a local coordinate system where the local ‘z’ axis is defined by proj_axis
: which is the axis normal to this monitor. If the distance between the near and far field locations is much larger than the size of the device, one can typically set far_field_approx
to True
, which will make use of the far-field approximation to speed up calculations. If the projection distance is comparable to the size of the device, we recommend setting far_field_approx
to False
, so that the approximations are not used, and the projection is accurate even just a few wavelengths away from the near field locations. For applications where the monitor is an open surface rather than a box that encloses the device, it is advisable to pick the size of the monitor such that the recorded near fields decay to negligible values near the edges of the monitor. You can define a FieldProjectionKSpaceMonitor object by
For details, please refer to the API reference.
A DiffractionMonitor object uses a 2D Fourier transform to compute the diffraction amplitudes and efficiency for allowed diffraction orders. You can define a DiffractionMonitor object by
For details, please refer to the API reference.
obj.plot(x=0)
will plot the object on the x=0
plane. Note that y
and z
are alternatively accepted to specify other planar axes. Include the ax
argument to plot to an existing axis, ie. obj.plot(y=0, ax=ax)
.plot()
, for example obj.plot(x=0, edgecolor='blue', fill=False)
. These keyword arguments correspond to those fed to Matplotlib Patches.Axes
, which can be manipulated, for example ax = obj.plot(x=0); ax.set_title('my_title')
.You can access the data of a specific monitor by its name. For instance, supposing you have a field monitor and set its name to “field”, you can refer to this name after running the simulation to get the monitor’s data.
To interpolate the electromagnetic fields to the Yee cell centers, you can use the method at_centers(monitor_name)
. For example:
By default, the electromagnetic fields are colocated to Yee grid boundaries. If you want to colocate data into custom coordinates, set
colocate=False
in field monitors to use the raw data on the Yee grid and avoid double interpolation.
For more details on visualizing and postprocessing simulation data, see this notebook.
To get the data of a particular monitor, you can use its name. For example, if you have a field monitor and have given it the name “field”, you can refer to this name to retrieve the monitor’s data after the simulation is run.