How do I build a scenario in pure Python (no JSON editing)?#
The web UI’s JSON export is the canonical way to author a scenario, but for quick experiments — sanity-checking a geometry, parameter studies on synthetic rooms, scripted regression fixtures — it’s faster to stay in Python.
The recipe:
Construct an empty
Scenariowith just a walkable polygon.Use
add_distribution/add_exit/add_zone/add_stageto populate it.Persist with
save_scenarioso the result loads back through the normalload_scenariopath.
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 Scenario, load_scenario, run_scenario, save_scenario
WKT = "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))"
scenario = Scenario(
raw={},
walkable_area_wkt=WKT,
model_type="CollisionFreeSpeedModel",
seed=42,
sim_params={"max_simulation_time": 60},
)
print(scenario.summary())
Executed on 21.07.2026, 09:45
Scenario: (in-memory)
Model: CollisionFreeSpeedModel
Seed: 42
Max time: 60s
Exits: 0
Distributions: 0
Stages: 0
Zones: 0
Journeys: 0
Agents: ~0
Add a distribution and an exit#
Each add_* returns the auto-generated id (jps-<collection>_{n}).
Pass key="my-name" to choose your own. Coordinates accept any
iterable of (x, y) pairs; the polygon is auto-closed.
dist_id = scenario.add_distribution(
[(1, 1), (4, 1), (4, 4), (1, 4)],
number=10,
desired_speed=1.3,
)
exit_id = scenario.add_exit(
[(9, 4), (10, 4), (10, 6), (9, 6)],
key="main-exit",
)
print(f"distribution: {dist_id}")
print(f"exit: {exit_id}")
distribution: jps-distributions_0
exit: main-exit
Add a slow zone and a waypoint stage#
add_zone(speed_factor=0.5) scales agent speed inside the polygon
(handy for stairs / congested regions). add_stage(waiting_time=...)
drops a checkpoint that agents pause at before moving on.
zone_id = scenario.add_zone(
[(5, 0), (7, 0), (7, 10), (5, 10)],
speed_factor=0.6,
)
stage_id = scenario.add_stage(
[(6, 4.5), (6.5, 4.5), (6.5, 5.5), (6, 5.5)],
waiting_time=2.0,
)
print(scenario.summary())
Scenario: (in-memory)
Model: CollisionFreeSpeedModel
Seed: 42
Max time: 60s
Exits: 1
Distributions: 1
Stages: 1
Zones: 1
Journeys: 0
Agents: ~10
jps-distributions_0: 10 agents
Persist and round-trip#
save_scenario(scenario, path) writes the self-contained JSON form
(geometry embedded in the same file). load_scenario reads it back —
no separate .wkt file needed.
import tempfile
from pathlib import Path
with tempfile.TemporaryDirectory() as tmp:
target = Path(tmp) / "my_scenario.json"
save_scenario(scenario, target)
reloaded = load_scenario(str(target))
print(reloaded.summary())
print(f"same exits? {list(reloaded.exits) == list(scenario.exits)}")
print(f"same distributions? {list(reloaded.distributions) == list(scenario.distributions)}")
Scenario: /tmp/tmpum7dmur9/my_scenario.json
Model: CollisionFreeSpeedModel
Seed: 42
Max time: 60s
Exits: 1
Distributions: 1
Stages: 1
Zones: 1
Journeys: 0
Agents: ~10
jps-distributions_0: 10 agents
same exits? True
same distributions? True
Run it#
Once the scenario has a distribution and an exit, you can hand it
straight to run_scenario. Stages and zones are optional decoration
on top.
result = run_scenario(scenario, seed=42)
print("success: ", result.success)
print("evacuation time:", result.evacuation_time, "s")
result.cleanup()
success: True
evacuation time: 17.75 s
1
Removing things by index#
remove_* accepts either the string id or an integer index — useful
when you don’t know the auto-generated id but you know it’s the
first / last one you added.
scenario.remove_zone(0) # by index
scenario.remove_exit("main-exit") # by key
print(scenario.summary())
Scenario: (in-memory)
Model: CollisionFreeSpeedModel
Seed: 42
Max time: 60s
Exits: 0
Distributions: 1
Stages: 1
Zones: 0
Journeys: 0
Agents: ~10
jps-distributions_0: 10 agents