How do I sweep over scenario shapes that axes / apply can’t express?#
run_sweep(base, axes=..., apply=...) is the right tool when every
trial is the same scenario with one or two knobs turned. Sometimes
trials differ in ways that don’t fit:
the journey (which stages to visit) changes per trial,
you want to add or remove elements per trial,
the geometry itself depends on a trial parameter.
For those, build each trial scenario explicitly with
Scenario.copy() + direct edits, then hand them to
run_sweep_from_factory.
import logging
from datetime import datetime
logging.getLogger("jupedsim_scenarios").setLevel(logging.WARNING)
print(f"Executed on {datetime.now().strftime('%d.%m.%Y, %H:%M')}")
from jupedsim_scenarios import load_scenario, run_sweep_from_factory
base = load_scenario("../assets/bottleneck.zip")
Executed on 21.07.2026, 09:46
Factory: one fresh Scenario per trial#
The factory receives the trial-parameters dict and returns a
Scenario. Start from base.copy() so each trial is isolated from
the others, then change whatever you need.
def make_trial(params):
scenario = base.copy()
scenario.set_agent_count(0, params["agents"])
scenario.set_agent_params(0, desired_speed=params["speed"])
scenario.max_simulation_time = params["horizon"]
scenario.source_path = params["label"]
return scenario
trials = [
{"label": "calm", "agents": 20, "speed": 1.0, "horizon": 60},
{"label": "normal", "agents": 30, "speed": 1.3, "horizon": 60},
{"label": "rushed", "agents": 30, "speed": 1.8, "horizon": 45},
{"label": "overcrowded", "agents": 50, "speed": 1.3, "horizon": 60},
]
sweep = run_sweep_from_factory(
make_trial,
trials=trials,
seeds=[100, 101, 102],
workers=2,
)
df = sweep.to_dataframe()[["label", "agents", "speed", "seed", "evacuation_time", "success"]]
df.head(8)
INFO - Using fallback logic: No journeys defined
INFO - Processing with parameters: {'number': 20, 'radius': 0.2, 'v0': 1.0, 'flow_start_time': 0, 'flow_end_time': 10, 'percentage': None, 'distribution_mode': 'by_number', 'use_flow_spawning': False, 'use_premovement': False, 'premovement_distribution': 'gamma', 'premovement_param_a': None, 'premovement_param_b': None, 'premovement_seed': None, 'radius_distribution': 'constant', 'v0_distribution': 'constant', 'desired_speed': 1.0}
INFO - Using default parameters: v0=1.0, radius=0.2, n_agents=20
INFO - Distribution jps-distributions_0: {'number': 20, 'radius': 0.2, 'v0': 1.0, 'distribution_mode': 'by_number', 'percentage': None, 'use_flow_spawning': False, 'flow_start_time': 0, 'flow_end_time': 10, 'strict_spawning': False, 'use_premovement': False, 'premovement_distribution': 'gamma', 'premovement_param_a': None, 'premovement_param_b': None, 'premovement_seed': None, 'radius_distribution': 'constant', 'radius_std': None, 'v0_distribution': 'constant', 'v0_std': None}
INFO - Added 20 agents using fallback logic (immediate), prepared 0 flow sources
INFO - Using fallback logic: No journeys defined
INFO - Processing with parameters: {'number': 20, 'radius': 0.2, 'v0': 1.0, 'flow_start_time': 0, 'flow_end_time': 10, 'percentage': None, 'distribution_mode': 'by_number', 'use_flow_spawning': False, 'use_premovement': False, 'premovement_distribution': 'gamma', 'premovement_param_a': None, 'premovement_param_b': None, 'premovement_seed': None, 'radius_distribution': 'constant', 'v0_distribution': 'constant', 'desired_speed': 1.0}
INFO - Using default parameters: v0=1.0, radius=0.2, n_agents=20
INFO - Distribution jps-distributions_0: {'number': 20, 'radius': 0.2, 'v0': 1.0, 'distribution_mode': 'by_number', 'percentage': None, 'use_flow_spawning': False, 'flow_start_time': 0, 'flow_end_time': 10, 'strict_spawning': False, 'use_premovement': False, 'premovement_distribution': 'gamma', 'premovement_param_a': None, 'premovement_param_b': None, 'premovement_seed': None, 'radius_distribution': 'constant', 'radius_std': None, 'v0_distribution': 'constant', 'v0_std': None}
INFO - Added 20 agents using fallback logic (immediate), prepared 0 flow sources
INFO - Using fallback logic: No journeys defined
INFO - Processing with parameters: {'number': 20, 'radius': 0.2, 'v0': 1.0, 'flow_start_time': 0, 'flow_end_time': 10, 'percentage': None, 'distribution_mode': 'by_number', 'use_flow_spawning': False, 'use_premovement': False, 'premovement_distribution': 'gamma', 'premovement_param_a': None, 'premovement_param_b': None, 'premovement_seed': None, 'radius_distribution': 'constant', 'v0_distribution': 'constant', 'desired_speed': 1.0}
INFO - Using default parameters: v0=1.0, radius=0.2, n_agents=20
INFO - Distribution jps-distributions_0: {'number': 20, 'radius': 0.2, 'v0': 1.0, 'distribution_mode': 'by_number', 'percentage': None, 'use_flow_spawning': False, 'flow_start_time': 0, 'flow_end_time': 10, 'strict_spawning': False, 'use_premovement': False, 'premovement_distribution': 'gamma', 'premovement_param_a': None, 'premovement_param_b': None, 'premovement_seed': None, 'radius_distribution': 'constant', 'radius_std': None, 'v0_distribution': 'constant', 'v0_std': None}
INFO - Added 20 agents using fallback logic (immediate), prepared 0 flow sources
INFO - Using fallback logic: No journeys defined
INFO - Processing with parameters: {'number': 30, 'radius': 0.2, 'v0': 1.3, 'flow_start_time': 0, 'flow_end_time': 10, 'percentage': None, 'distribution_mode': 'by_number', 'use_flow_spawning': False, 'use_premovement': False, 'premovement_distribution': 'gamma', 'premovement_param_a': None, 'premovement_param_b': None, 'premovement_seed': None, 'radius_distribution': 'constant', 'v0_distribution': 'constant', 'desired_speed': 1.3}
INFO - Using default parameters: v0=1.3, radius=0.2, n_agents=30
INFO - Distribution jps-distributions_0: {'number': 30, 'radius': 0.2, 'v0': 1.3, 'distribution_mode': 'by_number', 'percentage': None, 'use_flow_spawning': False, 'flow_start_time': 0, 'flow_end_time': 10, 'strict_spawning': False, 'use_premovement': False, 'premovement_distribution': 'gamma', 'premovement_param_a': None, 'premovement_param_b': None, 'premovement_seed': None, 'radius_distribution': 'constant', 'radius_std': None, 'v0_distribution': 'constant', 'v0_std': None}
INFO - Added 30 agents using fallback logic (immediate), prepared 0 flow sources
INFO - Using fallback logic: No journeys defined
INFO - Processing with parameters: {'number': 30, 'radius': 0.2, 'v0': 1.3, 'flow_start_time': 0, 'flow_end_time': 10, 'percentage': None, 'distribution_mode': 'by_number', 'use_flow_spawning': False, 'use_premovement': False, 'premovement_distribution': 'gamma', 'premovement_param_a': None, 'premovement_param_b': None, 'premovement_seed': None, 'radius_distribution': 'constant', 'v0_distribution': 'constant', 'desired_speed': 1.3}
INFO - Using default parameters: v0=1.3, radius=0.2, n_agents=30
INFO - Distribution jps-distributions_0: {'number': 30, 'radius': 0.2, 'v0': 1.3, 'distribution_mode': 'by_number', 'percentage': None, 'use_flow_spawning': False, 'flow_start_time': 0, 'flow_end_time': 10, 'strict_spawning': False, 'use_premovement': False, 'premovement_distribution': 'gamma', 'premovement_param_a': None, 'premovement_param_b': None, 'premovement_seed': None, 'radius_distribution': 'constant', 'radius_std': None, 'v0_distribution': 'constant', 'v0_std': None}
INFO - Added 30 agents using fallback logic (immediate), prepared 0 flow sources
INFO - Using fallback logic: No journeys defined
INFO - Processing with parameters: {'number': 30, 'radius': 0.2, 'v0': 1.3, 'flow_start_time': 0, 'flow_end_time': 10, 'percentage': None, 'distribution_mode': 'by_number', 'use_flow_spawning': False, 'use_premovement': False, 'premovement_distribution': 'gamma', 'premovement_param_a': None, 'premovement_param_b': None, 'premovement_seed': None, 'radius_distribution': 'constant', 'v0_distribution': 'constant', 'desired_speed': 1.3}
INFO - Using default parameters: v0=1.3, radius=0.2, n_agents=30
INFO - Distribution jps-distributions_0: {'number': 30, 'radius': 0.2, 'v0': 1.3, 'distribution_mode': 'by_number', 'percentage': None, 'use_flow_spawning': False, 'flow_start_time': 0, 'flow_end_time': 10, 'strict_spawning': False, 'use_premovement': False, 'premovement_distribution': 'gamma', 'premovement_param_a': None, 'premovement_param_b': None, 'premovement_seed': None, 'radius_distribution': 'constant', 'radius_std': None, 'v0_distribution': 'constant', 'v0_std': None}
INFO - Added 30 agents using fallback logic (immediate), prepared 0 flow sources
INFO - Using fallback logic: No journeys defined
INFO - Processing with parameters: {'number': 30, 'radius': 0.2, 'v0': 1.8, 'flow_start_time': 0, 'flow_end_time': 10, 'percentage': None, 'distribution_mode': 'by_number', 'use_flow_spawning': False, 'use_premovement': False, 'premovement_distribution': 'gamma', 'premovement_param_a': None, 'premovement_param_b': None, 'premovement_seed': None, 'radius_distribution': 'constant', 'v0_distribution': 'constant', 'desired_speed': 1.8}
INFO - Using default parameters: v0=1.8, radius=0.2, n_agents=30
INFO - Distribution jps-distributions_0: {'number': 30, 'radius': 0.2, 'v0': 1.8, 'distribution_mode': 'by_number', 'percentage': None, 'use_flow_spawning': False, 'flow_start_time': 0, 'flow_end_time': 10, 'strict_spawning': False, 'use_premovement': False, 'premovement_distribution': 'gamma', 'premovement_param_a': None, 'premovement_param_b': None, 'premovement_seed': None, 'radius_distribution': 'constant', 'radius_std': None, 'v0_distribution': 'constant', 'v0_std': None}
INFO - Added 30 agents using fallback logic (immediate), prepared 0 flow sources
INFO - Using fallback logic: No journeys defined
INFO - Processing with parameters: {'number': 30, 'radius': 0.2, 'v0': 1.8, 'flow_start_time': 0, 'flow_end_time': 10, 'percentage': None, 'distribution_mode': 'by_number', 'use_flow_spawning': False, 'use_premovement': False, 'premovement_distribution': 'gamma', 'premovement_param_a': None, 'premovement_param_b': None, 'premovement_seed': None, 'radius_distribution': 'constant', 'v0_distribution': 'constant', 'desired_speed': 1.8}
INFO - Using default parameters: v0=1.8, radius=0.2, n_agents=30
INFO - Distribution jps-distributions_0: {'number': 30, 'radius': 0.2, 'v0': 1.8, 'distribution_mode': 'by_number', 'percentage': None, 'use_flow_spawning': False, 'flow_start_time': 0, 'flow_end_time': 10, 'strict_spawning': False, 'use_premovement': False, 'premovement_distribution': 'gamma', 'premovement_param_a': None, 'premovement_param_b': None, 'premovement_seed': None, 'radius_distribution': 'constant', 'radius_std': None, 'v0_distribution': 'constant', 'v0_std': None}
INFO - Added 30 agents using fallback logic (immediate), prepared 0 flow sources
INFO - Using fallback logic: No journeys defined
INFO - Processing with parameters: {'number': 30, 'radius': 0.2, 'v0': 1.8, 'flow_start_time': 0, 'flow_end_time': 10, 'percentage': None, 'distribution_mode': 'by_number', 'use_flow_spawning': False, 'use_premovement': False, 'premovement_distribution': 'gamma', 'premovement_param_a': None, 'premovement_param_b': None, 'premovement_seed': None, 'radius_distribution': 'constant', 'v0_distribution': 'constant', 'desired_speed': 1.8}
INFO - Using default parameters: v0=1.8, radius=0.2, n_agents=30
INFO - Distribution jps-distributions_0: {'number': 30, 'radius': 0.2, 'v0': 1.8, 'distribution_mode': 'by_number', 'percentage': None, 'use_flow_spawning': False, 'flow_start_time': 0, 'flow_end_time': 10, 'strict_spawning': False, 'use_premovement': False, 'premovement_distribution': 'gamma', 'premovement_param_a': None, 'premovement_param_b': None, 'premovement_seed': None, 'radius_distribution': 'constant', 'radius_std': None, 'v0_distribution': 'constant', 'v0_std': None}
INFO - Added 30 agents using fallback logic (immediate), prepared 0 flow sources
INFO - Using fallback logic: No journeys defined
INFO - Processing with parameters: {'number': 50, 'radius': 0.2, 'v0': 1.3, 'flow_start_time': 0, 'flow_end_time': 10, 'percentage': None, 'distribution_mode': 'by_number', 'use_flow_spawning': False, 'use_premovement': False, 'premovement_distribution': 'gamma', 'premovement_param_a': None, 'premovement_param_b': None, 'premovement_seed': None, 'radius_distribution': 'constant', 'v0_distribution': 'constant', 'desired_speed': 1.3}
INFO - Using default parameters: v0=1.3, radius=0.2, n_agents=50
INFO - Distribution jps-distributions_0: {'number': 50, 'radius': 0.2, 'v0': 1.3, 'distribution_mode': 'by_number', 'percentage': None, 'use_flow_spawning': False, 'flow_start_time': 0, 'flow_end_time': 10, 'strict_spawning': False, 'use_premovement': False, 'premovement_distribution': 'gamma', 'premovement_param_a': None, 'premovement_param_b': None, 'premovement_seed': None, 'radius_distribution': 'constant', 'radius_std': None, 'v0_distribution': 'constant', 'v0_std': None}
INFO - Added 50 agents using fallback logic (immediate), prepared 0 flow sources
INFO - Using fallback logic: No journeys defined
INFO - Processing with parameters: {'number': 50, 'radius': 0.2, 'v0': 1.3, 'flow_start_time': 0, 'flow_end_time': 10, 'percentage': None, 'distribution_mode': 'by_number', 'use_flow_spawning': False, 'use_premovement': False, 'premovement_distribution': 'gamma', 'premovement_param_a': None, 'premovement_param_b': None, 'premovement_seed': None, 'radius_distribution': 'constant', 'v0_distribution': 'constant', 'desired_speed': 1.3}
INFO - Using default parameters: v0=1.3, radius=0.2, n_agents=50
INFO - Distribution jps-distributions_0: {'number': 50, 'radius': 0.2, 'v0': 1.3, 'distribution_mode': 'by_number', 'percentage': None, 'use_flow_spawning': False, 'flow_start_time': 0, 'flow_end_time': 10, 'strict_spawning': False, 'use_premovement': False, 'premovement_distribution': 'gamma', 'premovement_param_a': None, 'premovement_param_b': None, 'premovement_seed': None, 'radius_distribution': 'constant', 'radius_std': None, 'v0_distribution': 'constant', 'v0_std': None}
INFO - Added 50 agents using fallback logic (immediate), prepared 0 flow sources
INFO - Using fallback logic: No journeys defined
INFO - Processing with parameters: {'number': 50, 'radius': 0.2, 'v0': 1.3, 'flow_start_time': 0, 'flow_end_time': 10, 'percentage': None, 'distribution_mode': 'by_number', 'use_flow_spawning': False, 'use_premovement': False, 'premovement_distribution': 'gamma', 'premovement_param_a': None, 'premovement_param_b': None, 'premovement_seed': None, 'radius_distribution': 'constant', 'v0_distribution': 'constant', 'desired_speed': 1.3}
INFO - Using default parameters: v0=1.3, radius=0.2, n_agents=50
INFO - Distribution jps-distributions_0: {'number': 50, 'radius': 0.2, 'v0': 1.3, 'distribution_mode': 'by_number', 'percentage': None, 'use_flow_spawning': False, 'flow_start_time': 0, 'flow_end_time': 10, 'strict_spawning': False, 'use_premovement': False, 'premovement_distribution': 'gamma', 'premovement_param_a': None, 'premovement_param_b': None, 'premovement_seed': None, 'radius_distribution': 'constant', 'radius_std': None, 'v0_distribution': 'constant', 'v0_std': None}
INFO - Added 50 agents using fallback logic (immediate), prepared 0 flow sources
| label | agents | speed | seed | evacuation_time | success | |
|---|---|---|---|---|---|---|
| 0 | calm | 20 | 1.0 | 100 | 32.00 | True |
| 1 | calm | 20 | 1.0 | 101 | 28.61 | True |
| 2 | calm | 20 | 1.0 | 102 | 28.47 | True |
| 3 | normal | 30 | 1.3 | 100 | 33.01 | True |
| 4 | normal | 30 | 1.3 | 101 | 36.61 | True |
| 5 | normal | 30 | 1.3 | 102 | 35.76 | True |
| 6 | rushed | 30 | 1.8 | 100 | 28.82 | True |
| 7 | rushed | 30 | 1.8 | 101 | 30.49 | True |
Aggregate per trial label#
df.groupby("label").agg(
mean_evac=("evacuation_time", "mean"),
std_evac=("evacuation_time", "std"),
success_rate=("success", "mean"),
)
| mean_evac | std_evac | success_rate | |
|---|---|---|---|
| label | |||
| calm | 29.693333 | 1.998858 | 1.0 |
| normal | 35.126667 | 1.881710 | 1.0 |
| overcrowded | 54.240000 | 1.745766 | 1.0 |
| rushed | 30.286667 | 1.376311 | 1.0 |
Returning extras from the factory#
Return a (scenario, extras) tuple to attach arbitrary per-trial
data — useful for analysis that needs metadata the sweep itself
doesn’t track (e.g. the polygon you used, a description, a hash).
def make_trial_with_extras(params):
scenario = base.copy()
scenario.set_agent_count(0, params["agents"])
notes = f"{params['agents']} agents, default speed"
return scenario, {"notes": notes, "recipe_version": 2}
sweep_x = run_sweep_from_factory(
make_trial_with_extras,
trials=[{"agents": 20}, {"agents": 40}],
seeds=[200],
workers=1,
)
for trial in sweep_x:
print(trial.axis_values, "->", trial.extras)
sweep_x.cleanup()
{'agents': 20} -> {'notes': '20 agents, default speed', 'recipe_version': 2}
{'agents': 40} -> {'notes': '40 agents, default speed', 'recipe_version': 2}
2
When to pick which sweep#
Use |
Use |
|---|---|
One base scenario, knobs turned per trial. |
Each trial constructs a different scenario shape. |
Cartesian product of axes is what you want. |
Trials are an explicit list of dicts, not a grid. |
Mutations are 1–2 setter calls. |
Mutations need conditionals or |
You want the dataframe columns named after axes. |
You want richer per-trial metadata via |
sweep.cleanup()
12