REMI Lab
OPEN KNOWLEDGE / TOOLS

Parametric Sweeps

이 페이지의 내용

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.

ToolUse it for
rfx.parametric_sweep(factory, name, values)one parameter, any change (geometry, mesh, sources)
rfx.batch.ParameterSweep + run_batcha 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_manifestlong 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 np
from 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) outputs
dataset.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 json
from 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_kwargs are fingerprinted, so changing n_steps reruns every case. If you change something the runner cannot see (the factory, the metric, an input file), pass a new run_fingerprint="...".
  • The runner stops at the first failure. Pass continue_on_error=True to record failed cases with status="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_sweep holds every case in memory. Reduce the batch or the number of DFT-plane frequencies if it runs out of memory.
  • A short n_steps ranks cases by an unfinished response. Use a run length that lets each case settle before you compare metrics.
Back to top ↑