diff --git a/notebooks/corner.ipynb b/notebooks/corner.ipynb index a4d4397afe..a19e74cfb2 100644 --- a/notebooks/corner.ipynb +++ b/notebooks/corner.ipynb @@ -9,9 +9,9 @@ } }, "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:" ] @@ -38,15 +38,17 @@ "from shapely import GeometryCollection, Polygon, to_wkt\n", "import pathlib\n", "import jupedsim as jps\n", + "import jupedsim.distributions # for spawning positions\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\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" + "import sqlite3" ] }, { @@ -60,7 +62,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,7 +84,8 @@ "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')" ] }, { @@ -112,10 +115,10 @@ "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_agents=0.3,\n", " distance_to_polygon=0.15,\n", " seed=1,\n", ")\n", @@ -124,14 +127,50 @@ }, { "cell_type": "markdown", - "id": "40bee00b-7b0d-46d7-914d-d88bea7bc39d", + "id": "badcdfb6-ffd5-4a78-a3cf-fedc137f5732", + "metadata": {}, + "source": [ + "Let's have a look at the basic simulation setup. The spawning area is shown in light grey, the agents in blue and the exit area in dark grey:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "073c2dab-542e-4ad7-9a94-7988ffa1b15f", "metadata": { - "pycharm": { - "name": "#%% md\n" - } + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [ + "hide-input" + ] }, + "outputs": [], + "source": [ + "def plot_simulation_configuration(walkable_area, spawning_area, starting_positions, exit_area):\n", + " axes = plt.gca()\n", + " axes.plot(\n", + " *walkable_area.polygon.exterior.xy,\n", + " color='black',\n", + " )\n", + " axes.fill(*spawning_area.exterior.xy,\n", + " color='lightgrey')\n", + " axes.fill(*exit_area.exterior.xy, color='darkgrey')\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": {}, + "outputs": [], "source": [ - "**TODO: plot config setup - geo + start positions + exit**" + "plot_simulation_configuration(walkable_area, spawning_area, pos_in_spawning_area, exit_area)" ] }, { @@ -161,7 +200,7 @@ "source": [ "trajectory_file = \"corner.sqlite\" # output file\n", "simulation = jps.Simulation(\n", - " model=jps.CollisionFreeSpeedModel(),\n", + " model=jps.VelocityModelParameters(),\n", " geometry=area,\n", " trajectory_writer=jps.SqliteTrajectoryWriter(\n", " output_file=pathlib.Path(trajectory_file)\n", @@ -210,6 +249,13 @@ }, "outputs": [], "source": [ + "agent_parameters = jps.VelocityModelAgentParameters()\n", + "agent_parameters.journey_id = journey_id\n", + "agent_parameters.stage_id = exit_id\n", + "agent_parameters.radius = 0.15\n", + "agent_parameters.time_gap = 1\n", + "agent_parameters.tau = 0.5\n", + "\n", "v_distribution = normal(1.34, 0.2, num_agents)" ] }, @@ -238,12 +284,10 @@ }, "outputs": [], "source": [ - "for position, v0 in zip(positions, v_distribution):\n", - " simulation.add_agent(\n", - " jps.CollisionFreeSpeedModelAgentParameters(\n", - " journey_id=journey_id, stage_id=exit_id, position=position, v0=v0\n", - " )\n", - " )\n", + "for i in range(num_agents):\n", + " agent_parameters.position = pos_in_spawning_area[i]\n", + " agent_parameters.v0 = v_distribution[i]\n", + " simulation.add_agent(agent_parameters)\n", "\n", "while simulation.agent_count() > 0:\n", " simulation.iterate()" @@ -261,6 +305,286 @@ "## Visualizing the Trajectories\n" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "5379718a-4af8-470b-8cf0-b4a6dd6f7193", + "metadata": { + "pycharm": { + "name": "#%%\n" + }, + "tags": [ + "hide-input" + ] + }, + "outputs": [], + "source": [ + "def read_sqlite_file(\n", + " trajectory_file: str,\n", + ") -> (pedpy.TrajectoryData, pedpy.WalkableArea):\n", + " with sqlite3.connect(trajectory_file) as con:\n", + " data = pd.read_sql_query(\n", + " \"select frame, id, pos_x as x, pos_y as y, ori_x as ox, ori_y as oy from trajectory_data\",\n", + " con,\n", + " )\n", + " fps = float(\n", + " con.cursor()\n", + " .execute(\"select value from metadata where key = 'fps'\")\n", + " .fetchone()[0]\n", + " )\n", + " walkable_area = (\n", + " con.cursor().execute(\"select wkt from geometry\").fetchone()[0]\n", + " )\n", + " return (\n", + " pedpy.TrajectoryData(data=data, frame_rate=fps),\n", + " pedpy.WalkableArea(walkable_area),\n", + " )\n", + "\n", + "\n", + "def speed_to_color(speed, min_speed, max_speed, midpoint):\n", + " colorscale = px.colors.diverging.RdBu_r[::-1]\n", + "\n", + " # Normalize speed based on the midpoint\n", + " if speed >= midpoint:\n", + " normalized_speed = 0.5 + 0.5 * (speed - midpoint) / (\n", + " max_speed - midpoint\n", + " )\n", + " else:\n", + " normalized_speed = 0.5 * (speed - min_speed) / (midpoint - min_speed)\n", + "\n", + " # Clip to ensure the value is between 0 and 1\n", + " normalized_speed = np.clip(normalized_speed, 0, 1)\n", + "\n", + " # Find the corresponding color in the colorscale\n", + " color_idx = int(normalized_speed * (len(colorscale) - 1))\n", + " return colorscale[color_idx]\n", + "\n", + "\n", + "def get_geometry_traces(area):\n", + " geometry_traces = []\n", + " x, y = area.exterior.xy\n", + " geometry_traces.append(\n", + " go.Scatter(\n", + " x=np.array(x),\n", + " y=np.array(y),\n", + " mode=\"lines\",\n", + " line={\"color\": \"grey\"},\n", + " showlegend=False,\n", + " name=\"Exterior\",\n", + " hoverinfo=\"name\",\n", + " )\n", + " )\n", + " for inner in area.interiors:\n", + " xi, yi = zip(*inner.coords[:])\n", + " geometry_traces.append(\n", + " go.Scatter(\n", + " x=np.array(xi),\n", + " y=np.array(yi),\n", + " mode=\"lines\",\n", + " line={\"color\": \"grey\"},\n", + " showlegend=False,\n", + " name=\"Obstacle\",\n", + " hoverinfo=\"name\",\n", + " )\n", + " )\n", + " return geometry_traces\n", + "\n", + "\n", + "def get_shapes_for_frame(frame_data, min_speed, max_speed, midpoint):\n", + " def create_shape(row):\n", + " hover_trace = go.Scatter(\n", + " x=[row[\"x\"]],\n", + " y=[row[\"y\"]],\n", + " text=[f\"ID: {row['id']}, Pos({row['x']:.2f},{row['y']:.2f})\"],\n", + " mode=\"markers\",\n", + " marker=dict(size=1, opacity=1),\n", + " hoverinfo=\"text\",\n", + " showlegend=False,\n", + " )\n", + " if row[\"speed\"] == -1000: # Check for dummy speed\n", + " return (\n", + " go.layout.Shape(\n", + " type=\"circle\",\n", + " xref=\"x\",\n", + " yref=\"y\",\n", + " x0=row[\"x\"] - row[\"radius\"],\n", + " y0=row[\"y\"] - row[\"radius\"],\n", + " x1=row[\"x\"] + row[\"radius\"],\n", + " y1=row[\"y\"] + row[\"radius\"],\n", + " line=dict(width=0),\n", + " fillcolor=\"rgba(255,255,255,0)\", # Transparent fill\n", + " ),\n", + " hover_trace,\n", + " )\n", + " color = speed_to_color(row[\"speed\"], min_speed, max_speed, midpoint)\n", + " return (\n", + " go.layout.Shape(\n", + " type=\"circle\",\n", + " xref=\"x\",\n", + " yref=\"y\",\n", + " x0=row[\"x\"] - row[\"radius\"],\n", + " y0=row[\"y\"] - row[\"radius\"],\n", + " x1=row[\"x\"] + row[\"radius\"],\n", + " y1=row[\"y\"] + row[\"radius\"],\n", + " line_color=color,\n", + " fillcolor=color,\n", + " ),\n", + " hover_trace,\n", + " )\n", + "\n", + " results = frame_data.apply(create_shape, axis=1).tolist()\n", + " shapes = [res[0] for res in results]\n", + " hover_traces = [res[1] for res in results]\n", + " return shapes, hover_traces\n", + "\n", + "\n", + "def create_fig(\n", + " initial_agent_count,\n", + " initial_shapes,\n", + " initial_hover_trace,\n", + " geometry_traces,\n", + " hover_traces,\n", + " frames,\n", + " steps,\n", + " area_bounds,\n", + " width=800,\n", + " height=800,\n", + "):\n", + " minx, miny, maxx, maxy = area_bounds\n", + " fig = go.Figure(\n", + " data=geometry_traces + hover_traces + initial_hover_trace,\n", + " frames=frames,\n", + " layout=go.Layout(\n", + " shapes=initial_shapes,\n", + " title=f\"Number of Agents: {initial_agent_count}\",\n", + " title_x=0.5,\n", + " ),\n", + " )\n", + " fig.update_layout(\n", + " updatemenus=[\n", + " {\n", + " \"buttons\": [\n", + " {\n", + " \"args\": [\n", + " None,\n", + " {\n", + " \"frame\": {\"duration\": 100, \"redraw\": True},\n", + " \"fromcurrent\": True,\n", + " },\n", + " ],\n", + " \"label\": \"Play\",\n", + " \"method\": \"animate\",\n", + " }\n", + " ],\n", + " \"direction\": \"left\",\n", + " \"pad\": {\"r\": 10, \"t\": 87},\n", + " \"showactive\": False,\n", + " \"type\": \"buttons\",\n", + " \"x\": 0.1,\n", + " \"xanchor\": \"right\",\n", + " \"y\": 0,\n", + " \"yanchor\": \"top\",\n", + " }\n", + " ],\n", + " sliders=[\n", + " {\n", + " \"active\": 0,\n", + " \"yanchor\": \"top\",\n", + " \"xanchor\": \"left\",\n", + " \"currentvalue\": {\n", + " \"font\": {\"size\": 20},\n", + " \"prefix\": \"Frame:\",\n", + " \"visible\": True,\n", + " \"xanchor\": \"right\",\n", + " },\n", + " \"transition\": {\"duration\": 100, \"easing\": \"cubic-in-out\"},\n", + " \"pad\": {\"b\": 10, \"t\": 50},\n", + " \"len\": 0.9,\n", + " \"x\": 0.1,\n", + " \"y\": 0,\n", + " \"steps\": steps,\n", + " }\n", + " ],\n", + " autosize=False,\n", + " width=width,\n", + " height=height,\n", + " xaxis=dict(range=[minx - 0.5, maxx + 0.5]),\n", + " yaxis=dict(\n", + " scaleanchor=\"x\", scaleratio=1, range=[miny - 0.5, maxy + 0.5]\n", + " ),\n", + " )\n", + " return fig\n", + "\n", + "\n", + "def animate(\n", + " data: pedpy.TrajectoryData, area: pedpy.WalkableArea, *, every_nth_frame=5\n", + "):\n", + " data_df = pedpy.compute_individual_speed(traj_data=data, frame_step=5)\n", + " data_df = data_df.merge(data.data, on=[\"id\", \"frame\"], how=\"left\")\n", + " data_df[\"radius\"] = 0.2\n", + " min_speed = data_df[\"speed\"].min()\n", + " max_speed = data_df[\"speed\"].max()\n", + " midpoint = np.mean(data_df[\"speed\"])\n", + " max_agents = data_df.groupby(\"frame\").size().max()\n", + " dummy_agent_data = {\"x\": 0, \"y\": 0, \"radius\": 0, \"speed\": -1000}\n", + " frames = []\n", + " steps = []\n", + " unique_frames = data_df[\"frame\"].unique()\n", + " selected_frames = unique_frames[::every_nth_frame]\n", + " geometry_traces = get_geometry_traces(area.polygon)\n", + " initial_frame_data = data_df[data_df[\"frame\"] == data_df[\"frame\"].min()]\n", + " initial_agent_count = len(initial_frame_data)\n", + " initial_shapes, initial_hovers = get_shapes_for_frame(\n", + " initial_frame_data, min_speed, max_speed, midpoint\n", + " )\n", + " for frame_num in selected_frames[1:]:\n", + " frame_data = data_df[data_df[\"frame\"] == frame_num]\n", + " agent_count = len(frame_data)\n", + " while len(frame_data) < max_agents:\n", + " dummy_df = pd.DataFrame([dummy_agent_data])\n", + " frame_data = pd.concat([frame_data, dummy_df], ignore_index=True)\n", + "\n", + " shapes, hover_traces = get_shapes_for_frame(\n", + " frame_data, min_speed, max_speed, midpoint\n", + " )\n", + " frame = go.Frame(\n", + " data=geometry_traces + hover_traces,\n", + " name=str(frame_num),\n", + " layout=go.Layout(\n", + " shapes=shapes,\n", + " title=f\"Number of Agents: {agent_count}\",\n", + " title_x=0.5,\n", + " ),\n", + " )\n", + " frames.append(frame)\n", + " step = {\n", + " \"args\": [\n", + " [str(frame_num)],\n", + " {\n", + " \"frame\": {\"duration\": 100, \"redraw\": True},\n", + " \"mode\": \"immediate\",\n", + " \"transition\": {\"duration\": 500},\n", + " },\n", + " ],\n", + " \"label\": str(frame_num),\n", + " \"method\": \"animate\",\n", + " }\n", + " steps.append(step)\n", + "\n", + " return create_fig(\n", + " initial_agent_count,\n", + " initial_shapes,\n", + " initial_hovers,\n", + " geometry_traces,\n", + " hover_traces,\n", + " frames,\n", + " steps,\n", + " area.bounds,\n", + " width=800,\n", + " height=800,\n", + " )" + ] + }, { "cell_type": "code", "execution_count": null, @@ -272,8 +596,6 @@ }, "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)" ] @@ -303,9 +625,9 @@ "source": [ "## References & Further Exploration\n", "\n", - "**TODO RiMEA reference**\n", + "[1] RiMEA, 'Guideline for Microscopic Evacuation Analysis'(2016), URL: https://rimea.de/ \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)**." ]