Sweep parameters#
sweep() takes a base Spec and a dict of
dotted-path → list of values and yields one validated spec per point in their
Cartesian product. Because the axes are physically meaningful (depth in µm, NA,
density in cells/mm³), a sweep traces a scientifically interpretable surface,
for example “recall vs depth at NA 0.45”.
Define the axes#
Paths address any field in the spec:
nested models:
"acquisition.optics.na",a step by its (unique)
kind:"steps.place_neurons.density_per_mm3",a top-level field:
"seed".
from minisim import sweep
for variant in sweep(base_spec, {
"acquisition.optics.na": [0.3, 0.45, 0.6],
"acquisition.focal_depth_in_tissue_um": [50.0, 100.0, 150.0],
}):
print(variant.axes) # e.g. {'acquisition.optics.na': 0.45, ...}
Each yielded SweptSpec is a real Spec (it drops straight
into simulate) tagged with an axes dict recording the chosen value per path.
Every cross-field validator re-runs per combination, so an axis value that
produces an invalid spec raises immediately rather than at simulate time.
Collect a benchmark surface#
Combine a sweep with the benchmarking recipe to build a tidy table keyed on the physical axes:
import pandas as pd
from minisim import simulate, hungarian_match
rows = []
for variant in sweep(base_spec, {
"acquisition.optics.na": [0.3, 0.45, 0.6],
"acquisition.focal_depth_in_tissue_um": [50.0, 100.0, 150.0],
}):
rec = simulate(variant)
det = rec.ground_truth.detectable_subset()
A_est, _, _ = run_my_pipeline(rec.observed)
match = hungarian_match(A_est, det.A_observed)
rows.append({**variant.axes, "recall": match.recall()})
df = pd.DataFrame(rows) # one row per (na, depth) point, ready to pivot/plot
Notes#
axesis excluded from serialization, soSweptSpec.cache_key()equals the equivalent plain spec’s. Sweeping does not perturb cache dedup, and the tag vanishes when a recording is saved.An empty
axesdict yields the base spec once, withaxes={}.For expensive sweeps, swap
simulateforsimulate_cached()so repeated points load instead of recompute.