jupedsim_scenarios.sweep#
Monte Carlo sweep over scenario parameters and seeds.
run_sweep walks the cartesian product of one or more named axes and runs the scenario once per (axis-combination, seed) trial. Per-axis mutations are applied via user-supplied callables so the sweep doesn’t need to know about the scenario’s internal mutator surface — anything that mutates the scenario in place works:
- sweep = run_sweep(
base, axes={“v0”: [0.8, 1.2], “model”: [“CollisionFreeSpeedModel”]}, apply={
“v0”: lambda s, v: s.set_agent_params(0, desired_speed=v), “model”: lambda s, v: s.set_model_type(v),
}, seeds=range(40, 50),
) df = sweep.to_dataframe() # one row per trial; axis values + metrics
The library owns: cartesian product, seed iteration, per-trial scenario isolation (.copy() per trial), per-trial sqlite output naming, and result tabulation.
Set workers=N (or workers=0 for one process per CPU) to run trials
in parallel via joblib.Parallel (loky backend). Mutations are applied
in the parent process so user-supplied apply callables don’t need
any special pickling treatment — only the resulting mutated Scenario
crosses the process boundary.
Attributes#
Classes#
Collection of trials produced by run_sweep. |
|
One realised cell of the sweep: axis values + seed + result. |
Functions#
|
Run the scenario once per (axis combination, seed) pair. |
|
Run one simulation per (trial-params, seed) pair, building each |
Module Contents#
- class SweepResult[source]#
Collection of trials produced by run_sweep.
Holds the per-trial ScenarioResult objects (each pointing at its own on-disk sqlite). Call .cleanup() when done to delete the sqlites, or .save(path) first if you want to keep the metadata.
- save(path: str | pathlib.Path) None[source]#
Persist sweep metadata (axes, seeds, per-trial paths + metrics) as JSON.
The trajectory sqlites themselves are NOT moved — they stay where run_sweep’s output_dir put them. load() reattaches metadata to the sqlite files; if the sqlites are gone, the loaded result is metadata-only.
- class Trial[source]#
One realised cell of the sweep: axis values + seed + result.
extras is an opaque per-trial payload. run_sweep always leaves it None; run_sweep_from_factory lets the factory attach anything it likes (geometry, label, computed metadata) so downstream code can pick it up via for t in sweep.trials: t.extras.
- run_sweep(scenario: jupedsim_scenarios.runner.Scenario, *, axes: collections.abc.Mapping[str, collections.abc.Sequence[Any]] | None = None, apply: collections.abc.Mapping[str, AxisApplyFn] | None = None, seeds: collections.abc.Iterable[int | None] = (None,), output_dir: str | pathlib.Path | None = None, workers: int = 1, progress: collections.abc.Callable[[int, int, dict], None] | None = None) SweepResult[source]#
Run the scenario once per (axis combination, seed) pair.
- Parameters:
scenario – The base scenario.
.copy()is taken per trial; the caller’s scenario is never mutated.axes – Mapping of axis name → list of values. Trials cover the full cartesian product. Empty / None ⇒ no parameter sweep (seed-only).
apply – Mapping of axis name → callable
(Scenario, value) -> None. Mutates the trial’s scenario copy in place. Required for each axis inaxes.seeds – Seeds to replicate every axis combination over. Default
(None,)⇒ one trial per combination with whatever seed the scenario carries.output_dir – If given, every trial’s sqlite trajectory is placed inside it with a deterministic name (
trial_<index>.sqlite). If omitted, each trial gets its own tempfile (cleaned bySweepResult.cleanup).workers – Number of parallel worker processes.
1runs sequentially in the calling process;>1dispatches trials viajoblib.Parallel(loky backend);0selectsos.cpu_count(). Trial-level mutations are applied in the parent process, so userapplycallables don’t need any special pickling treatment.progress – Optional callback invoked after each trial with
(trial_index, total_trials, axis_values_with_seed).
- Return type:
- run_sweep_from_factory(factory: ScenarioFactoryFn, *, trials: collections.abc.Iterable[collections.abc.Mapping[str, Any]], seeds: collections.abc.Iterable[int | None] = (None,), output_dir: str | pathlib.Path | None = None, workers: int = 1, progress: collections.abc.Callable[[int, int, dict], None] | None = None) SweepResult[source]#
Run one simulation per (trial-params, seed) pair, building each scenario fresh via a user-supplied factory.
Use this when the scenario can’t be expressed as a single base mutated by axis values — typically because the geometry itself depends on trial parameters (e.g. a loop track whose radius scales with agent count). Each call to
factory(trial_params)is expected to construct a freshScenario.- Parameters:
factory – Callable
(trial_params) -> Scenarioor(trial_params) -> (Scenario, extras). Called once per trial-parameters dict in the parent process; the resulting Scenario is then pickled to a worker for the actual simulation.extras(if returned) is attached toTrial.extrasfor the caller to read after the sweep completes.trials – Iterable of trial-parameters mappings. The mapping’s keys become the DataFrame columns when you call
SweepResult.to_dataframe(), so name them meaningfully.seeds – Seeds to replicate every trial-params combination over. Default
(None,)⇒ one run per trial-params dict using the seed embedded in the factory’s Scenario.output_dir – Same semantics as
run_sweep.workers – Same semantics as
run_sweep.progress – Same semantics as
run_sweep.
- Return type: