# ADI Solver (Experimental)

Channel: development; package: 2.0.0rc1; source: 2c32b7cf79b796fae825bfa08fc73d9914e61723

Page: https://remilab.ai/rfx/guide/adi-solver/
Source: https://github.com/bk-squared/rfx/blob/2c32b7cf79b796fae825bfa08fc73d9914e61723/docs/public/guide/adi-solver.mdx

`solver="adi"` is an implicit time-stepping scheme that can take steps larger
than the usual Courant limit. It is **experimental**. This page shows when it
helps, what the larger step costs in accuracy, and what it cannot model.

## Why it exists

The default `solver="yee"` is explicit. Its time step is set by the smallest
cell anywhere in the mesh, so one thin layer or narrow gap forces tiny steps on
the whole model. ADI (the Zheng–Chen–Zhang two-sub-step scheme) removes that
stability limit, and `adi_cfl_factor` sets how many times larger than the Yee
step it goes.

Stability is free; accuracy is not. ADI adds a dispersion error that grows
roughly as the square of the step. `adi_cfl_factor` is a speed setting, not an
accuracy setting.

## Example: a PEC cavity three ways

This resonates an empty 24 × 12 × 36 mm PEC cavity with explicit Yee and with
ADI at factors 2 and 5, and compares the TE101 frequency with the closed form:

```python
import numpy as np
from rfx import GaussianPulse, Simulation

C0 = 299_792_458.0
a, b, d = 24e-3, 12e-3, 36e-3
f_te101 = C0 / 2 * np.sqrt((1 / a) ** 2 + (1 / d) ** 2)

def cavity(solver, factor=2.0):
    sim = Simulation(freq_max=12e9, domain=(a, b, d), dx=1.5e-3,
                     boundary="pec", solver=solver, adi_cfl_factor=factor)
    sim.add_source((0.37 * a, 0.41 * b, 0.20 * d), "ey",
                   waveform=GaussianPulse(f0=f_te101, bandwidth=0.9),
                   amplitude_kind="field")      # ADI implements field sources only
    sim.add_probe((0.63 * a, 0.58 * b, 0.80 * d), "ey")
    return sim

for label, solver, factor in [("yee", "yee", 1.0),
                              ("adi x2", "adi", 2.0),
                              ("adi x5", "adi", 5.0)]:
    sim = cavity(solver, factor)
    dt = sim.run(n_steps=2, skip_preflight=True).dt
    n_steps = int(6.9e-9 / dt)                   # the same 6.9 ns record each time
    result = sim.run(n_steps=n_steps)
    modes = result.find_resonances(freq_range=(0.7 * f_te101, 1.5 * f_te101))
    te101 = min(modes, key=lambda m: abs(m.freq - f_te101))
    err = 100 * (te101.freq - f_te101) / f_te101
    print(f"{label}: {n_steps} steps, TE101 error {err:+.2f}%")
```

The ADI runs take a half and a fifth of the Yee steps for the same record, and
their TE101 error grows with the factor. The factor-5 run triggers the
`adi_3d_accuracy` preflight advisory.

## Choosing `adi_cfl_factor`

The committed check holds the 3-D lane at the default factor 2 within 2 % of
the analytic eigenfrequency of an empty PEC cavity
(`tests/unit/misc/test_review_tier1_validation_battery.py::test_optc1_adi_3d_cavity_eigenfrequency`).
Larger factors are not judged; the error grows with the factor and shrinks with
finer cells.

**Use `adi_cfl_factor <= 2` when a frequency must be accurate.** The default
is 2; a larger factor trades accuracy for larger steps on stiff meshes.

## What ADI can model

ADI runs a small subset of rfx:

- `mode="3d"` or `mode="2d_tmz"`, on a uniform mesh;
- `boundary="pec"` only. Every absorbing boundary is refused when the
  `Simulation` is built, and the default boundary is `"cpml"`, so pass
  `boundary="pec"` explicitly;
- a uniform, non-dispersive dielectric filling the whole domain. A dielectric or
  conductivity interface is refused;
- soft sources with `amplitude_kind="field"` (`add_source`) and point probes.

It refuses everything else before the first step: interior conductors of any
kind (volumes, sheets, wires), ports, TFSF, waveguide, coaxial and Floquet
ports, lumped RLC elements, dispersive materials, DFT planes, NTFF boxes,
graded meshes and subgridding. `run()` also refuses `until_decay`, snapshots,
checkpointing, subpixel smoothing and `conformal_pec`.

**Interior PEC is refused at every factor.** A conductor inside the domain
made the ADI fields grow without bound, even at factor 1. Preflight reports
`adi_interior_pec_unsupported`, and `run()` and `forward()` raise even if you
skip preflight. A PEC domain boundary is fine. For a model with metal inside,
use `solver="yee"`.

`solver="adi"` is differentiable: `jax.grad` flows through the implicit
solves.

## Limits

- **Experimental.** The 3-D lane is checked against an analytic PEC cavity
  (within 2 % at factor 2), and the 2-D TMz lane against a cavity resonance at
  factor 5. Neither contains a conductor or material interface, and ADI refuses
  both.
- **No demonstrated speed-up.** The larger step is allowed, but no measured
  end-to-end speed and accuracy gain on a real stiff mesh has been published.
  Do not assume ADI is faster for your model.
- **Accuracy falls with the factor.** Keep `adi_cfl_factor <= 2` for
  frequency-accurate results, or use Yee.
- **No interior metal, ports or S-parameters.** Use `solver="yee"` for
  antennas, circuits and anything you will publish.
- Status and scope: see the
  [support matrix](https://github.com/bk-squared/rfx/blob/2c32b7cf79b796fae825bfa08fc73d9914e61723/docs/guides/support_matrix.md).

For a longer walk-through, run `examples/tutorials/adi_solver_demo.py` from a
source checkout.
