A sweep runs the same model over a set of parameter values and compares a metric. It is the simplest way to explore a small design space, and a good check before you start a gradient optimization. This page shows the four sweep tools and when to use each.
| Tool | Use it for |
|---|---|
rfx.parametric_sweep(factory, name, values) | one parameter, any change (geometry, mesh, sources) |
rfx.batch.ParameterSweep + run_batch | a Cartesian grid of several parameters |
rfx.vmap_material_sweep(sim, "material.eps_r", values) | material values only, run as one batched simulation |
rfx.batch.run_batch_with_manifest | long sweeps you want to resume and reproduce |
The examples use a coarse 2 mm mesh and a few hundred steps so the page runs quickly. A real sweep needs a converged mesh and a run long enough for the metric you compare.
A model factory
Every sweep tool except vmap_material_sweep calls a factory that builds a
fresh Simulation from the parameters. Keep the factory pure: everything that
defines a case comes from its arguments.
This model is a dielectric insert between a soft source and a probe:
import numpy as npfrom rfx import Box, GaussianPulse, Simulation
def make_insert_sim(insert_width=0.012, eps_r=4.0): sim = Simulation(freq_max=4e9, domain=(0.06, 0.04, 0.03), dx=2e-3, boundary="cpml", cpml_layers=6) sim.add_material("insert", eps_r=eps_r, sigma=0.01) y0, y1 = 0.020 - insert_width / 2, 0.020 + insert_width / 2 sim.add(Box((0.024, y0, 0.012), (0.036, y1, 0.018)), material="insert") sim.add_source((0.014, 0.020, 0.015), "ez", waveform=GaussianPulse(f0=2.4e9, bandwidth=0.8), amplitude_kind="current") sim.add_probe((0.046, 0.020, 0.015), "ez") return sim
def peak_probe(result): """Example metric: peak absolute probe signal.""" return float(np.max(np.abs(np.asarray(result.time_series[:, 0]))))One parameter
parametric_sweep calls factory(value) for each value, runs it, and returns
a SweepResult with .results, .param_values and .param_name:
from rfx import parametric_sweep
widths = [0.008, 0.012, 0.016]sweep1 = parametric_sweep(lambda w: make_insert_sim(insert_width=w), "insert_width", widths, n_steps=300)
for w, result in zip(sweep1.param_values, sweep1.results): print(f"width {w * 1e3:.0f} mm: peak probe {peak_probe(result):.3e}")n_steps= and until_decay= go to each run(); anything else goes in
run_kwargs=. rfx.plot_sweep(sweep1, metric=peak_probe) plots a metric
against the parameter. It also accepts the built-in names "s11_min_db" (for
runs with S-parameters) and "peak_field".
A grid of parameters
ParameterSweep holds the Cartesian product of several parameter lists.
run_batch calls factory(**params) for each combination and returns a list of
(params, result) pairs:
from rfx.batch import ParameterSweep, SimulationDataset, run_batch
grid = ParameterSweep(insert_width=[0.008, 0.016], eps_r=[3.0, 6.0])print(grid.keys, grid.total) # ['insert_width', 'eps_r'] 4
results = run_batch(make_insert_sim, grid, run_kwargs={"n_steps": 300})for params, result in results: print(params, f"{peak_probe(result):.3e}")SimulationDataset turns the pairs into arrays or a CSV file, for plotting or a
surrogate model. output_fn returns a 1-D feature vector per case:
dataset = SimulationDataset.from_results( results, input_keys=["insert_width", "eps_r"], output_fn=lambda r: np.array([peak_probe(r)]),)X, y = dataset.to_numpy() # X: (4, 2) inputs, y: (4, 1) outputsdataset.to_csv("sweep_metrics.csv")Material values in one batched run
When only material values change, the grid is the same for every case, and
vmap_material_sweep runs all of them as one batched simulation with
jax.vmap. On a GPU this is much faster than a loop. Name the parameter as
"<material>.<property>", or just "eps_r", "sigma" or "mu_r":
from rfx import vmap_material_sweep
base = make_insert_sim()vsweep = vmap_material_sweep(base, "insert.eps_r", np.array([2.0, 4.0, 6.0]), n_steps=300)print(vsweep.time_series.shape) # (n_values, n_steps, n_probes)The fast path supports PEC and CPML boundaries, soft sources, point probes and
DFT plane probes. A model with ports, TFSF, dispersion, flux monitors or an NTFF
box falls back to running the cases one by one. Memory grows with the batch
size, because each case keeps its own fields. Geometry and incidence-angle
sweeps belong in parametric_sweep.
Resumable sweeps with a manifest
For long sweeps, run_batch_with_manifest writes a manifest.json in the
output directory. It records a stable case ID, the parameters, status, timing,
metrics, and the size and hash of every artifact each case wrote.
import jsonfrom pathlib import Path
from rfx.batch import run_batch_with_manifest, summarize_batch_manifest
def metric_fn(result): return {"peak_probe": peak_probe(result)}
def artifact_fn(case_dir: Path, params, result): (case_dir / "metric.json").write_text(json.dumps(metric_fn(result))) return {"metric": "metric.json"} # paths relative to case_dir
records = run_batch_with_manifest( make_insert_sim, grid, "runs/insert-sweep", run_kwargs={"n_steps": 200}, metric_fn=metric_fn, artifact_fn=artifact_fn, resume=True,)print(summarize_batch_manifest("runs/insert-sweep/manifest.json")["status_counts"])How resume decides what to skip:
- A case is skipped only if its record is
completed, has at least one metric, matches the current parameters and run settings, and every artifact it declared is still on disk with the recorded size and hash. Anything else (failed, interrupted, modified or missing files) runs again. run_kwargsare fingerprinted, so changingn_stepsreruns every case. If you change something the runner cannot see (the factory, the metric, an input file), pass a newrun_fingerprint="...".- The runner stops at the first failure. Pass
continue_on_error=Trueto record failed cases withstatus="failed"and carry on.
Exporting S-parameters
If each case computes S-parameters (for example
run_kwargs={"compute_s_params": True} on a model with ports), write one
Touchstone file per case:
from rfx import write_touchstone
out_dir = Path("outputs/sweep")out_dir.mkdir(parents=True, exist_ok=True)for i, (params, result) in enumerate(results): if result.s_params is None: # this example model has no ports continue write_touchstone(out_dir / f"case_{i:03d}.s2p", result.s_params, result.freqs, z0=50.0)A Touchstone file is an exchange format, not a validation. Check the port type in the S-parameter support matrix before you rely on the numbers.
Limits
- A sweep repeats one model’s assumptions. If a single case uses an unsupported source, port or mesh combination, the sweep inherits it. Run and inspect one case before sweeping.
- Comparisons across geometry need the same accuracy everywhere. A feature
that snaps to the grid differently in each case adds a staircase error that
looks like a trend. Check
sim.preflight()for off-lattice warnings, and refine the mesh if the metric moves by less than a cell’s worth. vmap_material_sweepholds every case in memory. Reduce the batch or the number of DFT-plane frequencies if it runs out of memory.- A short
n_stepsranks cases by an unfinished response. Use a run length that lets each case settle before you compare metrics.
