# Simulation

Channel: release; package: 1.8.0; source: 7d1e97331656dd74ad0f5471653ac0c5effdff70

Page: https://remilab.ai/rfx/versions/v1.8.0/api/simulation/
Source: https://github.com/bk-squared/rfx/blob/7d1e97331656dd74ad0f5471653ac0c5effdff70/docs/public/api/simulation.mdx

`Simulation` is the primary high-level builder in rfx.
It owns the domain, boundary conditions, geometry, sources, probes, ports, and
the time-domain run.

## Constructor

```python
from rfx import Simulation

sim = Simulation(
    freq_max=4e9,
    domain=(0.08, 0.06, 0.025),
    boundary="cpml",
    cpml_layers=16,
    dx=1e-3,
    mode="3d",
)
```

Key constructor arguments:

| Argument | Meaning | Notes |
|---|---|---|
| `freq_max` | highest frequency of interest | drives mesh and timestep selection |
| `domain` | `(x, y, z)` domain size in meters | required unless derived by automation |
| `boundary` | boundary type | `"cpml"`, `"pec"`, `"upml"`, or a per-face `BoundarySpec`; the public examples center on `cpml` |
| `cpml_layers` | absorber thickness | `Simulation` default is 16 |
| `pec_faces` | face-based PEC truncation | deprecated and scheduled for removal in rfx v2.0; encode PEC faces in a `BoundarySpec` instead. Not a substitute for finite antenna ground-plane metal |
| `dx` | base lateral cell size | may be inferred by automation |
| `dz_profile` / `dx_profile` / `dy_profile` | non-uniform cell profiles | workflow helper for thin-substrate or graded meshes |
| `solver` | solver family | public examples and documented RF calculations use Yee |

> **Ground-plane rule:** if the structure needs a finite ground plane, model it
> as geometry. Do not use PEC face truncation (`pec_faces`, or a `pec` face in a
> `BoundarySpec`) as a substitute for antenna ground-plane metal.

## Core methods

| Method | Purpose | Public note |
|---|---|---|
| `add(shape, material=...)` | fill geometry with a named material | geometry should stay explicit in public examples |
| `add_material(...)` | register a named material | use this for custom dielectric / conductive / dispersive media |
| `add_source(...)` | inject a soft point source | preferred for ringdown and Harminv work |
| `add_port(...)` | add a lumped or wire port | use for impedance-normalized S-parameters within the documented lumped/wire limits |
| `add_probe(...)` | record a point observable | feeds `Result.find_resonances()` |
| `add_ntff_box(...)` | accumulate far-field data | use only when the radiation workflow is benchmarked |
| `fidelity_report(print_report=True)` | compare declared entities with their rasterized realization | run before a solve; reports geometry/material realization, not RF error |
| `preflight(strict=False)` | validate setup before a long run | returns a coded `PreflightReport`; use `.errors`, `.warnings`, `.by_code(...)`, or `.raise_for_failure()` for automation gates |
| `mesh_intelligence_report(...)` | summarize configured grid, preflight issues, and AD memory estimates | useful for non-uniform and memory-constrained planning |
| `plan_mesh(...)` | return a serializable `MeshPlan` for this configured simulation | wraps `mesh_intelligence_report()`, `preflight()`, and optional S-parameter preflight checks |
| `run(...)` | advance the simulation | returns a `Result` object |
| `forward(...)` | run the differentiable minimal-observable path | use for `jax.grad` / outer-`jax.jit` objectives; returns a `ForwardResult`, not the full `Result` |

Source, port, and probe builders are documented in detail in
[Sources & Ports](https://remilab.ai/rfx/versions/v1.8.0/api/sources-ports/); the fields they populate are described in
[Results & Observables](https://remilab.ai/rfx/versions/v1.8.0/api/results-observables/).

## Run loop

```python
from rfx import GaussianPulse

# Add the sources and observables required by this run.
sim.add_source(
    (0.04, 0.03, 0.0125),
    "ez",
    waveform=GaussianPulse(f0=2e9, bandwidth=0.5),
    amplitude_kind="current",
)
sim.add_probe((0.05, 0.03, 0.0125), "ez")

# until_decay is implemented on the uniform CPML/UPML runner and on the
# non-uniform (dx/dy/dz-profile) runner with CPML/UPML boundaries.
sim.fidelity_report()
result = sim.run(until_decay=1e-3, report_every=250)
```

`report_every=N` is optional host-side progress reporting. It prints completed
steps, elapsed time, rate, and ETA without changing the returned arrays. It is
supported on the uniform runner and the single-device non-uniform runner when
that runner is not using incompatible checkpointing. Distributed, ADI,
subgridded, traced/differentiable, and incompatible checkpointed paths reject
or warn rather than silently pretending to report progress.

`run()` returns a tuple-like `Result` (a `NamedTuple`) with fields such as
`time_series`, `s_params`, `freqs`, `ntff_data`, and `grid` — see
[Results & Observables](https://remilab.ai/rfx/versions/v1.8.0/api/results-observables/) for the primary field list.
Non-finite output is a setup or numerical failure, not successful RF evidence.
The automatic `run()` warning, and the corresponding warning on the uniform
single-device `forward()` path, inspect only `time_series` and lumped/wire
`s_params`. Non-uniform and distributed `forward()` paths return before that
guard. Inspect every unguarded state, NTFF, DFT-plane, and flux array explicitly
with `np.isfinite(...)` before using it.

After adding a lumped or wire port, `compute_s_params=True` populates
`Result.s_params` only for that port family. It is not a universal port
dispatcher. Microstrip-line,
waveguide, and coaxial-line ports use dedicated calculators
(`compute_msl_s_matrix()`, `compute_waveguide_s_matrix()`,
`compute_coaxial_line_reflection()`). See [Sources & Ports](https://remilab.ai/rfx/versions/v1.8.0/api/sources-ports/)
for the calculator that matches each port family.

### Preflight and post-run signals

Unless `skip_preflight=True`, `run()` checks the configured geometry, sources,
ports, mesh, absorber, and selected calculation before allocating the full run.
During normal Python execution outside JAX tracing, it may also report measured
output problems without changing the returned arrays. Each warning is
conditional on the recorded data described below; the absence of a warning is
not a convergence or validation result.

The no-field and ring-down witnesses run only on the uniform runner and the
single-device non-uniform runner. The ring-down witness further requires a
recorded probe series and a `GaussianPulse` registered through `add_source()`,
`add_polarized_source()`, lumped/wire `add_port()`, or `add_msl_port()`; it does
not inspect waveguide, TFSF, or Floquet excitations. Other lanes and source
families must be checked explicitly.

Dedicated S-matrix calculators have their own settling checks:
`compute_msl_s_matrix`, `compute_waveguide_s_matrix`,
`compute_mixed_s_matrix`, `compute_coaxial_two_port`, and
`compute_coax_msl_transition` compare their recorded `settling_db` values with
the shared -40 dB threshold. A warning means the fixed record may contain a
truncated transient; it does not modify the returned matrix or establish that
an unwarned result is validated.

| Signal | Interpretation | Action |
|---|---|---|
| `no field energy was recorded` | the recorded probe series, or all returned final-state fields when no series exists, is exactly zero | check source position, component, amplitude, boundary placement, and probe coverage |
| `ring-down truncated` | the final 5% of a recorded probe series is above -40 dB of its peak for an absorbing-boundary Gaussian-pulse run that used automatic `num_periods` | the probe suggests incomplete settling; increase `num_periods`, or use `until_decay` where supported, and verify the actual observable separately |
| non-finite `time_series` or lumped/wire `s_params` | the solver or setup produced NaN/Inf in an automatically inspected field | treat the run as failed; fix setup or stability, and check other result fields explicitly |
| passivity or per-frequency `\|S\|` advisory | an automatically guarded S-parameter path exceeds its documented tolerance | inspect mesh, settling, reference plane, normalization, and port-family guidance; do not interpret the affected bin as device physics |

Automatic passivity coverage is calculator-specific. Lumped/wire `run()` and
uniform single-device `forward()` warn on a non-finite entry or an individual
`|S| > 1.1`; they do not test total outgoing column power. MSL and waveguide
full-matrix calculators use column-power and per-entry checks with
normalization-specific tolerances.
`compute_coaxial_line_reflection()` has no automatic shared passivity warning:
inspect its `status`, residual fields, `s11` finiteness, and magnitude directly.
No warning means only that an implemented check did not fire; it is not a
passivity or convergence certificate.

On the uniform CPML/UPML runner, `until_decay=1e-3` monitors the sum of squared
field components outside the absorber as an interior field-norm proxy. It stops
after that proxy remains below `1e-3` of its peak for the required consecutive
checks. This avoids a false stop at a null of a single-cell monitor, but it does
not directly prove convergence of every DFT, S-parameter, Harminv, or NTFF
observable. The non-uniform (`dx_profile` / `dy_profile` / `dz_profile`) runner
supports the same stop on CPML/UPML boundaries, weighting each cell's squared
fields by that cell's `dx*dy*dz` volume so graded cells contribute in
proportion to their size. On that lane every step-sized buffer is allocated at
`decay_max_steps` and flux monitors must keep the default rectangular DFT
window; a non-uniform run with closed boundaries warns and executes the fixed
`n_steps` instead (that lane has no single-cell monitor stop). Other runners may warn or reject `until_decay`; consult the
emitted message instead of assuming it was applied. Closed PEC domains do not
normally lose their field norm, so use a fixed run length unless the model
contains a real loss mechanism. Explicit `n_steps` remains appropriate when an
exact accumulation window is required.

## Differentiable `forward(...)` path

Use `sim.forward(...)` when a scalar objective must be differentiated with
`jax.grad`, `jax.value_and_grad`, or an outer `jax.jit`-compiled loss function.
It returns the minimal `ForwardResult` needed by differentiable objectives
(time series, optional S11 vectors, optional NTFF data) rather than the broader
stateful `Result` from `run(...)`.

Common differentiable inputs are:

| Input | Scope |
|---|---|
| `eps_override=...` / `sigma_override=...` | continuous material arrays with `grid.shape` |
| `port_s11_freqs=...` | lumped/wire per-port differentiable S11 vectors on the uniform single-device path |
| `rlc_values_override={index: {"R": R, "L": L, "C": C}}` | scalar values for registered `add_lumped_rlc(...)` elements on the uniform single-device path |
| `checkpoint_segments=...` / `checkpoint_every=...` | reverse-mode memory reduction where the selected runner documents support |

Keep setup choices that change array shapes, registered geometry, or the
selected runner outside the jitted objective. Unsupported combinations raise
instead of silently falling back.

## Automation entry points

`Simulation` also exposes a convenience constructor that derives `dx`, `domain`,
and CPML thickness from the analysis frequency range:

```python
sim = Simulation.auto(freq_range=(1.5e9, 3.5e9), accuracy="standard")
```

`accuracy` is one of `"draft"`, `"standard"`, or `"high"`. Because the domain is
sized from the frequency range rather than from your structure, `auto()` emits a
warning reminding you to add geometry after construction (or to pass `domain` /
`dx` overrides). Use the plain constructor when you need to inspect or override
the derived setup. See [Automation](https://remilab.ai/rfx/versions/v1.8.0/api/automation/) for the underlying
`auto_configure()` helper.

Configured simulations can also produce the same planning artifact:

```python
mesh_plan = sim.plan_mesh(
    n_steps=10_000,
    available_memory_gb=24.0,
    sparameter_calculator="waveguide",
)
```

`MeshPlan` is advisory: it records support checks and declaration-only artifact
paths, but it does not claim solver replay or physics validation by itself.
Passing `artifact_root` only fills intended paths; it does not write files.
Configured-simulation plans use `plan_source="configured_simulation"`,
`freq_range[0] = None`, and `accuracy = None` in Python (`null` after JSON
serialization) because no auto-configure accuracy preset or lower-band edge is
implied by an existing `Simulation`.

## Current public boundary

This page covers the documented high-level builder only. Lower-level solver
internals, private symbols, and internal extensions are outside the public API
until documented. For recommended, limited, and unsupported configurations, see
[Support Boundaries](https://remilab.ai/rfx/versions/v1.8.0/api/support-boundaries/).
