Skip to content

Parametric Sweeps

Parametric sweeps are the safest way to explore a small RF design space before committing to a long optimization run. Start with a sequential sweep that you can inspect, then move to vectorized or accelerator-heavy evaluation only after the metric is stable.

rfx.batch.ParameterSweep stores a Cartesian product of scalar parameter values. It is intentionally small and explicit so every case can be inspected.

from rfx.batch import ParameterSweep
sweep = ParameterSweep(
patch_width=[0.028, 0.030, 0.032],
eps_r=[3.8, 4.2],
)
print(sweep.keys) # ['patch_width', 'eps_r']
print(sweep.total) # 6
for params in sweep.combinations():
print(params)

Keep the factory pure: all parameters needed to construct the case should come from the params dictionary. Store generated plots, Touchstone files, and HDF5 snapshots outside git unless they are curated docs assets.

from rfx import Simulation, Box, GaussianPulse
def make_patch_sim(patch_width: float, eps_r: float) -> Simulation:
sim = Simulation(freq_max=4e9, domain=(0.08, 0.06, 0.025), boundary="cpml")
sim.add_material("substrate", eps_r=eps_r)
sim.add(Box((0.0, 0.0, 0.0), (0.08, 0.06, 0.0016)), material="substrate")
sim.add(Box((0.024, 0.015, 0.0016), (0.052, 0.015 + patch_width, 0.0016)), material="pec")
sim.add_source((0.029, 0.030, 0.0008), "ez", waveform=GaussianPulse(2.4e9, 0.8))
sim.add_probe((0.029, 0.030, 0.0008), "ez")
return sim

run_batch(...) calls sim_factory(**params) for each parameter combination and returns a list of (params, result) tuples.

from rfx.batch import run_batch
results = run_batch(
make_patch_sim,
sweep,
run_kwargs={"n_steps": 500}, # use a short smoke first
)
for params, result in results:
print(params, result.time_series.shape)

For long runs, include a case identifier in your output path and write a manifest with the input parameters, command line, git SHA, support status, and metric used to rank the case. That manifest is what lets reviewers reproduce a plotted sweep.

SimulationDataset.from_results(...) converts batch outputs into rectangular arrays. The output_fn should return a one-dimensional numeric feature vector.

import numpy as np
from rfx.batch import SimulationDataset
def output_fn(result):
# Example diagnostic metric: peak absolute probe signal.
probe = np.asarray(result.time_series[:, 0])
return np.array([np.max(np.abs(probe))])
dataset = SimulationDataset.from_results(
results,
input_keys=["patch_width", "eps_r"],
output_fn=output_fn,
)
X, y = dataset.to_numpy()
dataset.to_csv("sweep_metrics.csv")

If the sweep uses a documented S-parameter calculator, keep the port family and support envelope with the output files.

from pathlib import Path
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:
continue
path = out_dir / f"case_{i:03d}.s2p"
write_touchstone(path, result.s_params, result.freqs, z0=50.0)

Touchstone export is an interoperability format, not validation by itself. Link any S-parameter claims to the current port-family evidence in docs/guides/sparameter_support_matrix.md.

Use jax.vmap when the computation is already a pure array function, such as a surrogate model, a closed-form RF approximation, or a differentiable objective that has been written without Python-side side effects. Most full Simulation object construction is easier to inspect and debug with explicit Python loops.

import jax
import jax.numpy as jnp
def quarter_wave_freq(length_m, eps_eff):
c0 = 299_792_458.0
return c0 / (4.0 * length_m * jnp.sqrt(eps_eff))
lengths = jnp.array([0.026, 0.028, 0.030])
eps_eff = jnp.array([3.0, 3.2, 3.4])
freqs = jax.vmap(quarter_wave_freq)(lengths, eps_eff)

For validated or long-running sweeps, prefer run_batch_with_manifest(). It writes manifest.json under the output directory with:

  • schema version
  • sweep keys and total case count
  • stable case IDs derived from parameter values
  • per-case status, timing, metrics, parameter digest, and artifact paths
  • artifact file size/hash metadata for resume integrity
  • summary counts, last-run status, and lightweight Python/platform metadata
  • resume-safe completed-case records
import json
from pathlib import Path
import numpy as np
from rfx.batch import (
ParameterSweep,
run_batch_with_manifest,
summarize_batch_manifest,
)
sweep = ParameterSweep(amplitude=[0.5, 1.0, 2.0])
def metric_fn(result):
state = result.state
peak_ez = float(np.max(np.abs(np.asarray(state.ez))))
return {"peak_ez": peak_ez}
def artifact_fn(case_dir: Path, params, result):
path = case_dir / "metric.json"
path.write_text(json.dumps(metric_fn(result), sort_keys=True))
# Case-local paths are normalized into output-root-relative manifest paths.
return {"metric": "metric.json"}
records = run_batch_with_manifest(
make_simulation,
sweep,
"runs/amplitude-sweep",
run_kwargs={"n_steps": 200},
metric_fn=metric_fn,
artifact_fn=artifact_fn,
resume=True,
)
summary = summarize_batch_manifest("runs/amplitude-sweep/manifest.json")
print(summary["status_counts"])

resume=True skips only records that are marked completed, match the current case parameters, include at least one metric, and still have all declared artifact files on disk with matching size/hash metadata. Missing or modified artifacts, empty metrics, interrupted running cases, and previous failures are not silently treated as valid completed runs.

run_kwargs are fingerprinted into each case record. Reusing the same output directory with a different run budget, such as a changed n_steps, forces a rerun instead of returning outdated cases. If the simulation factory, metric definition, mesh source, or any external input changes while run_kwargs stay the same, pass a new run_fingerprint="..." value to invalidate old cases.

By default the manifest-backed runner is fail-fast. For exploratory grids where later cases are still useful after one case fails, pass continue_on_error=True; failed cases are recorded with status="failed".

  • Did a single case run and produce physically plausible fields/probes?
  • Is the selected source/port family documented for the claimed metric?
  • Are generated files outside git or intentionally curated docs assets?
  • Does every case record parameters, git SHA, command, support status, and metric?
  • If results are public-facing, do they cite the support matrix rather than only saying that tests passed?