How Route Choice can Impact Evacuation Times#

This notebook can be directly downloaded here to run it locally.

In this demonstration, we’ll be simulating a room with a single exit. We’ll place two distinct groups of agents in a designated zone within the room. Each group will be assigned a specific route to reach the exit: one group will follow the shortest path, while the other will take a longer detour.

To chart these paths, we’ll use several waypoints, creating unique journeys for the agents to navigate.

Configuring the Room Layout#

For our simulation, we’ll utilize a square-shaped room with dimensions of 20 meters by 20 meters. Inside, obstacles will be strategically placed to segment the room and guide both agent groups.

Hide code cell source
import pathlib
import pandas as pd
import numpy as np
import jupedsim as jps
import shapely
from shapely import Polygon
import pedpy
import matplotlib.pyplot as plt
from matplotlib.patches import Circle

%matplotlib inline
complete_area = Polygon(
    [
        (0, 0),
        (0, 20),
        (20, 20),
        (20, 0),
    ]
)
obstacles = [
    Polygon(
        [
            (5, 0.0),
            (5, 16),
            (5.2, 16),
            (5.2, 0.0),
        ]
    ),
    Polygon(
        [(15, 19), (15, 5), (7.2, 5), (7.2, 4.8), (15.2, 4.8), (15.2, 19)]
    ),
]

exit_polygon = [(19, 19), (20, 19), (20, 20), (19, 20)]
waypoints = [([3, 19], 3), ([7, 19], 2), ([7, 2.5], 2), ([17.5, 2.5], 2)]
distribution_polygon = Polygon([[0, 0], [5, 0], [5, 10], [0, 10]])
obstacle = shapely.union_all(obstacles)
walkable_area = pedpy.WalkableArea(shapely.difference(complete_area, obstacle))
Hide code cell source
fig, ax = plt.subplots(nrows=1, ncols=1)
ax.set_aspect("equal")
pedpy.plot_walkable_area(walkable_area=walkable_area, axes=ax)

for idx, (waypoint, distance) in enumerate(waypoints):
    ax.plot(waypoint[0], waypoint[1], "ro")
    ax.annotate(
        f"WP {idx+1}",
        (waypoint[0], waypoint[1]),
        textcoords="offset points",
        xytext=(10, -15),
        ha="center",
    )
    circle = Circle(
        (waypoint[0], waypoint[1]), distance, fc="red", ec="red", alpha=0.1
    )
    ax.add_patch(circle)

x, y = Polygon(exit_polygon).exterior.xy
plt.fill(x, y, alpha=0.1, color="orange")
centroid = Polygon(exit_polygon).centroid
plt.text(centroid.x, centroid.y, "Exit", ha="center", va="center", fontsize=8)

x, y = distribution_polygon.exterior.xy
plt.fill(x, y, alpha=0.1, color="blue")
centroid = distribution_polygon.centroid
plt.text(
    centroid.x, centroid.y, "Start", ha="center", va="center", fontsize=10
)
Text(2.5, 5.0, 'Start')
../_images/d42c2e14beba192779032727251935c3fad4b246b139e77362e5d0b40380e863.png

Configuration of Simulation Scenarios#

With our room geometry in place, the next step is to define the simulation object, the operational model and its corresponding parameters. In this demonstration, we’ll use the “collision-free” model.

We’ll outline an array of percentage values, allowing us to adjust the sizes of the two groups across multiple simulations. As a result, creating distinct simulation objects for each scenario becomes essential.

simulations = {}
percentages = [0, 20, 40, 50, 60, 70, 100]
total_agents = 100
for percentage in percentages:
    trajectory_file = f"trajectories_percentage_{percentage}.sqlite"
    simulation = jps.Simulation(
        dt=0.05,
        model=jps.CollisionFreeSpeedModel(
            strength_neighbor_repulsion=2.6,
            range_neighbor_repulsion=0.1,
            range_geometry_repulsion=0.05,
        ),
        geometry=walkable_area.polygon,
        trajectory_writer=jps.SqliteTrajectoryWriter(
            output_file=pathlib.Path(trajectory_file),
        ),
    )
    simulations[percentage] = simulation

Outlining Agent Journeys#

Having established the base configurations, it’s time to outline the routes our agents will take. We’ve designated two distinct pathways:

  • The first route is a direct path, guiding agents along the shortest distance to the exit.

  • The second route, in contrast, takes agents on a more extended journey, guiding them along the longest distance to reach the same exit.

These variations in routing are designed to showcase how agents navigate and respond under different evacuation strategies.

def set_journeys(simulation):
    exit_id = simulation.add_exit_stage(exit_polygon)
    waypoint_ids = []
    for waypoint, distance in waypoints:
        waypoint_ids.append(simulation.add_waypoint_stage(waypoint, distance))

    long_journey = jps.JourneyDescription([*waypoint_ids[:], exit_id])
    for idx, waypoint in enumerate(waypoint_ids):
        next_waypoint = (
            exit_id if idx == len(waypoint_ids) - 1 else waypoint_ids[idx + 1]
        )
        long_journey.set_transition_for_stage(
            waypoint, jps.Transition.create_fixed_transition(next_waypoint)
        )

    short_journey = jps.JourneyDescription([waypoint_ids[0], exit_id])
    short_journey.set_transition_for_stage(
        waypoint_ids[0], jps.Transition.create_fixed_transition(exit_id)
    )

    long_journey_id = simulation.add_journey(long_journey)
    short_journey_id = simulation.add_journey(short_journey)
    return short_journey_id, long_journey_id, waypoint_ids[0]

Allocation and Configuration of Agents#

With our environment set up, it’s time to introduce and configure the agents, utilizing the parameters we’ve previously discussed. We’re going to place agents in two distinct groups, the proportion of which will be determined by the specified percentage parameter.

  • The first group will be directed to take the longer route to the exit.

  • Conversely, the second group will be guided along the shortest path to reach the exit.

By doing so, we aim to observe and analyze the behaviors and dynamics between these two groups under varying evacuation strategies.

positions = jps.distribute_by_number(
    polygon=distribution_polygon,
    number_of_agents=total_agents,
    distance_to_agents=0.4,
    distance_to_polygon=0.7,
    seed=45131502,
)

Reminder:

Given that the journey operates as a graph, it’s essential to designate the initial target for the agents by setting the stage_id.

Launching the Simulations#

Having configured our environment, agents, and routes, we are now poised to set the simulation into motion. For the purposes of this demonstration, agent trajectories throughout the simulation will be systematically captured and stored within an SQLite database. This will allow for a detailed post-analysis of agent behaviors and movement patterns.

Note Given that we’ve set the time step at \(dt=0.05\) seconds and aim to restrict the simulation duration to approximately 2 minutes, we will cap the number of iterations per simulation to 3000.

trajectory_files = {}
for percentage, simulation in simulations.items():
    short_journey_id, long_journey_id, first_waypoint_id = set_journeys(
        simulation
    )

    num_items = int(len(positions) * (percentage / 100.0))

    for position in positions[num_items:]:
        simulation.add_agent(
            jps.CollisionFreeSpeedModelAgentParameters(
                position=position,
                journey_id=short_journey_id,
                stage_id=first_waypoint_id,
            )
        )

    for position in positions[:num_items]:
        simulation.add_agent(
            jps.CollisionFreeSpeedModelAgentParameters(
                position=position,
                journey_id=long_journey_id,
                stage_id=first_waypoint_id,
            )
        )

    while simulation.agent_count() > 0 and simulation.iteration_count() < 3000:
        simulation.iterate()

    trajectory_file = f"trajectories_percentage_{percentage}.sqlite"
    trajectory_files[percentage] = trajectory_file

Visualizing Agent Pathways#

To gain insights into the movement patterns of our agents, we’ll visualize their trajectories. Data for this endeavor will be pulled directly from the SQLite database we’ve previously populated.

from jupedsim.internal.notebook_utils import animate, read_sqlite_file

agent_trajectories = {}
for percentage in percentages:
    trajectory_file = trajectory_files[percentage]
    agent_trajectories[percentage], walkable_area = read_sqlite_file(
        trajectory_file
    )
    animate(
        agent_trajectories[percentage],
        walkable_area,
        title_note=f"Percentage: {percentage}%",
    ).show()