# Tutorial: Convergence Study

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

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

A convergence study answers a simple question: does the reported metric stabilize as the Yee cell size decreases? Use it before making quantitative claims from a new geometry, mesh, or objective.

## 1. Choose one scalar metric

Pick a metric that matches the claim you want to make:

| Claim type | Example metric |
|---|---|
| resonance | first Harminv frequency in a target band |
| S-parameter | `abs(S[receiver, driven, f_idx])` at a documented frequency |
| field amplitude | peak or RMS probe value after source decay |
| far field | directivity or normalized pattern value from a documented NTFF setup |

Do not mix several changing metrics in one convergence decision. Run separate studies if needed.

## 2. Build a simulation factory

The factory should accept `dx` and return the same physical problem at that cell size.

```python
from rfx import Simulation, Box, GaussianPulse

def make_case(dx: float) -> Simulation:
    # Every face of the cavity and of the insert lands on a cell boundary at
    # all three cell sizes below, so the rasterized structure is the same
    # physical problem at each resolution.
    sim = Simulation(
        freq_max=4e9,
        domain=(0.08, 0.064, 0.04),
        boundary="pec",
        dx=dx,
    )
    sim.add_material("insert", eps_r=4.0, sigma=0.01)
    sim.add(Box((0.032, 0.024, 0.016), (0.048, 0.040, 0.024)), material="insert")
    sim.add_source((0.024, 0.032, 0.008), "ez", waveform=GaussianPulse(f0=2.4e9, bandwidth=0.8),
                   amplitude_kind="current")
    sim.add_probe((0.056, 0.032, 0.008), "ez")
    return sim
```

## 3. Run the study

```python
from rfx import convergence_study

def first_resonance(result) -> float:
    modes = result.find_resonances(freq_range=(2e9, 4e9))
    if not modes:
        raise RuntimeError("no mode in the band — lengthen the run or widen freq_range")
    return float(min(mode.freq for mode in modes))

study = convergence_study(
    sim_factory=make_case,
    dx_values=[2.0e-3, 1.6e-3, 1.0e-3],
    metric_fn=first_resonance,
    run_kwargs={"num_periods": 40},
)

study.summary()
```

That study prints:

```text
Mesh Convergence Summary
==================================================
   dx (mm)          Metric    Rel. Error
--------------------------------------------------
    2.0000     2.92824e+09       0.0227%
    1.6000     2.92846e+09       0.0150%
    1.0000     2.92872e+09       0.0063%
--------------------------------------------------
Extrapolated value : 2.9289e+09
Convergence order  : 1.85
```

Use a short trial run first, then rerun the final study with the time window
required by your observable. Keep geometry, source, absorber thickness where the model has one,
probe, and run duration fixed as closely as grid quantization permits. The
example selects the lowest mode in a fixed band, so the same mode is tracked at
every resolution, and it handles an empty `find_resonances(...)` result
explicitly rather than indexing into it.

## 4. Interpret the result

A useful convergence report should include:

- the tested `dx` values,
- the scalar metric at each resolution,
- the Richardson-extrapolated value and observed order,
- the relative error of the finest point,
- the exact command or notebook cell used to reproduce the study.

If the finest two points do not move monotonically or the observed order is implausible, treat the result as inconclusive and inspect the setup before tightening tolerances.

## 5. Archive the evidence

A convergence plot or table is evidence for that exact observable, not for every feature in the simulation. Keep it with the support status, git SHA, run command, and any external reference used for the final claim.

For broader validation context, see [Cross-Validation and Accuracy](https://remilab.ai/rfx/versions/v1.8.0/guide/validation/) and the [Benchmark Table](https://remilab.ai/rfx/versions/v1.8.0/guide/benchmarks/).
