From 6d30c5376ab6fb4f9fe0324ceef2d0edc98edc7f Mon Sep 17 00:00:00 2001 From: Jette Schumann Date: Sun, 8 Oct 2023 09:19:46 +0200 Subject: [PATCH] Revised the notebook --- docs/source/conf.py | 2 +- docs/source/notebooks/index.rst | 17 +- notebooks/corner.ipynb | 202 ++++--- notebooks/double-bottleneck.ipynb | 798 ++++++++++++++++++++++----- notebooks/queues_waiting.ipynb | 864 ++++++++++++++++++++++++++++++ 5 files changed, 1681 insertions(+), 202 deletions(-) create mode 100644 notebooks/queues_waiting.ipynb diff --git a/docs/source/conf.py b/docs/source/conf.py index 9753b8b91b..ae036ccff9 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -65,7 +65,7 @@ # -- Automatic execution of jupyter notebooks -------------------------------- nb_execution_excludepatterns = [] -nb_execution_timeout = 120 +nb_execution_timeout = 900 myst_enable_extensions = [ "amsmath", "colon_fence", diff --git a/docs/source/notebooks/index.rst b/docs/source/notebooks/index.rst index fa69d9bad0..ea7051c41c 100644 --- a/docs/source/notebooks/index.rst +++ b/docs/source/notebooks/index.rst @@ -5,11 +5,12 @@ Notebooks .. toctree:: :maxdepth: 1 - Simple Bottleneck Simulation - Simple Corner Simulation - Double bottleneck - How to work with Journeys - Modelling Motivation - How routing can influence evacuation times - Single file movement - Lane Formation + bottleneck + corner + double-bottleneck + journey + lane-formation + motivation + queues_waiting + routing + single-file diff --git a/notebooks/corner.ipynb b/notebooks/corner.ipynb index a4d4397afe..93b0add349 100644 --- a/notebooks/corner.ipynb +++ b/notebooks/corner.ipynb @@ -4,14 +4,19 @@ "cell_type": "markdown", "id": "ae798e28-45c8-401a-891d-fdfa71c6516a", "metadata": { + "editable": true, "pycharm": { "name": "#%% md\n" - } + }, + "slideshow": { + "slide_type": "" + }, + "tags": [] }, "source": [ - "# Simple Corner Simulation\n", + "# Movement around Corners\n", "\n", - "In the following we'll investigate the movement of pedestrians around corners. When pedestrians walk around corners they are expected to slow down and take a path that is close to the corner. According to RiMEA Test 6 **[TODO REF]** a scenario is configured where **20 agents** move towards a **corner** at which they should turn to the left.\n", + "In the following we'll investigate the movement of pedestrians around corners. When pedestrians walk around corners they are expected to slow down and take a path that is close to the corner. According to RiMEA Test 6 [1] a scenario is configured where **20 agents** move towards a **corner** at which they should turn to the left.\n", "\n", "Let's begin by importing the required packages for our simulation:" ] @@ -41,12 +46,8 @@ "import pedpy\n", "import pandas as pd\n", "import numpy as np\n", - "import plotly # visualise trajectories\n", - "import plotly.express as px\n", - "import plotly.graph_objects as go\n", - "from plotly.graph_objs import Figure\n", - "import sqlite3\n", - "from numpy.random import normal # normal distribution of free movement speed" + "from numpy.random import normal # normal distribution of free movement speed\n", + "import matplotlib.pyplot as plt" ] }, { @@ -60,7 +61,7 @@ "source": [ "## Setting up the Geometry\n", "\n", - "We define a corridor with a width of 2 meters and a corner on halfway:" + "According to the RiMEA Test we define a corridor with a width of 2 meters and a corner on halfway:" ] }, { @@ -82,21 +83,28 @@ "outputs": [], "source": [ "area = Polygon([(0, 0), (12, 0), (12, 12), (10, 12), (10, 2), (0, 2)])\n", - "area" + "walkable_area = pedpy.WalkableArea(area)\n", + "pedpy.plot_walkable_area(walkable_area=walkable_area).set_aspect(\"equal\")" ] }, { "cell_type": "markdown", - "id": "269d95ac", + "id": "6acd2af2-b811-4779-b3b3-46684ac9a281", "metadata": { + "editable": true, "pycharm": { "name": "#%% md\n" - } + }, + "raw_mimetype": "", + "slideshow": { + "slide_type": "" + }, + "tags": [] }, "source": [ "## Definition of Start Positions and Exit\n", "\n", - "Now we'll calculate the position of 20 agents in the lower left part of the geometry within an rectangle of 6 x 2 meters. To calculate the positions we use a library function from JuPedSim. We assume an agent size of 0.3 m and set the distance parameters accordingly. The exit is defined in the upper right of the geometry." + "Now we'll calculate the position of 20 agents in the lower left part of the geometry within an rectangle of 6 x 2 meters. For this purpose, we use a library function from JuPedSim that calclulates positions in a given polygon. We assume an agent size of 0.4 m (diameter) and set the distance parameters accordingly. The exit is defined in the upper right of the geometry." ] }, { @@ -104,19 +112,24 @@ "execution_count": null, "id": "fccaffe5-2f29-44a9-82f4-e6f220ffe8c8", "metadata": { + "editable": true, "pycharm": { "name": "#%%\n" - } + }, + "slideshow": { + "slide_type": "" + }, + "tags": [] }, "outputs": [], "source": [ "spawning_area = Polygon([(0, 0), (6, 0), (6, 2), (0, 2)])\n", "num_agents = 20\n", - "positions = jps.distributions.distribute_by_number(\n", + "pos_in_spawning_area = jps.distributions.distribute_by_number(\n", " polygon=spawning_area,\n", " number_of_agents=num_agents,\n", " distance_to_agents=0.4,\n", - " distance_to_polygon=0.15,\n", + " distance_to_polygon=0.2,\n", " seed=1,\n", ")\n", "exit_area = Polygon([(10, 11), (12, 11), (12, 12), (10, 12)])" @@ -124,28 +137,80 @@ }, { "cell_type": "markdown", - "id": "40bee00b-7b0d-46d7-914d-d88bea7bc39d", + "id": "badcdfb6-ffd5-4a78-a3cf-fedc137f5732", "metadata": { - "pycharm": { - "name": "#%% md\n" - } + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "Let's have a look at the basic simulation setup. The spawning area is shown in grey, the agents in blue and the exit area in red:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "073c2dab-542e-4ad7-9a94-7988ffa1b15f", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [ + "hide-input" + ] + }, + "outputs": [], + "source": [ + "def plot_simulation_configuration(\n", + " walkable_area, spawning_area, starting_positions, exit_area\n", + "):\n", + " axes = pedpy.plot_walkable_area(walkable_area=walkable_area)\n", + " axes.fill(*spawning_area.exterior.xy, color=\"lightgrey\")\n", + " axes.fill(*exit_area.exterior.xy, color=\"indianred\")\n", + " axes.scatter(*zip(*starting_positions))\n", + " axes.set_xlabel(\"x/m\")\n", + " axes.set_ylabel(\"y/m\")\n", + " axes.set_aspect(\"equal\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a840a3be-8759-4058-9e37-3b2cc483f0c3", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] }, + "outputs": [], "source": [ - "**TODO: plot config setup - geo + start positions + exit**" + "plot_simulation_configuration(\n", + " walkable_area, spawning_area, pos_in_spawning_area, exit_area\n", + ")" ] }, { "cell_type": "markdown", "id": "f2f206a3", "metadata": { + "editable": true, "pycharm": { "name": "#%% md\n" - } + }, + "slideshow": { + "slide_type": "" + }, + "tags": [] }, "source": [ "## Setting up the Simulation and Routing Details\n", "\n", - "As a next step we create a simulation object, set the configuration for the operational model (collision-free speed model) and define the routes for the agents. For this scenario only one journey is created as all agents should follow the same route." + "As a next step we create a simulation object, set the configuration for the operational model (collision-free speed model) and define the routes for the agents. Default values for the model parameters are set implicitly. For this scenario, only one journey is created as all agents should follow the same route." ] }, { @@ -153,9 +218,14 @@ "execution_count": null, "id": "36627194", "metadata": { + "editable": true, "pycharm": { "name": "#%%\n" - } + }, + "slideshow": { + "slide_type": "" + }, + "tags": [] }, "outputs": [], "source": [ @@ -174,9 +244,14 @@ "execution_count": null, "id": "c1cfdadc", "metadata": { + "editable": true, "pycharm": { "name": "#%%\n" - } + }, + "slideshow": { + "slide_type": "" + }, + "tags": [] }, "outputs": [], "source": [ @@ -194,37 +269,9 @@ } }, "source": [ - "## Specifying Agent Parameters\n", - "\n", - "As a next step we define the model-specific parameters for the agents. They share the same journey and model parameters except for the free movement speed which is normally distributed. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "bad06382", - "metadata": { - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "v_distribution = normal(1.34, 0.2, num_agents)" - ] - }, - { - "cell_type": "markdown", - "id": "569d86fe", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [ - "## Executing the Simulation\n", + "## Agent Parameters and Executing the Simulation\n", "\n", - "Now we can specifiy the indiviual starting positions and speeds and add the agents to the simulation. After that the simulation is started and iterates until all agents have reached the exit." + "As a next step we define the agent parameters and add them to the simulation. They share the same journey and model parameters except for the starting position and free movement speed which is normally distributed. After adding the agents, the simulation is started and iterates until all agents have reached the exit." ] }, { @@ -232,16 +279,23 @@ "execution_count": null, "id": "2a413666", "metadata": { + "editable": true, "pycharm": { "name": "#%%\n" - } + }, + "slideshow": { + "slide_type": "" + }, + "tags": [] }, "outputs": [], "source": [ - "for position, v0 in zip(positions, v_distribution):\n", + "v_distribution = normal(1.34, 0.05, num_agents)\n", + "\n", + "for pos, v0 in zip(pos_in_spawning_area, v_distribution):\n", " simulation.add_agent(\n", " jps.CollisionFreeSpeedModelAgentParameters(\n", - " journey_id=journey_id, stage_id=exit_id, position=position, v0=v0\n", + " journey_id=journey_id, stage_id=exit_id, position=pos, v0=v0\n", " )\n", " )\n", "\n", @@ -264,18 +318,25 @@ { "cell_type": "code", "execution_count": null, - "id": "8ac7d6d7", + "id": "5379718a-4af8-470b-8cf0-b4a6dd6f7193", "metadata": { + "editable": true, "pycharm": { "name": "#%%\n" - } + }, + "slideshow": { + "slide_type": "" + }, + "tags": [ + "hide-input" + ] }, "outputs": [], "source": [ "from jupedsim.internal.notebook_utils import animate, read_sqlite_file\n", "\n", "trajectory_data, walkable_area = read_sqlite_file(trajectory_file)\n", - "animate(trajectory_data, walkable_area)" + "animate(trajectory_data, walkable_area, every_nth_frame=5)" ] }, { @@ -287,8 +348,6 @@ } }, "source": [ - "**TODO add colorbar for speed to plot**\n", - "\n", "As expected the agents choose the shortest path and approach the corner in a funnel-shaped formation. Agents moving closer to the corner become slower than agents at the edge of the crowd who choose a longer path around the corner." ] }, @@ -296,18 +355,25 @@ "cell_type": "markdown", "id": "a4c85934", "metadata": { + "editable": true, "pycharm": { "name": "#%% md\n" - } + }, + "slideshow": { + "slide_type": "" + }, + "tags": [] }, "source": [ "## References & Further Exploration\n", "\n", - "**TODO RiMEA reference**\n", + "[1] RiMEA, 'Guideline for Microscopic Evacuation Analysis'(2016), URL: https://rimea.de/ \n", + "\n", + "Another RiMEA test regarding the movement in bottlenecks can be found in **TODO Link double-bottleneck**.\n", "\n", - "The chosen model here is based on the collision-free speed model. JuPedSim also incorporates another model known as GCFM. For more details on GCFM, refer to another notebook **(TODO: Link to the GCFM notebook)**.\n", + "This examples shows the basic behaviour of agents when moving around corners. A more advanced simulation and analysis can be found in **TODO ref to Mohcine's notebook**.\n", "\n", - "The demonstration employed a straightforward journey with a singular exit. For a more intricate journey featuring multiple intermediate stops and waiting zones, see the upcoming section **(TODO: Link to the advanced journey section)**." + "The demonstration employed a straightforward journey with a singular exit. For a more intricate journey featuring multiple intermediate stops and waiting zones, see the upcoming section **TODO: Link to the advanced journey section)**." ] } ], diff --git a/notebooks/double-bottleneck.ipynb b/notebooks/double-bottleneck.ipynb index a4b3688f84..bf45545b47 100644 --- a/notebooks/double-bottleneck.ipynb +++ b/notebooks/double-bottleneck.ipynb @@ -2,83 +2,145 @@ "cells": [ { "cell_type": "markdown", - "id": "ae798e28-45c8-401a-891d-fdfa71c6516a", - "metadata": {}, + "id": "119ebeac-d99f-4d30-a3ce-d992b9a86cee", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "source": [ - "# Double Bottleneck Simulation \n", + "# Movement through Bottlenecks\n", "\n", - "In this demonstration, we'll construct a **double bottleneck** situation and simulate the evacuation of **10 agents** positioned on a grid.\n", + "In this following, we'll investigate the movement of a crowd through **two successive bottlenecks** with a simulation. We expect that a jam occurs at the first bottleneck but not at the second one since the flow is considerably reduced by the first bottleneck.\n", "\n", - "**TODO: MORE EXPLANATION. What happens here? Why? Why do we hope to show?**\n" + "For this purpose, we'll setup a simulation scenario according to the RiMEA Test 12 [1] and analyse the results with pedpy to inspect the density and flow. After that we'll vary the width of the bottlenecks and investigate the effects on the movement.\n", + "\n", + "Let's begin by importing the required packages for our simulation:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a6d4f847-92e0-4905-b52b-a9e61aa2355f", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "from shapely import GeometryCollection, Polygon, to_wkt\n", + "import pathlib\n", + "import jupedsim as jps\n", + "from numpy.random import normal # normal distribution of free movement speed\n", + "import pedpy\n", + "import pandas as pd\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt" ] }, { "cell_type": "markdown", - "id": "0a36d789-2a97-46dd-ba5d-d231ed372b03", - "metadata": {}, + "id": "7a69458e-d0af-46e8-b3ff-0dca91e1015c", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "source": [ - "***Introduction***[CB]\n", - "\n", - "The simulation of the double bottleneck is based on the work for the (single) bottleneck. \n", - "Here, agents are positioned on both side of the bottleneck: left and right.\n", + "## Geometry Setup\n", "\n", - "How will the agents \"meet\"? \n", - "Will they disturb each other? Is it even possible to go through? \n", - "And what is the impact of the width of the bottleneck?\n", + "Let's construct the geometry according to RiMEA by defining two rooms and a corridor with a width of 1 meter. To consider the interactions of agents in the second bottleneck (when leaving the second room) the corridor ends 3 meters behind the second room.\n", + "By creating the union of all parts we end up with the whole walkable area.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d3087364-73e8-4b35-8a5e-2222df89f1fe", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "room1 = Polygon([(0, 0), (10, 0), (10, 10), (0, 10)])\n", + "room2 = Polygon([(15, 0), (25, 0), (25, 10), (15, 10)])\n", + "corridor = Polygon([(10, 4.5), (28, 4.5), (28, 5.5), (10, 5.5)])\n", "\n", - "In order to answer those questions, please follow the instructions below. " + "area = GeometryCollection(corridor.union(room1.union(room2)))\n", + "walkable_area = pedpy.WalkableArea(area.geoms[0])\n", + "pedpy.plot_walkable_area(walkable_area=walkable_area).set_aspect(\"equal\")" ] }, { "cell_type": "markdown", - "id": "af10ff53-673c-49e3-bd7d-79447e05c9db", - "metadata": {}, + "id": "c9f63836-0f81-4684-b99e-abdf664c9d53", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "source": [ - "Let's begin by importing the required packages for our simulation:" + "## Definition of Start Positions and Exit\n", + "\n", + "Now we define the spawning area according to RiMEA and calculate 150 positions within that area. The exit area is defined at the end of the corridor.\n" ] }, { "cell_type": "code", "execution_count": null, - "id": "9dba16d9", + "id": "5dec36fb-55f2-4225-aac7-32be899c9254", "metadata": { "editable": true, "slideshow": { "slide_type": "" }, - "tags": [ - "hide-input" - ] + "tags": [] }, "outputs": [], "source": [ - "from shapely import GeometryCollection, Polygon, to_wkt\n", - "import pathlib\n", - "import pandas as pd\n", - "import numpy as np\n", - "import jupedsim as jps\n", - "from jupedsim.distributions import distribute_by_number\n", - "import sqlite3 # parse trajectory db\n", - "import plotly.express as px\n", - "import plotly.graph_objects as go\n", - "from plotly.graph_objs import Figure\n", - "import pedpy # analysis\n", - "\n", - "%matplotlib inline" + "spawning_area = Polygon([(0, 0), (5, 0), (5, 10), (0, 10)])\n", + "num_agents = 150\n", + "pos_in_spawning_area = jps.distributions.distribute_by_number(\n", + " polygon=spawning_area,\n", + " number_of_agents=num_agents,\n", + " distance_to_agents=0.3,\n", + " distance_to_polygon=0.15,\n", + " seed=1,\n", + ")\n", + "exit_area = Polygon([(27, 4.5), (28, 4.5), (28, 5.5), (27, 5.5)])" ] }, { "cell_type": "markdown", - "id": "4c1eae67-0c1e-4f0b-b1f7-4e383e9092c4", - "metadata": {}, + "id": "573c6a35-d7dd-4126-8f3f-a7ec0cbc3f7e", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "source": [ - "## Setting up a geometry" + "Let's have a look at our setup:" ] }, { "cell_type": "code", "execution_count": null, - "id": "a45d0955-7092-4dda-bc44-707893e4449b", + "id": "220d8141-614e-464d-9dcc-fc5ea8d1a6af", "metadata": { "editable": true, "slideshow": { @@ -90,192 +152,623 @@ }, "outputs": [], "source": [ - "p1 = Polygon([(0, 0), (10, 0), (10, 10), (0, 10)])\n", - "p2 = Polygon([(10, 4), (15, 4), (15, 6), (10, 6)])\n", - "p3 = Polygon([(15, 0), (25, 0), (25, 10), (15, 10)])\n", - "area = GeometryCollection(p1.union(p2.union(p3)))\n", - "walkable_area = pedpy.WalkableArea(area.geoms[0])\n", - "pedpy.plot_walkable_area(walkable_area=walkable_area)" + "def plot_simulation_configuration(\n", + " walkable_area, spawning_area, starting_positions, exit_area\n", + "):\n", + " axes = pedpy.plot_walkable_area(walkable_area=walkable_area)\n", + " axes.fill(*spawning_area.exterior.xy, color=\"lightgrey\")\n", + " axes.fill(*exit_area.exterior.xy, color=\"indianred\")\n", + " axes.scatter(*zip(*starting_positions), s=1)\n", + " axes.set_xlabel(\"x/m\")\n", + " axes.set_ylabel(\"y/m\")\n", + " axes.set_aspect(\"equal\")" ] }, { - "cell_type": "markdown", - "id": "06443296", - "metadata": {}, + "cell_type": "code", + "execution_count": null, + "id": "1c65a38f-18bb-4d7f-bb24-e5ab2b7db651", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], "source": [ - "## Operational model\n", - "\n", - "Once the geometry is set, our subsequent task is to specify the model and its associated parameters.\n", - "For this demonstration, we'll employ the \"collision-free\" model." + "plot_simulation_configuration(\n", + " walkable_area, spawning_area, pos_in_spawning_area, exit_area\n", + ")" ] }, { "cell_type": "markdown", - "id": "f2f206a3", - "metadata": {}, + "id": "2b990c43-7c81-4f19-92d6-97280974c300", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "source": [ - "## Setting Up the Simulation Object\n", + "## Specification of Parameters und Running the Simulation\n", "\n", - "Having established the model and geometry details, and combined with other parameters such as the time step dt, we can proceed to construct our simulation object as illustrated below:" + "Now we just need to define the details of the operational model, routing and the specific agent parameters. In this example, the agents share the same parameters, ecxept for their free movement speed (and starting position)." ] }, { "cell_type": "code", "execution_count": null, - "id": "36627194", - "metadata": {}, + "id": "6a9b725e-cf77-4589-9023-3105665684ea", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "outputs": [], "source": [ - "trajectory_file = \"double_bottleneck.sqlite\"\n", + "trajectory_file = \"double-botteleneck.sqlite\" # output file\n", "simulation = jps.Simulation(\n", " model=jps.CollisionFreeSpeedModel(),\n", " geometry=area,\n", " trajectory_writer=jps.SqliteTrajectoryWriter(\n", " output_file=pathlib.Path(trajectory_file)\n", " ),\n", - ")" + ")\n", + "\n", + "exit_id = simulation.add_exit_stage(exit_area.exterior.coords[:-1])\n", + "journey = jps.JourneyDescription([exit_id])\n", + "journey_id = simulation.add_journey(journey)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cc6e1a78-2e15-4156-a6f8-47ef26a9db50", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "v_distribution = normal(1.34, 0.05, num_agents)\n", + "\n", + "for pos, v0 in zip(pos_in_spawning_area, v_distribution):\n", + " simulation.add_agent(\n", + " jps.CollisionFreeSpeedModelAgentParameters(\n", + " journey_id=journey_id,\n", + " stage_id=exit_id,\n", + " position=pos,\n", + " v0=v0,\n", + " radius=0.15,\n", + " )\n", + " )\n", + "\n", + "while simulation.agent_count() > 0:\n", + " simulation.iterate()" + ] + }, + { + "cell_type": "markdown", + "id": "77d7e165-1c0d-4aca-a5fa-d88d0625e8f4", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "## Visualization\n", + "\n", + "Let's have a look at the visualization of the simulated trajectories:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7a4455d6-3f11-4312-806a-d6d7ad6e1510", + "metadata": { + "editable": true, + "pycharm": { + "name": "#%%\n" + }, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "from jupedsim.internal.notebook_utils import animate, read_sqlite_file\n", + "\n", + "trajectory_data, walkable_area = read_sqlite_file(trajectory_file)\n", + "animate(trajectory_data, walkable_area)" ] }, { "cell_type": "markdown", - "id": "64564e7f", + "id": "00a44351-ee83-4dd0-b4f2-d16e2c82f35d", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "## Analysis\n", + "\n", + "We'll investigate the $N-t$ curve and Voronoi density with the help of PedPy. \n", + "\n", + "We evaluate the $N−t$ curve at the exit for room 1 and room 2. The gradient of this curve provides insights into the flow rate through the bottlenecks. Subsequently, we assess the Voronoi density for the whole geometry. This allows to identify areas in the setup where jamming occurs.\n", + "\n", + "Let's start with the $N-t$ curve. We define two measurement lines: between room 1 and the corridor and between room 2 and the corridor. To reuse the measurement lines for the scenario with the wider bottleneck we enlarge it here." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9d6ba215-a990-44c7-ad0d-0e35c57b139d", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "measurement_line1 = pedpy.MeasurementLine([(10, 4.4), (10, 5.6)])\n", + "measurement_line2 = pedpy.MeasurementLine([(25, 4.4), (25, 5.6)])\n", + "\n", + "pedpy.plot_measurement_setup(\n", + " walkable_area=walkable_area,\n", + " traj=trajectory_data,\n", + " traj_alpha=0.5,\n", + " traj_width=1,\n", + " measurement_lines=[measurement_line1, measurement_line2],\n", + " ml_color=\"b\",\n", + " ma_line_width=1,\n", + " ma_alpha=0.2,\n", + ").set_aspect(\"equal\")" + ] + }, + { + "cell_type": "markdown", + "id": "4c47d318-35e8-4e85-8dba-794ed9887e95", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "As a next stept we calculate the $N-t$ data and plot it:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "77936260-1008-4121-a0eb-beff089bac8f", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "nt1, crossing_frames1 = pedpy.compute_n_t(\n", + " traj_data=trajectory_data,\n", + " measurement_line=measurement_line1,\n", + ")\n", + "nt2, crossing_frames2 = pedpy.compute_n_t(\n", + " traj_data=trajectory_data,\n", + " measurement_line=measurement_line2,\n", + ")\n", + "\n", + "fig = plt.figure()\n", + "ax = fig.add_subplot(111)\n", + "ax.set_title(\"N-t\")\n", + "ax.plot(\n", + " nt1[\"time\"],\n", + " nt1[\"cumulative_pedestrians\"],\n", + " label=\"First Bottleneck\",\n", + ")\n", + "ax.plot(nt2[\"time\"], nt2[\"cumulative_pedestrians\"], label=\"Second Bottleneck\")\n", + "ax.legend()\n", + "ax.set_xlabel(\"t / s\")\n", + "ax.set_ylabel(\"# Pedestrians\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "f38a4c1d-b918-42d3-8ab3-695b186b9102", "metadata": {}, "source": [ - "## Specifying Routing Details\n", + "The results above show a similar and time-shifted flow for both bottlenecks. Further, to inspect the formation of jamming we analyse the individual speed and density over time. To do so, we use PedPy to calculate the individual speed and Voronoi polygons for each frame calculated by the simulation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "96ff53d6-c330-4059-9b32-942e8e9a6e07", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "individual_speed = pedpy.compute_individual_speed(\n", + " traj_data=trajectory_data,\n", + " frame_step=5,\n", + " speed_calculation=pedpy.SpeedCalculation.BORDER_SINGLE_SIDED,\n", + ")\n", + "\n", + "individual_voronoi_cells = pedpy.compute_individual_voronoi_polygons(\n", + " traj_data=trajectory_data,\n", + " walkable_area=walkable_area,\n", + " cut_off=pedpy.Cutoff(radius=0.8, quad_segments=3),\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "645f5da2-a06d-4185-8be9-7d5a79347105", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "Now we calculate the profiles in a grid of 0.25 x 0.25 meters for the time period in which the agents are entering the first bottleneck. This usually requires a bit of computation time, which is why we only consider 100 frames." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e32d7995-43cb-4d3c-b75e-1b50d3c976e9", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "min_frame_profiles = 1800\n", + "max_frame_profiles = 1900\n", + "\n", + "density_profiles, speed_profiles = pedpy.compute_profiles(\n", + " individual_voronoi_speed_data=pd.merge(\n", + " individual_voronoi_cells[\n", + " individual_voronoi_cells.frame.between(\n", + " min_frame_profiles, max_frame_profiles\n", + " )\n", + " ],\n", + " individual_speed[\n", + " individual_speed.frame.between(\n", + " min_frame_profiles, max_frame_profiles\n", + " )\n", + " ],\n", + " on=[\"id\", \"frame\"],\n", + " ),\n", + " walkable_area=walkable_area.polygon,\n", + " grid_size=0.25,\n", + " speed_method=pedpy.SpeedMethod.ARITHMETIC,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a75a840f-3f56-41a4-935b-19c525a2f0b4", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [ + "hide-input" + ] + }, + "outputs": [], + "source": [ + "fig, (ax0, ax1) = plt.subplots(nrows=1, ncols=2)\n", + "cm = pedpy.plot_profiles(\n", + " walkable_area=walkable_area,\n", + " profiles=density_profiles,\n", + " axes=ax0,\n", + " label=\"$\\\\rho$ / 1/$m^2$\",\n", + " vmin=0,\n", + " vmax=10,\n", + " title=\"Density\",\n", + ")\n", + "cm = pedpy.plot_profiles(\n", + " walkable_area=walkable_area,\n", + " profiles=speed_profiles,\n", + " axes=ax1,\n", + " label=\"v / m/s\",\n", + " vmin=0,\n", + " vmax=2,\n", + " title=\"Speed\",\n", + ")\n", + "fig.tight_layout(pad=2)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "ffeb03ca-482b-4c00-a389-221be7f809e5", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "## Comparison of Different Bottleneck Widths\n", "\n", - "At this juncture, we'll provide basic routing instructions, guiding the agents to progress towards the **first exit point** and the **second exit point**." + "As a last step we'll investigate how the width of the bottleneck influences our results. For this purpose, we'll configure and run the simulation with the exact same parameters except for the geometry. We will change the width of the corridor to 0.8 and 1.2 meters and compare the results.\n", + "\n", + "### Scenario with a Narrower Bottleneck\n", + "\n", + "Let's configure the simulation with a corridor width of 0.8 meters. We can reuse most of the parameters from our first simulation.\n", + "\n", + "We define the walkable area:" ] }, { "cell_type": "code", "execution_count": null, - "id": "c1cfdadc", + "id": "1e3eed30-af2a-45bd-8c96-b7614bde1c57", "metadata": {}, "outputs": [], "source": [ - "exits = [\n", - " simulation.add_exit_stage([(24, 0), (25, 0), (25, 10), (24, 10)]),\n", - " simulation.add_exit_stage([(0, 0), (1, 0), (1, 10), (0, 10)]),\n", - "]\n", - "journeys = [\n", - " simulation.add_journey(jps.JourneyDescription([exit])) for exit in exits\n", - "]" + "corridor_narrow = Polygon([(10, 4.6), (28, 4.6), (28, 5.4), (10, 5.4)])\n", + "area_narrow = GeometryCollection(corridor_narrow.union(room1.union(room2)))\n", + "walkable_area_narrow = pedpy.WalkableArea(area_narrow.geoms[0])\n", + "pedpy.plot_walkable_area(walkable_area=walkable_area_narrow).set_aspect(\n", + " \"equal\"\n", + ")" ] }, { "cell_type": "markdown", - "id": "269d95ac", + "id": "80b36d1c-38ee-4a94-8562-6032ebba7c66", "metadata": {}, "source": [ - "## Defining and Distributing Agents\n", + "The starting positions, exit, operational model parameters and general agent parameter remain the same. So we just need to setup a new simulation object with the journeys:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0875cb4d-b86a-4397-b8a4-7b5a08d54305", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "trajectory_file_narrow = \"double-botteleneck_narrow.sqlite\" # output file\n", + "simulation_narrow = jps.Simulation(\n", + " model=jps.CollisionFreeSpeedModel(),\n", + " geometry=area_narrow,\n", + " trajectory_writer=jps.SqliteTrajectoryWriter(\n", + " output_file=pathlib.Path(trajectory_file_narrow)\n", + " ),\n", + ")\n", "\n", - "Now, we'll position the agents and establish their attributes, leveraging previously mentioned parameters such as `exit1`, `exit2` and `profile_id`." + "exit_id = simulation_narrow.add_exit_stage(exit_area.exterior.coords[:-1])\n", + "journey = jps.JourneyDescription([exit_id])\n", + "journey_id = simulation_narrow.add_journey(journey)" + ] + }, + { + "cell_type": "markdown", + "id": "276322a5-0831-45bc-bf0c-bb54965a547a", + "metadata": {}, + "source": [ + "And execute the simulation - this may take a bit of computation time since the corridor is quite narrow:" ] }, { "cell_type": "code", "execution_count": null, - "id": "bad06382", + "id": "b1494dfc-a0bf-4b41-97c9-03c2e06bd39e", "metadata": {}, "outputs": [], "source": [ - "agent_parameters = jps.CollisionFreeSpeedModelAgentParameters()\n", - "agent_parameters.journey_id = journeys[0]\n", - "agent_parameters.stage_id = exits[0]\n", - "agent_parameters.orientation = (1.0, 0.0)\n", - "agent_parameters.v0 = 1.2\n", - "agent_parameters.radius = 0.15\n", - "agent_parameters.time_gap = 1\n", - "\n", - "for position in [(7, 7), (1, 3), (1, 5), (1, 7), (2, 7)]:\n", - " simulation.add_agent(\n", + "for pos, v0 in zip(pos_in_spawning_area, v_distribution):\n", + " simulation_narrow.add_agent(\n", " jps.CollisionFreeSpeedModelAgentParameters(\n", - " journey_id=journeys[0], stage_id=exits[0], position=position\n", + " journey_id=journey_id,\n", + " stage_id=exit_id,\n", + " position=pos,\n", + " v0=v0,\n", + " radius=0.15,\n", " )\n", " )\n", - "for position in [(25, 7), (21, 3), (21, 5), (21, 7), (22, 7)]:\n", - " simulation.add_agent(\n", - " jps.CollisionFreeSpeedModelAgentParameters(\n", - " journey_id=journeys[1], stage_id=exits[1], position=position\n", - " )\n", - " )" + "\n", + "while simulation_narrow.agent_count() > 0:\n", + " simulation_narrow.iterate()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8faa7a60-ac4e-412d-8f4b-9d2e4df66236", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "from jupedsim.internal.notebook_utils import animate, read_sqlite_file\n", + "\n", + "trajectory_data_narrow, walkable_area = read_sqlite_file(\n", + " trajectory_file_narrow\n", + ")\n", + "animate(trajectory_data_narrow, walkable_area)" ] }, { "cell_type": "markdown", - "id": "569d86fe", + "id": "6f8ec057-4df9-42b3-ac23-17835ce8c75b", "metadata": {}, "source": [ - "## Executing the Simulation\n", + "### Scenario with a Wider Bottleneck\n", "\n", - "With all components in place, we're set to initiate the simulation.\n", - "For this demonstration, the trajectories will be recorded in an sqlite database." + "Next, we configure the simulation with a corridor width of 1.2 meters. The steps are the same as for the narrower botteleck:" ] }, { "cell_type": "code", "execution_count": null, - "id": "2a413666", - "metadata": {}, + "id": "8370d7ec-b6f3-4ad4-a9f5-51b410c790f1", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "outputs": [], "source": [ - "while simulation.agent_count() > 0:\n", - " simulation.iterate()" + "corridor_wide = Polygon([(10, 4.4), (28, 4.4), (28, 5.6), (10, 5.6)])\n", + "area_wide = GeometryCollection(corridor_wide.union(room1.union(room2)))\n", + "walkable_area_wide = pedpy.WalkableArea(area_wide.geoms[0])\n", + "pedpy.plot_walkable_area(walkable_area=walkable_area_wide).set_aspect(\"equal\")" ] }, { - "cell_type": "markdown", - "id": "078b5b68", - "metadata": {}, + "cell_type": "code", + "execution_count": null, + "id": "b7978a3b-3f5b-4ec9-98f5-195239bc4cfe", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], "source": [ - "## Visualizing the Trajectories\n", + "trajectory_file_wide = \"double-botteleneck_wide.sqlite\" # output file\n", + "simulation_wide = jps.Simulation(\n", + " model=jps.CollisionFreeSpeedModel(),\n", + " geometry=area_wide,\n", + " trajectory_writer=jps.SqliteTrajectoryWriter(\n", + " output_file=pathlib.Path(trajectory_file_wide)\n", + " ),\n", + ")\n", "\n", - "For trajectory visualization, we'll extract data from the sqlite database. A straightforward method for this is employing the jupedsim-visualizer.\n", + "exit_id = simulation_wide.add_exit_stage(exit_area.exterior.coords[:-1])\n", + "journey = jps.JourneyDescription([exit_id])\n", + "journey_id = simulation_wide.add_journey(journey)\n", "\n", - "To-Do List:\n", + "for pos, v0 in zip(pos_in_spawning_area, v_distribution):\n", + " simulation_wide.add_agent(\n", + " jps.CollisionFreeSpeedModelAgentParameters(\n", + " journey_id=journey_id,\n", + " stage_id=exit_id,\n", + " position=pos,\n", + " v0=v0,\n", + " radius=0.15,\n", + " )\n", + " )\n", "\n", - " Incorporate references and hyperlinks to additional resources.\n", - " Integrate results visualization using the visualizer." + "while simulation_wide.agent_count() > 0:\n", + " simulation_wide.iterate()" ] }, { "cell_type": "code", "execution_count": null, - "id": "06d3e0c1-3066-48f1-9750-2129ae9549b4", - "metadata": {}, + "id": "15605003-b57a-4530-8251-cd745020da41", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "outputs": [], "source": [ "from jupedsim.internal.notebook_utils import animate, read_sqlite_file\n", "\n", - "trajectory_data, walkable_area = read_sqlite_file(trajectory_file)\n", - "animate(trajectory_data, walkable_area)" + "trajectory_data_wide, walkable_area = read_sqlite_file(trajectory_file_wide)\n", + "animate(trajectory_data_wide, walkable_area)" ] }, { "cell_type": "markdown", - "id": "a4c85934", + "id": "2aec6644-a390-41f7-820c-d1857e1c2374", "metadata": { "editable": true, "slideshow": { "slide_type": "" }, - "tags": [ - "hide-input" - ] + "tags": [] }, "source": [ - "## References & Further Exploration\n", + "### Comparison of the Results\n", "\n", - "The operational model discussed in the Model section is based on the collision-free model. JuPedSim also incorporates another model known as GCFM. For more details on GCFM, refer to another notebook (TODO: Link to the GCFM notebook).\n", - "\n", - "Our demonstration employed a straightforward journey with a singular exit. For a more intricate journey featuring multiple intermediate stops and waiting zones, see the upcoming section (TODO: Link to the advanced journey section).\n", - "\n", - "While we designated a single parameter profile for agents in this example, it's feasible to define multiple parameter profiles. Learn how to alternate between these profiles in the subsequent section (TODO: Link to the profile-switching section)." + "Now we can compare the $N-t$ curves for the three scenarios. " ] }, { - "cell_type": "markdown", - "id": "90f64e5a", + "cell_type": "code", + "execution_count": null, + "id": "a44a855e-7bbf-4f9b-8f4a-810cb6b1d524", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "nt1_narrow, crossing_frames1_narrow = pedpy.compute_n_t(\n", + " traj_data=trajectory_data_narrow,\n", + " measurement_line=measurement_line1,\n", + ")\n", + "nt1_wide, crossing_frames1_wide = pedpy.compute_n_t(\n", + " traj_data=trajectory_data_wide,\n", + " measurement_line=measurement_line1,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "392fa2fe-402a-49b3-85be-b2bd9e3f6b47", "metadata": { "editable": true, "slideshow": { @@ -285,7 +778,62 @@ "hide-input" ] }, - "source": [] + "outputs": [], + "source": [ + "fig = plt.figure()\n", + "ax = fig.add_subplot(111)\n", + "ax.set_title(\"N-t for Different Bottleneck Widths\")\n", + "ax.plot(\n", + " nt1_narrow[\"time\"],\n", + " nt1_narrow[\"cumulative_pedestrians\"],\n", + " label=\"0.8 m First Bottleneck\",\n", + ")\n", + "ax.plot(\n", + " nt1[\"time\"], nt1[\"cumulative_pedestrians\"], label=\"1.0 m First Bottleneck\"\n", + ")\n", + "ax.plot(\n", + " nt1_wide[\"time\"],\n", + " nt1_wide[\"cumulative_pedestrians\"],\n", + " label=\"1.2 m First Bottleneck\",\n", + ")\n", + "ax.legend()\n", + "ax.set_xlabel(\"t / s\")\n", + "ax.set_ylabel(\"# Pedestrians\")\n", + "ax.set_aspect(\"equal\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "59663b9b-c7e7-4cb2-bbc1-28b337e4ec1d", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "The results show: the smaller the width of the bottleneck, the smaller the flow as it takes longer for all 150 agents to enter the first bottleneck." + ] + }, + { + "cell_type": "markdown", + "id": "79c725d7-c79b-4e83-a432-a52a8c67f012", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "## References & Further Exploration\n", + "\n", + "[1] RiMEA, 'Guideline for Microscopic Evacuation Analysis'(2016), URL: https://rimea.de/ \n", + "\n", + "**TODO ref to related notebooks**" + ] } ], "metadata": { diff --git a/notebooks/queues_waiting.ipynb b/notebooks/queues_waiting.ipynb new file mode 100644 index 0000000000..88afe11680 --- /dev/null +++ b/notebooks/queues_waiting.ipynb @@ -0,0 +1,864 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "d0cb5cc2-0e8a-4cef-9f3c-abdce978c06c", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "# Waiting in Queues\n", + "\n", + "In the following, we'll simulate scenarios where agents are waiting in queues as an example for implementing **crowd management measures**. Since waiting behaviour is not a process that can be modelled by the operational model itself we explicitely need to define (and trigger) the **waiting behaviour** of the agents. In this example, we'll simulate a scenario where people arrive to a concert and can approach the entrance by four line-up gates. At the gates a ticket control is performed that lasts 10 seconds for each person.\n", + "\n", + "Let's import the required package:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8235dfdb-12e3-451e-9f3c-9ce251659d3f", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "from shapely import from_wkt, Polygon, intersection\n", + "import pedpy\n", + "import jupedsim as jps\n", + "import matplotlib.pyplot as plt\n", + "from matplotlib.patches import Circle\n", + "import pathlib\n", + "import pandas as pd\n", + "import numpy as np\n", + "from numpy.random import normal # normal distribution of free movement speed" + ] + }, + { + "cell_type": "markdown", + "id": "033f2cbc-c777-41e4-876c-b6dea1f5754a", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "## Definition of the Geometry\n", + "\n", + "The geometry is given in wkt format and can be easily converted:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "87cbcd1c-ef17-4646-a4a4-7def9f1de1ba", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "geo_wkt = \"GEOMETRYCOLLECTION (POLYGON ((33.07 62.14, 32.43 60.21, 32.28 58.14, 32.28 56.07, 32.28 53.99, 32.28 51.92, 32.28 49.84, 32.28 47.77, 32.28 44.83, 31.02 44.83, 26.13 44.83, 25.28 44.83, 25.58 49.98, 25.7 52.07, 25.77 54.17, 25.85 56.27, 26.04 58.14, 25.87 60.26, 24.27 61.67, 22.17 61.96, 21.89 62.17, 22.16 67.48, 22.09 68.2, 23.57 68.2, 24.21 68.57, 24.21 71.37, 21.12 71.37, 21.12 76.37, 24.11 76.37, 26.11 76.37, 31.12 76.37, 31.12 71.37, 27.4 71.37, 27.4 68.55, 27.99 68.2, 34.29 68.2, 34.83 68.56, 35.02 65.35, 35.2 62.14, 33.07 62.14), (29.15 57.02, 30.15 58.79, 28.15 58.79, 29.15 57.02), (24.95 68.67, 25.05 68.67, 25.05 71.31, 24.95 71.31, 24.95 68.67), (25.75 68.67, 25.85 68.67, 25.85 71.31, 25.75 71.31, 25.75 68.67), (26.55 68.67, 26.65 68.67, 26.65 71.31, 26.55 71.31, 26.55 68.67)))\"\n", + "geo = from_wkt(geo_wkt)\n", + "walkable_area = pedpy.WalkableArea(geo.geoms[0])\n", + "pedpy.plot_walkable_area(walkable_area=walkable_area).set_aspect(\"equal\")" + ] + }, + { + "cell_type": "markdown", + "id": "06e4f6cb-99ad-4fca-aa95-388b751a04b2", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "The geometry consists of four entrance gates at the top and an obstacle in the middle. The people should arrive at the bottom. The way to the entrance gates is enclosed by barriers which results in the shown geometry." + ] + }, + { + "cell_type": "markdown", + "id": "3ad3734b-c275-467f-b994-9419663717c2", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "## Definition of Starting Positions and Exit\n", + "\n", + "Let's calculate 20 positions in an area at the bottom of the geometry. The exit area is placed at the very top." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ada55f48-a649-48b8-82eb-9c1291e2e50b", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "num_agents = 20\n", + "spawning_polygon = Polygon([(25, 45), (35, 45), (35, 54), (25, 54)])\n", + "spawning_area = intersection(spawning_polygon, geo)\n", + "\n", + "agent_start_positions = jps.distribute_by_number(\n", + " polygon=spawning_area,\n", + " number_of_agents=num_agents,\n", + " distance_to_agents=0.4,\n", + " distance_to_polygon=0.2,\n", + " seed=123,\n", + ")\n", + "exit_area = Polygon([(22, 76), (30, 76), (30, 74), (22, 74)])" + ] + }, + { + "cell_type": "markdown", + "id": "5f240571-f73a-4dc3-9a83-08fe420c6245", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "Let's have a look at the setup:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9aa39661-63b5-4fc9-9b8f-f68772da47ff", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [ + "hide-input" + ] + }, + "outputs": [], + "source": [ + "def plot_simulation_configuration(\n", + " walkable_area, spawning_area, starting_positions, exit_area\n", + "):\n", + " axes = pedpy.plot_walkable_area(walkable_area=walkable_area)\n", + " axes.fill(*spawning_area.exterior.xy, color=\"lightgrey\")\n", + " axes.fill(*exit_area.exterior.xy, color=\"indianred\")\n", + " axes.scatter(*zip(*starting_positions), s=1)\n", + " axes.set_xlabel(\"x/m\")\n", + " axes.set_ylabel(\"y/m\")\n", + " axes.set_aspect(\"equal\")\n", + "\n", + " return axes" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5ff7e941-36ca-4d0b-84bb-bdf426c52ea5", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "plot_simulation_configuration(\n", + " walkable_area, spawning_area, agent_start_positions, exit_area\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "1a4cf369-69f8-416d-94ce-263a39dbe2fa", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "## Setting up the Simulation\n", + "\n", + "Let's setup a simulation object using the collision-free speed model:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "72481075-cd6a-4a45-98a8-394305361a0c", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "trajectory_file = \"queues_waiting.sqlite\" # output file\n", + "simulation = jps.Simulation(\n", + " model=jps.CollisionFreeSpeedModel(),\n", + " geometry=geo,\n", + " trajectory_writer=jps.SqliteTrajectoryWriter(\n", + " output_file=pathlib.Path(trajectory_file)\n", + " ),\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "3aa85a3a-f851-44ac-a626-13359d68f0b3", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "## Configure the Queues\n", + "\n", + "JuPedSim is providing the concept of queues which can be defined as a stage on the agents' journeys. To let the agents wait at the gates before they walk to the exit, we need to create a queue for each gate by defining several ordered waiting positions.\n", + "\n", + "We define five waiting positions for each gate - three positions in the gate and two infront:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3e5a057f-0d04-4808-86ba-6e0f48395f32", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "waiting_positions_gate1 = [\n", + " (27.1, 71),\n", + " (27.1, 70),\n", + " (27.1, 69),\n", + " (27.1, 67),\n", + " (27.1, 66),\n", + "]\n", + "waiting_positions_gate2 = [\n", + " (26.2, 71),\n", + " (26.2, 70),\n", + " (26.2, 69),\n", + " (26.2, 67),\n", + " (26.2, 66),\n", + "]\n", + "waiting_positions_gate3 = [\n", + " (25.35, 71),\n", + " (25.35, 70),\n", + " (25.35, 69),\n", + " (25.35, 67),\n", + " (25.35, 66),\n", + "]\n", + "\n", + "waiting_positions_gate4 = [\n", + " (24.5, 71),\n", + " (24.5, 70),\n", + " (24.5, 69),\n", + " (24.5, 67),\n", + " (24.5, 66),\n", + "]\n", + "\n", + "\n", + "waiting_positions_gates = [\n", + " waiting_positions_gate1,\n", + " waiting_positions_gate2,\n", + " waiting_positions_gate3,\n", + " waiting_positions_gate4,\n", + "]" + ] + }, + { + "cell_type": "markdown", + "id": "64c4e957-2be0-432e-b34a-b4a747bd3402", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "Now we create the queues based on these points and add them to the simulation. The handle for the queues is needed at a later point in the simulation loop to control the waiting." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "aee3557f-07e7-437b-8c51-6aa800f2e19c", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "waypoints_gates = [\n", + " simulation.add_queue_stage(i) for i in waiting_positions_gates\n", + "]\n", + "queue_gates = [simulation.get_stage(i) for i in waypoints_gates]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2c29e32b-65c7-4632-860e-b728626a910f", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "45faedec-1b5a-4b81-ad26-16b755600597", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "## Configure the Journeys\n", + "\n", + "We want to spread the agents evenly on the queues based on their current load. To do so we define an additional waypoint which implements distributing the agents to the desired (least targeted) entrance gate. In this way, all agents share the same journey but may chose different gates. We place the waypoint for distributing on the left above the obstacle." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d1cfbfa8-bd3d-4573-8300-556682babe90", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "waypoint_coords = (27.2, 59)\n", + "waypoint_dist = 0.75\n", + "\n", + "waypoint_for_distributing = simulation.add_waypoint_stage(\n", + " waypoint_coords, waypoint_dist\n", + ")\n", + "\n", + "exit = simulation.add_exit_stage(exit_area.exterior.coords[:-1])\n", + "journey = jps.JourneyDescription(\n", + " waypoints_gates + [exit, waypoint_for_distributing]\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "56c5a9c5-72e2-4cd7-9e84-4f148228896b", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "Now we need to set the transitions on the journey. For the transitions between the waypoint and the gates we choose the *least targeted* approach." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "885c5052-2594-4404-bda6-77c7c9189331", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "journey.set_transition_for_stage(\n", + " waypoint_for_distributing,\n", + " jps.Transition.create_least_targeted_transition(waypoints_gates),\n", + ")\n", + "\n", + "for wp in waypoints_gates:\n", + " journey.set_transition_for_stage(\n", + " wp, jps.Transition.create_fixed_transition(exit)\n", + " )\n", + "\n", + "journey_id = simulation.add_journey(journey)" + ] + }, + { + "cell_type": "markdown", + "id": "8ed59c07-4676-4e53-8d0a-74a456609b46", + "metadata": {}, + "source": [ + "Let's have a look at our setup:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "35149015-18cb-442f-af0b-63ae0477e730", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def plot_journey_details(\n", + " walkable_area,\n", + " source_area,\n", + " agent_start_positions,\n", + " exit_area,\n", + " waiting_positions_queues,\n", + " waypoints,\n", + " dists,\n", + "):\n", + " axes = plot_simulation_configuration(\n", + " walkable_area, source_area, agent_start_positions, exit_area\n", + " )\n", + " for queue_positions in waiting_positions_queues:\n", + " axes.scatter(*zip(*queue_positions), s=1)\n", + "\n", + " for coords, dist in zip(waypoints, dists):\n", + " circle = Circle(coords, dist, color=\"lightsteelblue\")\n", + " axes.add_patch(circle)\n", + " axes.scatter(coords[0], coords[1], marker=\"x\", color=\"black\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c6efc766-4b8e-4b7d-8fbf-2b4ecffbabbe", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "plot_journey_details(\n", + " walkable_area,\n", + " spawning_area,\n", + " agent_start_positions,\n", + " exit_area,\n", + " waiting_positions_gates,\n", + " [waypoint_coords],\n", + " [waypoint_dist],\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "332ee827-55b2-4c16-a6e9-119f876e3aa6", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "The plot shows the several waiting positions per gate and the waypoint for distributing. The agents start from the grey area at the bottom and walk to the center of the waypoint. When they reached the blue area they will decide for the least targeted gate and line up in the subsequent queue.\n", + "\n", + "The last two waiting positions are defined further away from the gates. As agents move to the first available waiting positions in the queue, the congestion (unordered waiting behaviour) forms at the last waiting point in the queue when the gates are full." + ] + }, + { + "cell_type": "markdown", + "id": "e5121043-b1bf-40c7-83c6-8ec254a62a53", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "## Running the Simulation\n", + "\n", + "As a last step we set the missing agent parameters and add the agents to the simulation:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3f97d654-6ed9-4d56-a852-a146645f0cb4", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "v_distribution = normal(1.34, 0.05, num_agents)\n", + "\n", + "for pos, v0 in zip(agent_start_positions, v_distribution):\n", + " simulation.add_agent(\n", + " jps.CollisionFreeSpeedModelAgentParameters(\n", + " journey_id=journey_id,\n", + " stage_id=waypoint_for_distributing,\n", + " position=pos,\n", + " v0=v0,\n", + " )\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "49e07060-5a14-4069-8b58-d522ae2d9852", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "Now we run the simulation and control the queues. Once an agent has entered one of the four queues, the indivdual waiting time of 10 seconds starts. After the waiting time the agent on the first waiting position in the queue is realeased and the others move up." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c73f5025-5ed7-4467-ad59-36542a4d2100", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "number_of_gates = 4\n", + "queue_started = [False for i in range(number_of_gates)]\n", + "gate_offsets = [0 for i in range(number_of_gates)]\n", + "\n", + "while (\n", + " simulation.agent_count() > 0\n", + " and simulation.iteration_count() < 5 * 60 * 100\n", + "):\n", + " for i in range(number_of_gates):\n", + " if queue_gates[i].count_enqueued() == 0:\n", + " queue_started[i] = False\n", + " elif not queue_started[i] and queue_gates[i].count_enqueued() > 0:\n", + " queue_started[i] = True\n", + " gate_offsets[i] = simulation.iteration_count()\n", + " elif (\n", + " queue_started[i]\n", + " and (simulation.iteration_count() - gate_offsets[i]) % 1000 == 0\n", + " ):\n", + " queue_gates[i].pop(1)\n", + "\n", + " simulation.iterate()" + ] + }, + { + "cell_type": "markdown", + "id": "9134c45f-0627-4bb7-8c09-23674fd75695", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "## Visualization of the Results" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6fb29a2d-3be9-48eb-9327-11b9851a4d79", + "metadata": { + "editable": true, + "pycharm": { + "name": "#%%\n" + }, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "from jupedsim.internal.notebook_utils import animate, read_sqlite_file\n", + "\n", + "trajectory_data, walkable_area = read_sqlite_file(trajectory_file)\n", + "animate(trajectory_data, walkable_area, every_nth_frame=5)" + ] + }, + { + "cell_type": "markdown", + "id": "e760c937-ba34-4246-b978-bf9c1e588c01", + "metadata": {}, + "source": [ + "Since the entrance gates are very close to each other it can happen that the agents can get in each other's way and might be pushed into another gate that is actually not a part of their journey. In this example, the agents are able to solve their conflicts at the beginne. Please note that the position and radius of the distribution waypoint, the speed and starting positions of the agents have a considerable influence on the initial filling of the gates. Agents could be stuck in a queue they didn't want to go to. To implement the distributing process in a more orderly manner, pre-filtering could be implemented using an additional queue instead of the waypoint for distributing." + ] + }, + { + "cell_type": "markdown", + "id": "1cd80c83-b9fe-4808-a6e9-743fc52b9b4d", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "## Different Routing Strategy\n", + "\n", + "In the scenario above it is assumed that the people are evenly distributed among the gates. Therefore, the entrances are evenly occupied. In reality, this is not always the case and often the entrance with the shortest route is chosen. For this reason, we look at another scenario with a different distribution strategy. We can reuse the general settings from above but will change the journey." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a7bee3ee-1565-40ac-acf7-6b3b575e362f", + "metadata": {}, + "outputs": [], + "source": [ + "trajectory_file_uneven = \"queues_waiting_uneven.sqlite\" # output file\n", + "simulation_uneven = jps.Simulation(\n", + " model=jps.CollisionFreeSpeedModel(),\n", + " geometry=geo,\n", + " trajectory_writer=jps.SqliteTrajectoryWriter(\n", + " output_file=pathlib.Path(trajectory_file_uneven)\n", + " ),\n", + ")\n", + "\n", + "waypoint_for_distributing = simulation_uneven.add_waypoint_stage(\n", + " waypoint_coords, waypoint_dist\n", + ")\n", + "exit = simulation_uneven.add_exit_stage(exit_area.exterior.coords[:-1])\n", + "\n", + "waypoints_gates = [\n", + " simulation_uneven.add_queue_stage(i) for i in waiting_positions_gates\n", + "]\n", + "queue_gates = [simulation_uneven.get_stage(i) for i in waypoints_gates]\n", + "\n", + "journey_uneven = jps.JourneyDescription(\n", + " [waypoint_for_distributing, exit] + waypoints_gates\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "b5784da9-53d9-467d-af83-8124cb0d4ab0", + "metadata": {}, + "source": [ + "Now we are using the *round robin* approach to distribute the agents on the gates. We define that two people are walking to gate 1 and 2, and the following ones move to gate 3 and 4 (one person each). This means that twice as many agents use gate 1 and 2." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "97942121-d4d6-4c5f-b4c5-2c0802940328", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "journey_uneven.set_transition_for_stage(\n", + " waypoint_for_distributing,\n", + " jps.Transition.create_round_robin_transition(\n", + " [\n", + " (waypoints_gates[0], 2),\n", + " (waypoints_gates[1], 2),\n", + " (waypoints_gates[2], 1),\n", + " (waypoints_gates[3], 1),\n", + " ]\n", + " ),\n", + ")\n", + "\n", + "for wp in waypoints_gates:\n", + " journey_uneven.set_transition_for_stage(\n", + " wp, jps.Transition.create_fixed_transition(exit)\n", + " )\n", + "\n", + "journey_id = simulation_uneven.add_journey(journey_uneven)" + ] + }, + { + "cell_type": "markdown", + "id": "71304bce-f5b7-4857-852b-ccf229adf8c1", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "As a last step we add the agents to the simulation, start the loop and configure the ticket control as in the other scenario. The results show that the entrance on the right side is used more frequently." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2424042a-e86e-46e8-bf8f-bbde16546f8b", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [ + "hide-input" + ] + }, + "outputs": [], + "source": [ + "for pos, v0 in zip(agent_start_positions, v_distribution):\n", + " simulation_uneven.add_agent(\n", + " jps.CollisionFreeSpeedModelAgentParameters(\n", + " journey_id=journey_id,\n", + " stage_id=waypoint_for_distributing,\n", + " position=pos,\n", + " v0=v0,\n", + " )\n", + " )\n", + "\n", + "number_of_gates = 4\n", + "queue_started = [False for i in range(number_of_gates)]\n", + "gate_offsets = [0 for i in range(number_of_gates)]\n", + "\n", + "while (\n", + " simulation_uneven.agent_count() > 0\n", + " and simulation_uneven.iteration_count() < 5 * 60 * 100\n", + "):\n", + " for i in range(number_of_gates):\n", + " if queue_gates[i].count_enqueued() == 0:\n", + " queue_started[i] = False\n", + " elif not queue_started[i] and queue_gates[i].count_enqueued() > 0:\n", + " queue_started[i] = True\n", + " gate_offsets[i] = simulation_uneven.iteration_count()\n", + " elif (\n", + " queue_started[i]\n", + " and (simulation_uneven.iteration_count() - gate_offsets[i]) % 1000\n", + " == 0\n", + " ):\n", + " queue_gates[i].pop(1)\n", + "\n", + " simulation_uneven.iterate()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "29bca166-e076-4aea-afb9-62f5f463bea6", + "metadata": { + "editable": true, + "pycharm": { + "name": "#%%\n" + }, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "from jupedsim.internal.notebook_utils import animate, read_sqlite_file\n", + "\n", + "trajectory_data_uneven, walkable_area = read_sqlite_file(\n", + " trajectory_file_uneven\n", + ")\n", + "animate(trajectory_data_uneven, walkable_area, every_nth_frame=5)" + ] + }, + { + "cell_type": "markdown", + "id": "51b622db-2edb-4c36-92df-b1f140fa9f4c", + "metadata": { + "editable": true, + "pycharm": { + "name": "#%% md\n" + }, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "## References & Further Exploration\n", + "\n", + "**TODO Links to other related notebooks**" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}