Simulation holds one model: the domain and mesh, boundaries, materials,
geometry, sources, ports and observables. You build it with add_* calls,
check it with preflight(), and solve it with run() or, for gradients,
forward(). This page lists the constructor and those entry points. The
mental model behind them is in Core Concepts.
Constructor
Simulation(freq_max, domain, *, boundary="cpml", cpml_layers=16, cpml_kappa_max=1.0, dx=None, mode="3d", dz_profile=None, dx_profile=None, dy_profile=None, dt=None, dt_min_cell=None, precision="float32", solver="yee", adi_cfl_factor=2.0, stencil_order=2, interface_eps="sampled")import numpy as npfrom rfx import Box, GaussianPulse, Simulation
sim = Simulation( freq_max=5e9, # highest frequency of interest (Hz) domain=(0.04, 0.03, 0.03), # x, y, z size (m) dx=2e-3, # cubic cell size (m) boundary="cpml", cpml_layers=6,)| Argument | Meaning |
|---|---|
freq_max | Highest frequency you care about, in Hz. It sets the default time step, the default dx and the mesh checks in preflight. |
domain | (Lx, Ly, Lz) in metres. On a graded axis, pass 0 for that axis and the size comes from the profile’s sum. |
dx | Cell size in metres. If omitted, rfx derives it from freq_max. |
boundary | "cpml" (default), "pec", "upml", or a BoundarySpec for per-face control, including "pmc" and "periodic". See Core Concepts. |
cpml_layers | Absorber thickness in cells (default 16). rfx adds the absorber outside domain on each CPML face, so the whole domain is interior. Keep sources and structures a few cells from its edge. |
dx_profile, dy_profile, dz_profile | Cell sizes along an axis, for a graded mesh. See Non-Uniform Mesh. |
mode | "3d" (default), "2d_tmz" (Ez, Hx, Hy) or "2d_tez" (Hz, Ex, Ey). |
precision | "float32" (default), "float64" (enable JAX x64 first) or "mixed" (float16 fields). The last two run on the uniform single-device solver only. |
solver | "yee" (default) or the experimental "adi". See ADI Solver. |
stencil_order | 2 (default) or 4. Order 4 only runs on a uniform, PEC or periodic, non-dispersive model. |
dt, dt_min_cell | Pin the time step on a graded mesh (for mesh-as-design-variable work). Refused on a uniform mesh. |
Put PEC faces in a BoundarySpec. Neither
is a finite ground plane: for that, draw a sheet with add_thin_conductor().
Building the model
Every add_* method returns the simulation, so calls can be chained.
| Method | Purpose | Reference |
|---|---|---|
add_material(name, *, eps_r, sigma, mu_r, ...) | register a named material | Geometry & Materials |
add(shape, *, material) | fill a shape with a material | Geometry & Materials |
add_thin_conductor(shape, ...) | a zero-thickness metal sheet | Geometry & Materials |
add_source, add_polarized_source, add_tfsf_source | field excitation, no impedance | Sources & Ports |
add_port, add_msl_port, add_waveguide_port, add_coaxial_port | ports for S-parameters | Sources & Ports |
add_lumped_rlc | an R/L/C element in one cell | Sources & Ports |
add_probe, add_vector_probe, add_dft_plane_probe, add_flux_monitor, add_ntff_box | observables | Results & Observables |
add_refinement(z_range, *, ratio=4, ...) | a finer z slab (subgridding) | experimental; only run() on a uniform mesh accepts it |
sim.add_material("slab", eps_r=3.0, sigma=0.01)sim.add(Box((0.018, 0.0, 0.0), (0.024, 0.03, 0.03)), material="slab")sim.add_source((0.010, 0.015, 0.015), "ez", waveform=GaussianPulse(f0=3e9, bandwidth=0.8), amplitude_kind="current")sim.add_probe((0.030, 0.015, 0.015), "ez")Checking before you run
| Method | Returns | Use |
|---|---|---|
preflight(*, strict=False, check_ntff=True, check_resolution=True, ...) | PreflightReport | Setup errors and warnings. run() calls it for you. |
preflight_sparameters(*, calculator="run", strict=False, ...) | PreflightReport | Is this port family valid for the S-parameter calculator you plan to call? See Sources & Ports. |
fidelity_report(print_report=True) | report | Where each shape actually landed on the grid, next to what you drew. |
freeze_mesh() | Grid or NonUniformGrid | Fix the mesh now, so you can read grid indices before placing more geometry. |
plan_mesh(*, n_steps=None, available_memory_gb=None, sparameter_calculator=None, ...) | MeshPlan | A serializable summary of the mesh, memory estimate and checks. See Automation. |
mesh_intelligence_report(...) | MeshIntelligenceReport | Grid, preflight issues and AD memory in one report. |
report = sim.preflight()print(report.ok, len(report.errors), len(report.warnings))report.raise_for_failure() # raises only on errorssim.fidelity_report()PreflightReport is a list of coded issues. Read .ok, .errors,
.warnings, .infos or .by_code(code). Don’t test it with if report:: that
raises TypeError, because an advisory-only report is non-empty but fine.
strict=True raises on errors instead of returning them.
run()
run() is the general entry point. It returns a Result; see
Results & Observables.
run(*, n_steps=None, num_periods=20.0, until_decay=None, decay_check_interval=50, decay_min_steps=100, decay_max_steps=50000, decay_energy_consecutive=2, ..., checkpoint=False, compute_s_params=None, s_param_freqs=None, s_param_n_steps=None, snapshot=None, subpixel_smoothing=False, conformal_pec=None, conformal_min_weight=0.1, devices=None, exchange_interval=1, skip_preflight=False, report_every=None, report_label="", ringdown=None) -> Resultresult = sim.run(n_steps=300)print(result.time_series.shape, f"settling {result.settling_db:.1f} dB")Run length. Give exactly one of:
| Argument | Meaning |
|---|---|
n_steps=N | exactly N steps |
num_periods=P | P periods at freq_max (default 20, used when nothing else is given) |
until_decay=1e-3 | step until the interior field energy falls to that fraction of its peak, up to decay_max_steps |
until_decay works on absorbing boundaries, on uniform and graded meshes. It
watches the total energy in the interior, so it is a sound stop for spectra,
flux and S-parameters. In a closed PEC box the energy does not decay. There
the uniform solver falls back to a single field sample, which is reliable only
for lossy or radiating ring-down, and a graded mesh runs the fixed n_steps
with a warning. Use a fixed n_steps in closed structures and in convergence
studies.
Other arguments you will use:
| Argument | Meaning |
|---|---|
compute_s_params=True, s_param_freqs= | S-parameters for lumped and wire ports (add_port) only. Other port families have their own calculator. |
snapshot=SnapshotSpec(interval=10, components=("ez",), slice_axis=None, slice_index=None) | Record field snapshots. See Visualization and Analysis. |
report_every=N | Print progress (steps, rate, ETA) every N steps. Does not change the result. |
subpixel_smoothing=True | Smoothed permittivity at dielectric interfaces. |
conformal_pec=True | Dey–Mittra conformal PEC for curved metal. |
ringdown=RingdownSpec(...) | Complete a wire port’s S-parameters when the record was cut while the structure still rings. See below. |
devices=[...] | Run across several devices (x-slab decomposition). exchange_interval must stay 1. |
skip_preflight=True | Skip the automatic preflight. Use only after you have read it once. |
A run argument that the selected solver path does not implement raises
before the first step, for example snapshots on a graded mesh or until_decay
with the ADI solver. Nothing is silently dropped.
Warnings after the run. run() warns when a probe recorded no field,
when a Gaussian-pulse probe record had not rung down (end of record above
−40 dB of peak), and when time_series or lumped/wire s_params contain
NaN/Inf or an |S| above 1.1. These checks cover only those arrays. For
DFT planes, flux and NTFF data, check np.isfinite(...) yourself. No warning
does not mean the run converged.
Ring-down completion
from rfx.ringdown import RingdownSpec, then
run(..., ringdown=RingdownSpec()). For a wire-port run (add_port(..., extent=...)) whose record ended too early, rfx fits the slowly decaying poles
in the last part of the record and adds the missing tail of each spectrum in
closed form. The completed matrix is result.ringdown.s_params, on the same
frequencies as result.s_params, and result.ringdown.report holds the fitted
poles and the checks. report.ok is decided by the error witness (completing
from [T/4, T] versus [T/2, T]). The two-window difference W2 is reported,
not judged. None of the witnesses is an error bound. The source must be off in
the fit window. It covers wire ports (one port on a uniform mesh, or a graded
mesh) with a fixed n_steps. Other ports, TFSF, until_decay and devices=
are refused with the reason. s_params is None when the report says no
completion was produced.
forward(): the differentiable path
forward() runs the same physics with only the outputs a loss function needs,
so it can sit inside jax.grad, jax.value_and_grad or jax.jit. It
returns a ForwardResult with time_series, s_params/freqs (lumped/wire
S11 vectors when port_s11_freqs= is set), ntff_data, dft_planes, grid
and dt.
forward(*, eps_override=None, sigma_override=None, mu_r_override=None, design_box=None, design_eps_override=None, design_sigma_override=None, design_occupancy_override=None, pec_mask_override=None, pec_occupancy_override=None, n_steps=None, num_periods=20.0, checkpoint=True, checkpoint_segments=None, emit_time_series=True, checkpoint_every=None, n_warmup=0, skip_preflight=False, distributed=False, devices=None, exchange_interval=1, port_s11_freqs=None, rlc_values_override=None) -> ForwardResultimport jaximport jax.numpy as jnp
grid = sim.freeze_mesh()i0 = grid.position_to_index((0.018, 0.0, 0.0))[0]i1 = grid.position_to_index((0.024, 0.0, 0.0))[0]
def loss(eps_slab): eps = jnp.ones(grid.shape).at[i0:i1].set(eps_slab) out = sim.forward(eps_override=eps, n_steps=300, skip_preflight=True) return jnp.sum(out.time_series[:, 0] ** 2)
print(jax.grad(loss)(3.0))| Input | What it differentiates |
|---|---|
eps_override, sigma_override, mu_r_override | whole-grid material arrays, shape grid.shape; they replace the registered materials everywhere |
design_box= with design_eps_override= / design_sigma_override= / design_occupancy_override= | a box-sized design region; far less reverse-mode memory than a whole-grid override (pass checkpoint=False with it) |
pec_occupancy_override | relaxed metal occupancy in [0, 1] |
port_s11_freqs= | lumped/wire S11 vectors, uniform single-device only |
rlc_values_override={index: {"R": ..., "L": ..., "C": ...}} | values of registered add_lumped_rlc elements |
Per-cell versus per-edge conductivity in a design box. A single
design_sigma_override array is per cell. rfx averages the four cells
around each E edge, and that average also reaches the edge layer on the box’s
plus faces. A 3-tuple (sigma_x, sigma_y, sigma_z) is per edge at the box
indices and is written as-is. If a per-edge design needs that plus-face layer,
declare the box one cell larger on that side.
Keep anything that changes array shapes, the geometry list or the solver path
outside the function you differentiate. A combination the differentiable path
does not support raises instead of returning a zero gradient. A model with
add_refinement is refused by forward(). Guides:
Autodiff and Adjoint,
Inverse Design,
Memory Reduction.
S-parameter calculators
Each port family has its own calculator method on Simulation:
compute_msl_s_matrix(), compute_waveguide_s_matrix(),
compute_coaxial_line_reflection() and compute_coaxial_two_port(). Lumped
and wire ports use run(compute_s_params=True). compute_s_matrix(lane=None, **kwargs) picks the right method from the registered ports and forwards your
arguments. s_matrix_lane() tells you which one it would pick without running
anything. Details: Sources & Ports.
Simulation.auto()
Simulation.auto(freq_range, *, accuracy="standard", **kwargs) -> Simulationauto_sim = Simulation.auto(freq_range=(1.5e9, 3.5e9), accuracy="draft")This derives dx, the domain and the CPML thickness from the frequency range
alone. accuracy is "draft", "standard" or "high". Any keyword argument
overrides the derived value. It warns on purpose: the domain was sized without
your geometry, so check it after you add shapes. To derive a mesh from your
geometry instead, use auto_configure or plan_mesh on the
Automation page.
Other methods
These are documented in the generated reference:
estimate_ad_memory, plan_ad_memory, ad_memory_preflight and
explain_ad_memory (reverse-mode memory, see
Memory Reduction); export_scene,
export_artifact_bundle and artifact_report (saving a model and its
results); conductor_mask.
Limits
- A graded mesh accepts fewer features than a uniform one. Snapshots, lumped port S-parameters, coaxial ports, UPML and Floquet ports raise there. See Support Boundaries.
add_refinementworks only throughrun()on a uniform mesh.forward(),optimize()andtopology_optimize()refuse a refined model.precision="mixed"raises the CPML reflection floor. Don’t use it for S-parameters or low-reflection work.- A completed run is not a converged run. Check
result.settling_dband do a convergence study before you quote a number.
