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:

  1. Construct an empty Scenario with just a walkable polygon.

  2. Use add_distribution / add_exit / add_zone / add_stage to populate it.

  3. Persist with save_scenario so the result loads back through the normal load_scenario 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')}")

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