diff --git a/notebooks/direct_steering.ipynb b/notebooks/direct_steering.ipynb new file mode 100644 index 000000000..19b02eefc --- /dev/null +++ b/notebooks/direct_steering.ipynb @@ -0,0 +1,411 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "4aee4651", + "metadata": {}, + "source": [ + "# Direct steering\n", + "\n", + "This notebook can be directly downloaded {download}`here <./direct_steering.ipynb>` to run it locally.\n", + "\n", + "It demonstrates the use of [direct steering](https://www.jupedsim.org/stable/concepts/routing.html#direct-steering) of agents.\n", + "\n", + "An agent (leader) embarks on a journey defined by specific waypoints and a final destination. Meanwhile, the remaining agents trail behind, constantly adjusting their course to align with the leader's current position." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a6d4f847-92e0-4905-b52b-a9e61aa2355f", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "import pathlib\n", + "import random\n", + "\n", + "import jupedsim as jps\n", + "import pedpy\n", + "from matplotlib.patches import Circle\n", + "from shapely import GeometryCollection, Point, Polygon" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d3087364-73e8-4b35-8a5e-2222df89f1fe", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "area = GeometryCollection(Polygon([(0, 0), (28, 0), (28, 10), (0, 10)]))\n", + "walkable_area = pedpy.WalkableArea(area.geoms[0])" + ] + }, + { + "cell_type": "markdown", + "id": "c9f63836-0f81-4684-b99e-abdf664c9d53", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "## Definition of Start Positions and Exit\n", + "\n", + "Now we define the spawning area and way points for the leader to follow." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5dec36fb-55f2-4225-aac7-32be899c9254", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "num_agents = 5\n", + "spawning_area = Polygon([(0, 0), (2, 0), (2, 10), (0, 10)])\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.8,\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)])\n", + "waypoints = [\n", + " (8, 8),\n", + " (8, 2),\n", + " (4, 2),\n", + " (4, 8),\n", + " (18, 2),\n", + " (18, 8),\n", + " (23, 2),\n", + " (23, 8),\n", + "]\n", + "distance_to_waypoints = 0.5" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "220d8141-614e-464d-9dcc-fc5ea8d1a6af", + "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(*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", + " for idx, waypoint in enumerate(waypoints):\n", + " axes.plot(waypoint[0], waypoint[1], \"ro\")\n", + " axes.annotate(\n", + " f\"WP {idx+1}\",\n", + " (waypoint[0], waypoint[1]),\n", + " textcoords=\"offset points\",\n", + " xytext=(10, -15),\n", + " ha=\"center\",\n", + " )\n", + " circle = Circle(\n", + " (waypoint[0], waypoint[1]),\n", + " distance_to_waypoints,\n", + " fc=\"red\",\n", + " ec=\"red\",\n", + " alpha=0.1,\n", + " )\n", + " axes.add_patch(circle)\n", + "\n", + "\n", + "plot_simulation_configuration(\n", + " walkable_area, spawning_area, pos_in_spawning_area, exit_area\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "2b990c43-7c81-4f19-92d6-97280974c300", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "## Specification of Parameters und Running the Simulation\n", + "\n", + "Now we just need to define the details of the operational model as well as the exit." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6a9b725e-cf77-4589-9023-3105665684ea", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "trajectory_file = \"output.sqlite\" # output file\n", + "simulation = jps.Simulation(\n", + " model=jps.GeneralizedCentrifugalForceModel(\n", + " max_neighbor_repulsion_force=10,\n", + " max_neighbor_interaction_distance=2,\n", + " max_neighbor_interpolation_distance=0.1,\n", + " strength_neighbor_repulsion=0.3,\n", + " max_geometry_repulsion_force=3,\n", + " ),\n", + " geometry=area,\n", + " trajectory_writer=jps.SqliteTrajectoryWriter(\n", + " output_file=pathlib.Path(trajectory_file)\n", + " ),\n", + ")\n", + "exit_id = simulation.add_exit_stage(exit_area.exterior.coords[:-1])" + ] + }, + { + "cell_type": "markdown", + "id": "015995f0", + "metadata": {}, + "source": [ + "## Define Journey for leader" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "17f4ba2f", + "metadata": {}, + "outputs": [], + "source": [ + "waypoint_ids = [\n", + " simulation.add_waypoint_stage(waypoint, distance_to_waypoints)\n", + " for waypoint in waypoints\n", + "]\n", + "journey_leader = jps.JourneyDescription([*waypoint_ids, exit_id])\n", + "for i, waypoint_id in enumerate(waypoint_ids):\n", + " journey_leader.set_transition_for_stage(\n", + " waypoint_id,\n", + " jps.Transition.create_fixed_transition(\n", + " waypoint_ids[i + 1] if i + 1 < len(waypoint_ids) else exit_id\n", + " ),\n", + " )\n", + "journey_id = simulation.add_journey(journey_leader)" + ] + }, + { + "cell_type": "markdown", + "id": "61413b86", + "metadata": {}, + "source": [ + "## Define Journey for followers" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1a65b40e", + "metadata": {}, + "outputs": [], + "source": [ + "direct_steering_stage = simulation.add_direct_steering_stage()\n", + "direct_steering_journey = jps.JourneyDescription([direct_steering_stage])\n", + "direct_steering_journey_id = simulation.add_journey(direct_steering_journey)" + ] + }, + { + "cell_type": "markdown", + "id": "0d643d8f", + "metadata": {}, + "source": [ + "## Add agents\n", + "\n", + "First, add leader, then its followers." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "86acd974", + "metadata": {}, + "outputs": [], + "source": [ + "leader_id = simulation.add_agent(\n", + " jps.GeneralizedCentrifugalForceModelAgentParameters(\n", + " journey_id=journey_id,\n", + " stage_id=waypoint_ids[0],\n", + " position=pos_in_spawning_area[0],\n", + " v0=1.0,\n", + " b_min=0.1,\n", + " b_max=0.2,\n", + " a_min=0.1,\n", + " a_v=0.2,\n", + " orientation=(1, 0),\n", + " )\n", + ")\n", + "# Followers\n", + "followers_ids = set(\n", + " [\n", + " simulation.add_agent(\n", + " jps.GeneralizedCentrifugalForceModelAgentParameters(\n", + " journey_id=direct_steering_journey_id,\n", + " stage_id=direct_steering_stage,\n", + " position=pos,\n", + " v0=0.8,\n", + " b_min=0.1,\n", + " b_max=0.2,\n", + " a_min=0.1,\n", + " a_v=0.2,\n", + " orientation=(1, 0),\n", + " )\n", + " )\n", + " for pos in pos_in_spawning_area[1:]\n", + " ]\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "18018147", + "metadata": {}, + "source": [ + "## Simulation loop" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "335348ab", + "metadata": {}, + "outputs": [], + "source": [ + "while simulation.agent_count() > 0:\n", + " # Find leader's position\n", + " if leader_id in simulation.removed_agents():\n", + " leader_id = None\n", + " if leader_id:\n", + " position_leader = simulation.agent(leader_id).position\n", + "\n", + " # Move followers towards leader\n", + " for agent in simulation.agents():\n", + " if agent.id == leader_id:\n", + " continue\n", + " # Define a target position near the leader with some randomness\n", + " near_leader = (\n", + " position_leader[0] + random.normalvariate(1, 0.1),\n", + " position_leader[1] + random.normalvariate(1, 0.1),\n", + " )\n", + " near_leader_point = Point(near_leader[0], near_leader[1])\n", + "\n", + " # If the target position is inside the walkable area, set it as the agent's target\n", + " target = (\n", + " near_leader\n", + " if any(geom.contains(near_leader_point) for geom in area.geoms)\n", + " else position_leader\n", + " )\n", + " agent.target = target\n", + "\n", + " # Check if the agent reached the exit and mark it for removal if so\n", + " if Point(agent.position).distance(exit_area.centroid) < 1:\n", + " simulation.mark_agent_for_removal(agent.id)\n", + "\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, every_nth_frame=10)" + ] + } + ], + "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.10.14" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}