Microwave & RF 📡#
Overview#
Warning
RF simulations will be subject to new license requirements in the future.
Warning
Breaking changes were introduced in v2.10.0, please see the migration guide for help migrating your code.
This page consolidates Tidy3D features related to microwave and RF simulation. While microwave/RF and optical simulations have many properties in common, there are some differences in the typical RF user workflow that deserve special consideration.
The following sections discuss:
TerminalComponentModeler: The core simulation object in microwave/RF models
RF Materials Models: Typical material types in microwave/RF simulation
RF Materials Library: The RF material library contains various dispersive models for real-world RF materials.
Layer-based Grid Refinement: Automated grid refinement strategy for planar structures (e.g. printed circuit boards)
Lumped Port & Elements: Lumped excitations and circuit elements
Wave Port: Port excitation based on modal fields
Radiation & Scattering: Useful features for antenna and scattering problems
See also
If you are completely new to Tidy3D, we recommend first checking out the following beginner resources:
TerminalComponentModeler#
Tool for modeling two-terminal multiport devices and computing port parameters with lumped and wave ports. |
|
Data associated with a |
|
Stores the computed S-matrix and reference impedances for the terminal ports. |
|
Port parameter matrix elements for terminal-based ports. |
|
Array of values over dimensions of frequency and port name. |
The TerminalComponentModeler is the core simulation object for 3D RF/microwave simulations in Tidy3D. Its primary function is to simulate the system over N number of ports and M number of frequency points, with the end result being a MxNxN S-parameter matrix.
my_tcm = TerminalComponentModeler(
simulation=base_sim,
ports=[port1, port2],
freqs=my_frequencies,
...
)
The key parts of a TerminalComponentModeler are:
The
Simulationfield defines the underlying Tidy3D Simulation object. This baseSimulationobject contains information about the simulation domain such as structures, boundary conditions, grid specifications, and monitors. Note that sources should not be included in the base simulation, but rather in theportsfield instead.The
portsfield defines the list of source excitations. These are commonly of typeLumpedPortorWavePort. The number of ports determines the number of batch jobs in theTerminalComponentModelerand the dimensionality of the S-parameter matrix.The
freqsfield defines the list of frequency points for the simulation.
More information and explanation for additional fields can be found in the documentation page for the TerminalComponentModeler. In order to submit the simulation, use tidy3d.web.upload(), tidy3d.web.start(), tidy3d.web.monitor(), and tidy3d.web.load().
# Upload simulation and get cost estimate
my_task_id = tidy3d.web.upload(my_tcm, task_name='my_task_name')
# Run simulation
tidy3d.web.start(my_task_id)
# Monitor simulation
tidy3d.web.monitor(my_task_id)
# Load results after completion
my_tcm_data = tidy3d.web.load(my_task_id)
Alternatively, use the tidy3d.web.run() method to perform all of the above in one single step.
# Upload, run simulation, and download data
my_tcm_data = tidy3d.web.run(my_tcm, task_name='my_task_name', path='my/local/download/path')
To get the S-matrix from the results, use the smatrix() method of the TerminalComponentModelerData object.
# Get S-matrix from results
my_s_matrix = my_tcm_data.smatrix()
The S-matrix is stored as a MicrowaveSMatrixData whose data property contains a TerminalPortDataArray instance. To obtain a specific S_ij value, use the port_in and port_out coordinates with the corresponding port name. To obtain a specific frequency, use the f coordinate.
# Get return loss
my_S11 = my_s_matrix.data.sel(port_in="my_port_1", port_out="my_port_1")
Note
At this moment, Tidy3D uses the physics phase convention \(e^{-i\omega t}\). Other RF simulation software and texts may use the electrical engineering convention \(e^{i\omega t}\). This affects the calculated S-parameters and impedance values. To convert between the two, simply use the complex conjugation operation, e.g. np.conjugate().
To access simulation data for a given port excitation, use the data attribute of the TerminalComponentModelerData.
# Get simulation data for a given port "my_port"
sim_data = my_tcm_data.data["my_port"]
# Get monitor data from a given monitor "my_monitor"
my_monitor_data = sim_data["my_monitor"]
The data attribute holds the simulation data in a dictionary with the respective port name as keys. The data for each monitor can then be accessed from the simulation data object using the monitor name as the dictionary key.
See also
To learn more about the web API workflow in Tidy3D, please refer to the following pages:
To learn more about data post-processing and visualization, please refer to the following pages:
Please refer to the following example models to see the TerminalComponentModeler in action:
RF Materials Models#
Perfect electrical conductor class. |
|
Perfect magnetic conductor class. |
|
Lossy metal that can be modeled with a surface impedance boundary condition (SIBC). |
|
Advanced parameters for fitting surface impedance of a |
|
Modified Hammerstad surface roughness model. |
|
Huray surface roughness model. |
The PECMedium and LossyMetalMedium classes can be used to model metallic materials.
# lossless metal
my_pec = PECMedium()
# lossy metal (conductivity in S/um)
my_lossy_metal = LossyMetalMedium(conductivity=58, freq_range=(1e9, 10e9))
Note that the unit of conductivity is S/um and the unit of freq_range is Hz. The LossyMetalMedium class implements the surface impedance boundary condition (SIBC). It can accept surface roughness specifications using the Hammerstad or Huray models. Please refer to their respective documentation pages for details. Edge singularity correction is also available but turned off by default at this time.
Note
When modeling lossy metals, always be sure to check the skin depth — if the skin depth is significant compared to the geometry size, then LossyMetalMedium may be not accurate. In that case, use a regular dispersive medium instead.
Dispersionless medium. |
|
Tool for fitting refractive index data to get a dispersive medium described by |
To model lossless dielectrics, use the regular Medium.
# lossless dielectric
my_lossless_dielectric = Medium(permittivity=2.2)
To model a lossy dielectric with constant loss tangent, use the constant_loss_tangent_model() method of the tidy3d.plugins.dispersion.FastDispersionFitter utility class.
# lossy dielectric (constant loss tangent)
my_lossy_dielectric = FastDispersionFitter.constant_loss_tangent_model(
eps_real=4.4,
loss_tangent=0.002,
frequency_range=(1e9, 5e9)
)
More advanced material models, including frequency dependence and anisotropy, are also available in Tidy3D.
See also
For a more comprehensive discussion of the different EM mediums available in Tidy3D, please refer to the EM Mediums page:
RF Materials Library#
The RF material library is a dictionary containing various dispersive models for real-world RF materials. To use the materials in the library, import it first by:
>>> from tidy3d.plugins.microwave import rf_material_library
The key of the dictionary is the abbreviated material name.
Note: some materials have multiple variant models, in which case the second key is the “variant” name.
To import a material “mat” of variant “var”:
>>> medium = rf_material_library['mat']['var']
For example, Rogers3010 laminate can be loaded as:
>>> Rogers3010 = rf_material_library['RO3010']['design']
You can also import the default variant of a material by:
>>> medium = rf_material_library['mat'].medium
It is often useful to see the full list of variants for a given medium:
>>> print(rf_material_library['mat'].variants.keys())
To access the details of a variant, including material model and references, use the following command:
>>> rf_material_library['mat'].variants['var']
ArlonAD255C (“AD255C”)#
Variant |
Valid for |
Model Info |
Reference |
|---|---|---|---|
|
1.0 - 30.0 GHz |
5-pole, lossy |
[1] |
|
1.0 - 30.0 GHz |
5-pole, lossy |
[1] |
Examples:
>>> medium = material_library['AD255C']['design']
>>> medium = material_library['AD255C']['process']
References:
AD255C High Performance Polyimide Laminates [url]
FR4 (“FR4”)#
Variant |
Valid for |
Model Info |
Reference |
|---|---|---|---|
|
1.0 - 3.0 GHz |
5-pole, lossy |
[1] |
|
1.0 - 3.0 GHz |
5-pole, lossy |
[2] |
Examples:
>>> medium = material_library['FR4']['lowloss']
>>> medium = material_library['FR4']['standard']
References:
Rogers3003 (“RO3003”)#
Variant |
Valid for |
Model Info |
Reference |
|---|---|---|---|
|
1.0 - 30.0 GHz |
5-pole, lossy |
[1] |
|
1.0 - 30.0 GHz |
5-pole, lossy |
[1] |
Examples:
>>> medium = material_library['RO3003']['design']
>>> medium = material_library['RO3003']['process']
References:
RO3003™ Laminates [url]
Rogers3010 (“RO3010”)#
Variant |
Valid for |
Model Info |
Reference |
|---|---|---|---|
|
1.0 - 30.0 GHz |
5-pole, lossy |
[1] |
|
1.0 - 30.0 GHz |
5-pole, lossy |
[1] |
Examples:
>>> medium = material_library['RO3010']['design']
>>> medium = material_library['RO3010']['process']
References:
RO3010™ Laminates [url]
Rogers4003C (“RO4003C”)#
Variant |
Valid for |
Model Info |
Reference |
|---|---|---|---|
|
8.0 - 40.0 GHz |
5-pole, lossy |
[1] |
|
8.0 - 40.0 GHz |
5-pole, lossy |
[1] |
Examples:
>>> medium = material_library['RO4003C']['design']
>>> medium = material_library['RO4003C']['process']
References:
RO4003C™ Laminates [url]
Rogers4350B (“RO4350B”)#
Variant |
Valid for |
Model Info |
Reference |
|---|---|---|---|
|
8.0 - 40.0 GHz |
5-pole, lossy |
[1] |
|
8.0 - 40.0 GHz |
5-pole, lossy |
[1] |
Examples:
>>> medium = material_library['RO4350B']['design']
>>> medium = material_library['RO4350B']['process']
References:
RO4350B™ Laminates [url]
Layer-based Grid Refinement#
Specification for automatic mesh refinement and snapping in layered structures. |
|
Specification for corner detection on a 2D plane. |
|
Specification for local mesh refinement that defines the grid step size and the number of grid cells in the refinement region. |
The LayerRefinementSpec class allows the user to specify automated refinement within a layered region, for instance, the metallic trace plane of a printed circuit board. The grid will be automatically refined near any metallic corners and edges in that layer.
import tidy3d as td
# Define layer refinement spec
my_layer_refinement_spec = td.LayerRefinementSpec(
axis=2, # layer normal axis
center=(0, 0, 0), # layer center
size=(3, 2, 0.1), # layer size
min_steps_along_axis=2, # minimum number of grid points along normal axis
corner_refinement=td.GridRefinement(dl=100, num_cells=2) # metal corner refinement specification
)
# Add layer refinement spec to overall grid specification
my_grid_spec = td.GridSpec(
...,
layer_refinement_specs = [my_layer_refinement_spec]
)
More than one LayerRefinementSpec is permitted. In addition to manually defining the center and size of the LayerRefinementSpec, one can alternatively use the from_bounds(), from_layer_bounds(), or from_structures() convenience methods.
my_layer_refinement_spec_2 = LayerRefinementSpec.from_structures(
structures=[my_planar_structure], # position, size, and axis automatically determined based on structure
...
)
Note that different LayerRefinementSpec instances are recommended for structures on different physical layers.
See also
For more explanation and examples, please refer to the following pages:
Example applications:
Lumped Port & Elements#
Class representing a single rectangular lumped port. |
|
Class representing a single coaxial lumped port. |
The LumpedPort feature represents a planar, uniform current excitation with a fixed impedance termination.
# Define a lumped port
my_port_1 = LumpedPort(
name="My Port 1",
center=(0,0,0),
size=(0, port_width, port_height),
voltage_axis=2, # z-axis aligned excitation
impedance=50, # port impedance
)
The LumpedPort can be 1D (line) or 2D (plane). For 2D, only axis-aligned planes are supported at this time. Only real impedance values are supported at this time.
Note
Lumped ports and elements are fundamentally approximations and thus should only be used when the port/element size is much smaller than the wavelength of interest (typically lambda/10). For more accurate results, especially when the port is adjacent to an intentional waveguide or transmission line, consider using the WavePort excitation instead.
The CoaxialLumpedPort represents an analytical coaxial field source.
# Define coaxial lumped port
my_coaxial_port_1 = CoaxialLumpedPort(
name="My Coaxial Port 1",
center=(0,0,0),
inner_diameter=1000, # inner diameter in um
outer_diameter=2000, # outer diameter in um
normal_axis=0, # normal axis to port plane
direction="+", # direction of signal along normal axis
impedance=50, # port impedance
)
Note
Because the CoaxialLumpedPort injects an analytical field source, the structure connected to this port must match the physical port dimensions. Any deviation will result in signal reflection and potential inaccuracies. One common source of this issue is in imported geometries with faceted cylinders.
Class representing a rectangular lumped resistor. |
|
Class representing a coaxial lumped resistor. |
|
Lumped element representing a network consisting of resistors, capacitors, and inductors. |
|
Class for representing a simple network consisting of a resistor, capacitor, and inductor. |
|
Class for representing a network consisting of an arbitrary number of resistors, capacitors, and inductors. |
For a simple resistive lumped element, use LumpedResistor.
my_resistor = LumpedResistor(
name="My resistor",
center=(0,0,0),
size=(0, element_width, element_height),
voltage_axis=2, # z-axis aligned
resistance=50, # real-valued impedance
)
For more complicated RLC networks, use the general LinearLumpedElement class.
my_lumped_element = LinearLumpedElement(
name="My lumped element",
center=(0,0,0),
size=(0, element_width, element_height),
voltage_axis=2, # z-axis aligned
network=RLCNetwork(resistance=50, inductance=1e-9) # RLC network
)
All lumped elements should be added to the lumped_elements field of the base Simulation instance.
my_simulation = Simulation(
lumped_elements=[my_resistor, my_lumped_element],
...,
)
See also
For more in-depth discussion and examples, please see the following learning center article:
Example applications:
Wave Port#
Class representing a single wave port |
|
Stores specifications for the mode solver to find an electromagnetic mode. |
The WavePort represents a modal source port. The port mode is first calculated in the 2D mode solver, then injected into the 3D simulation. The WavePort is also automatically terminated with a modal absorbing boundary ModeABCBoundary that perfectly absorbs the outgoing mode. Any non-matching modes are subject to PEC reflection.
my_wave_port_1 = WavePort(
center=(0,0,0),
size=(port_width, port_height, 0),
name='My Wave Port 1',
direction='+', # direction of signal
mode_spec=ModeSpec(target_neff=1.5), # specification for mode solver
current_integral=my_current_integral, # current integration curve for port impedance calculation
)
Most fields are self explanatory. Some additional notes:
mode_specis used to specify the effective index search value for the mode solvercurrent_integraland/orvoltage_integralare used to specify the integration paths for port impedance calculation. If only one of the two is specified, then the port power is also used (automatically determined).
If it is desired to only solve for the 2D port mode, one can use the to_mode_solver() convenience method to generate a ModeSolver simulation object.
# Define a mode solver from the wave port
my_mode_solver = my_wave_port_1.to_mode_solver(
simulation=base_sim, # base Simulation object
freqs=my_frequencies, # frequencies for 2D mode solver
)
# Execute mode solver
my_mode_data = web.run(my_mode_solver, task_name='mode solver')
Class for computing the voltage between two points defined by an axis-aligned line. |
|
Class for computing conduction current via Ampère's circuital law on an axis-aligned loop. |
|
Class for computing the voltage between two points defined by a custom path. |
|
Class for computing conduction current via Ampère's circuital law on a custom path. |
|
Class for defining the simplest type of path integral, which is aligned with Cartesian axes. |
|
Class for defining a custom path integral defined as a curve on an axis-aligned plane. |
|
Tool for computing the characteristic impedance of a transmission line. |
The classes above are used to define the voltage/current integration paths for impedance calculation.
# Define voltage integration line
my_voltage_integral = AxisAlignedVoltageIntegral(
center=(0,0,0), # center of integration line
size=(5, 0, 0), # length of integration line
sign='+', # sign of integral
)
# Define current integration loop
my_current_integral = AxisAlignedCurrentIntegral(
center=(0,0,0), # center of integration loop
size=(20, 20, 0), # size of integration loop
sign='+', # sign of integral (should match wave port direction)
)
In addition to being used in the WavePort definition, the current/voltage integration objects can also be applied to arbitrary EM field data (2D and 3D). This is most commonly used in conjunction with the ImpedanceCalculator to calculate the line impedance of a 2D mode.
# Define impedance calculator
my_Z_calculator = ImpedanceCalculator(
voltage_integral = my_voltage_integral,
current_integral = my_current_integral,
)
# Calculate impedance of 2D mode
Z_mode = my_Z_calculator.compute_impedance(my_mode_data)
As before, only one of the two integration paths (voltage or current) are strictly necessary. This determines the convention used to calculate the impedance (PI, PV, or VI).
See also
For more information, please see the following articles:
Example applications:
Radiation & Scattering#
|
|
This class provides methods to calculate the array factor and far-field radiation patterns for rectangular phased antenna arrays. |
|
Tool for detecting and analyzing lobes in antenna radiation patterns, along with their characteristics such as direction and beamwidth. |
|
Data representing the main parameters and figures of merit for antennas. |
When modeling antennas or scattering problems, it is vital to analyze the radiated far-field. For such applications, the DirectivityMonitor should be used.
# Define angular coordinates
# Theta is the elevation angle relative to global +z axis
# Phi is the azimuthal angle relative to global +x axis
my_theta = np.linspace(0, np.pi, 91)
my_phi = np.linspace(0, 2*np.pi, 181)
# Define directivity monitor
my_directivity_monitor = DirectivityMonitor(
center=(0,0,0),
size=(100, 100, 100),
freqs=my_frequencies,
phi=my_phi,
theta=my_theta,
name='My radiation monitor',
far_field_approx=True,
)
The DirectivityMonitor should completely surround the structure of interest. The far_field_approx flag can be used to set whether the far-field approximation is used (default True).
Once the monitor is defined, it should be added to the radiation_monitors option of the TerminalComponentModeler.
# Add directivity monitor to simulation
my_tcm = TerminalComponentModeler(
...,
radiation_monitors=[my_directivity_monitor],
)
Once the simulation is completed, the get_antenna_metrics_data() method of the TerminalComponentModelerData object is used to obtain the radiation metrics.
# Get radiation metrics
my_antenna_metrics = my_tcm_data.get_antenna_metrics_data()
# Get individual metrics
my_directivity = my_antenna_metrics.directivity
my_gain = my_antenna_metrics.gain
my_radiation_efficiency = my_antenna_metrics.radiation_efficiency
my_reflection_efficiency = my_antenna_metrics.reflection_efficiency
my_realized_gain = my_antenna_metrics.realized_gain
my_supplied_power = my_antenna_metrics.supplied_power
my_radiated_power = my_antenna_metrics.radiated_power
my_radiation_intensity = my_antenna_metrics.radiation_intensity
my_axial_ratio = my_antenna_metrics.axial_ratio
my_left_pol = my_antenna_metrics.left_polarization
my_right_pol = my_antenna_metrics.right_polarization
Each metric is in the form of an xarray.DataArray object that can be used for plotting, export, and further analysis. For examples of how these datasets can be manipulated, please refer to the notebooks in the “See also” section below.
The LobeMeasurer utility class can be used to analyze radiation pattern lobes.
# Define lobe measurer
my_lobes = LobeMeasurer(
angle=phi, # Angular axis of interest
radiation_pattern=my_gain, # Radiation pattern to measure
)
# Get lobe characteristics
my_lobe_measures = my_lobes.lobe_measures
my_main_lobe = my_lobes.main_lobe
my_side_lobes = my_lobes.side_lobe
Lobe characteristics such as direction, magnitude, and -3 dB beamwidth can be obtained for the main and side lobes. Additionally, the LobeMeasurer.plot() utility function adds main lobe beam direction and width markers to polar radiation plots.
See also
For more in-depth discussion and examples, please see the following learning center article:
Example applications: