How do I persist a sweep and reload it later?#

A SweepResult carries everything you need to re-do the analysis: the axes, the seeds, per-trial metrics, and the paths of the trajectory sqlites. SweepResult.save(path) writes that bundle as JSON; SweepResult.load(path) brings it back.

What it does not do: move the sqlite files. They stay wherever run_sweep’s output_dir put them, so save the sweep alongside the output directory (or use a stable path).

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')}")

import tempfile
from pathlib import Path

from jupedsim_scenarios import SweepResult, load_scenario, run_sweep

base = load_scenario("../assets/bottleneck.zip")
tmp = Path(tempfile.mkdtemp(prefix="sweep-howto-"))
print(f"sqlite output dir: {tmp}")
Executed on 21.07.2026, 09:46
sqlite output dir: /tmp/sweep-howto-dxp61m7b

Run a small sweep and save it#

sweep = run_sweep(
    base,
    axes={"num_agents": [20, 30, 40]},
    apply={"num_agents": lambda s, n: s.set_agent_count(0, n)},
    seeds=[100, 101, 102],
    output_dir=tmp,
    workers=2,
)

sweep_json = tmp / "sweep.json"
sweep.save(sweep_json)
print(f"saved to {sweep_json} ({sweep_json.stat().st_size} bytes)")
INFO - Using fallback logic: No journeys defined
INFO - Processing with parameters: {'number': 20, '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'}
INFO - Using default parameters: v0=1.3, radius=0.2, n_agents=20
INFO - Distribution jps-distributions_0: {'number': 20, '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 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.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'}
INFO - Using default parameters: v0=1.3, radius=0.2, n_agents=20
INFO - Distribution jps-distributions_0: {'number': 20, '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 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.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'}
INFO - Using default parameters: v0=1.3, radius=0.2, n_agents=20
INFO - Distribution jps-distributions_0: {'number': 20, '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 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'}
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'}
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'}
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': 40, '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'}
INFO - Using default parameters: v0=1.3, radius=0.2, n_agents=40
INFO - Distribution jps-distributions_0: {'number': 40, '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 40 agents using fallback logic (immediate), prepared 0 flow sources
INFO - Using fallback logic: No journeys defined
INFO - Processing with parameters: {'number': 40, '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'}
INFO - Using default parameters: v0=1.3, radius=0.2, n_agents=40
INFO - Distribution jps-distributions_0: {'number': 40, '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 40 agents using fallback logic (immediate), prepared 0 flow sources
INFO - Using fallback logic: No journeys defined
INFO - Processing with parameters: {'number': 40, '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'}
INFO - Using default parameters: v0=1.3, radius=0.2, n_agents=40
INFO - Distribution jps-distributions_0: {'number': 40, '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 40 agents using fallback logic (immediate), prepared 0 flow sources
saved to /tmp/sweep-howto-dxp61m7b/sweep.json (7389 bytes)

Reload and confirm the metrics survived#

The reloaded SweepResult carries the same metrics, axes, and seeds as the original. Indexing and slicing work on the trial list.

restored = SweepResult.load(sweep_json)

print(f"trials:         {len(restored)}")
print(f"axes:           {dict(restored.axes)}")
print(f"seeds:          {restored.seeds}")
print(f"first trial:    seed={restored[0].seed}, success={restored[0].result.success}")
print(f"last three:     {[t.index for t in restored[-3:]]}")
trials:         9
axes:           {'num_agents': [20, 30, 40]}
seeds:          [100, 101, 102]
first trial:    seed=100, success=True
last three:     [6, 7, 8]

The dataframe round-trips too#

df_original = sweep.to_dataframe()[["num_agents", "seed", "evacuation_time"]]
df_restored = restored.to_dataframe()[["num_agents", "seed", "evacuation_time"]]

print("original head:")
print(df_original.head())
print("\nrestored head:")
print(df_restored.head())
print(f"\nequal: {df_original.equals(df_restored)}")
original head:
   num_agents  seed  evacuation_time
0          20   100            26.91
1          20   101            27.80
2          20   102            26.77
3          30   100            33.01
4          30   101            36.61

restored head:
   num_agents  seed  evacuation_time
0          20   100            26.91
1          20   101            27.80
2          20   102            26.77
3          30   100            33.01
4          30   101            36.61

equal: True

What breaks if the sqlites are gone#

Metrics live in the JSON, so things derived from them — success, evacuation time, the dataframe — still work after the sqlites are deleted. trajectory_dataframe() is the part that needs the file, and will raise FileNotFoundError.

sweep.cleanup()                             # delete sqlite files
broken = SweepResult.load(sweep_json)

print("metrics still work:")
print(broken.to_dataframe()[["num_agents", "seed", "evacuation_time"]].head(3))

try:
    broken[0].result.trajectory_dataframe()
except FileNotFoundError as exc:
    print(f"\nas expected: {type(exc).__name__}: {exc}")
metrics still work:
   num_agents  seed  evacuation_time
0          20   100            26.91
1          20   101            27.80
2          20   102            26.77

as expected: FileNotFoundError: No trajectory SQLite file available

When to use this#

  • Long sweeps: save once, re-analyse many times without re-running.

  • Notebook handoff: ship the JSON to a collaborator who only needs the metrics, not the trajectories.

  • CI regression baselines: store reference sweep JSONs in version control, compare future runs against them.