{ "cells": [ { "cell_type": "code", "execution_count": 1, "id": "a944b13a-eed4-4325-906e-4de5cbf00529", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Defaulting to user installation because normal site-packages is not writeable\n", "Requirement already satisfied: numpy in ./.local/lib/python3.9/site-packages (1.26.0)\n", "Requirement already satisfied: pandas in ./.local/lib/python3.9/site-packages (2.1.1)\n", "Requirement already satisfied: matplotlib in ./.local/lib/python3.9/site-packages (3.8.0)\n", "Requirement already satisfied: seaborn in ./.local/lib/python3.9/site-packages (0.13.0)\n", "Requirement already satisfied: Pillow in ./.local/lib/python3.9/site-packages (10.0.1)\n", "Requirement already satisfied: ipywidgets in ./.local/lib/python3.9/site-packages (8.1.1)\n", "Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.9/site-packages (from pandas) (2022.2.1)\n", "Requirement already satisfied: python-dateutil>=2.8.2 in /usr/local/lib/python3.9/site-packages (from pandas) (2.8.2)\n", "Requirement already satisfied: tzdata>=2022.1 in ./.local/lib/python3.9/site-packages (from pandas) (2023.3)\n", "Requirement already satisfied: cycler>=0.10 in ./.local/lib/python3.9/site-packages (from matplotlib) (0.12.0)\n", "Requirement already satisfied: kiwisolver>=1.0.1 in ./.local/lib/python3.9/site-packages (from matplotlib) (1.4.5)\n", "Requirement already satisfied: pyparsing>=2.3.1 in /usr/local/lib/python3.9/site-packages (from matplotlib) (3.0.9)\n", "Requirement already satisfied: contourpy>=1.0.1 in ./.local/lib/python3.9/site-packages (from matplotlib) (1.1.1)\n", "Requirement already satisfied: fonttools>=4.22.0 in ./.local/lib/python3.9/site-packages (from matplotlib) (4.43.0)\n", "Requirement already satisfied: packaging>=20.0 in /usr/local/lib/python3.9/site-packages (from matplotlib) (21.3)\n", "Requirement already satisfied: importlib-resources>=3.2.0 in ./.local/lib/python3.9/site-packages (from matplotlib) (6.1.0)\n", "Requirement already satisfied: ipython>=6.1.0 in /usr/local/lib/python3.9/site-packages (from ipywidgets) (8.5.0)\n", "Requirement already satisfied: widgetsnbextension~=4.0.9 in ./.local/lib/python3.9/site-packages (from ipywidgets) (4.0.9)\n", "Requirement already satisfied: traitlets>=4.3.1 in /usr/local/lib/python3.9/site-packages (from ipywidgets) (5.3.0)\n", "Requirement already satisfied: jupyterlab-widgets~=3.0.9 in ./.local/lib/python3.9/site-packages (from ipywidgets) (3.0.9)\n", "Requirement already satisfied: comm>=0.1.3 in ./.local/lib/python3.9/site-packages (from ipywidgets) (0.1.4)\n", "Requirement already satisfied: zipp>=3.1.0 in /usr/local/lib/python3.9/site-packages (from importlib-resources>=3.2.0->matplotlib) (3.8.1)\n", "Requirement already satisfied: backcall in /usr/local/lib/python3.9/site-packages (from ipython>=6.1.0->ipywidgets) (0.2.0)\n", "Requirement already satisfied: decorator in /usr/local/lib/python3.9/site-packages (from ipython>=6.1.0->ipywidgets) (5.1.1)\n", "Requirement already satisfied: jedi>=0.16 in /usr/local/lib/python3.9/site-packages (from ipython>=6.1.0->ipywidgets) (0.18.1)\n", "Requirement already satisfied: pickleshare in /usr/local/lib/python3.9/site-packages (from ipython>=6.1.0->ipywidgets) (0.7.5)\n", "Requirement already satisfied: pexpect>4.3 in /usr/local/lib/python3.9/site-packages (from ipython>=6.1.0->ipywidgets) (4.8.0)\n", "Requirement already satisfied: prompt-toolkit<3.1.0,>3.0.1 in /usr/local/lib/python3.9/site-packages (from ipython>=6.1.0->ipywidgets) (3.0.31)\n", "Requirement already satisfied: pygments>=2.4.0 in /usr/local/lib/python3.9/site-packages (from ipython>=6.1.0->ipywidgets) (2.13.0)\n", "Requirement already satisfied: stack-data in /usr/local/lib/python3.9/site-packages (from ipython>=6.1.0->ipywidgets) (0.5.0)\n", "Requirement already satisfied: matplotlib-inline in /usr/local/lib/python3.9/site-packages (from ipython>=6.1.0->ipywidgets) (0.1.6)\n", "Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.9/site-packages (from python-dateutil>=2.8.2->pandas) (1.16.0)\n", "Requirement already satisfied: parso<0.9.0,>=0.8.0 in /usr/local/lib/python3.9/site-packages (from jedi>=0.16->ipython>=6.1.0->ipywidgets) (0.8.3)\n", "Requirement already satisfied: ptyprocess>=0.5 in /usr/local/lib/python3.9/site-packages (from pexpect>4.3->ipython>=6.1.0->ipywidgets) (0.7.0)\n", "Requirement already satisfied: wcwidth in /usr/local/lib/python3.9/site-packages (from prompt-toolkit<3.1.0,>3.0.1->ipython>=6.1.0->ipywidgets) (0.2.5)\n", "Requirement already satisfied: pure-eval in /usr/local/lib/python3.9/site-packages (from stack-data->ipython>=6.1.0->ipywidgets) (0.2.2)\n", "Requirement already satisfied: asttokens in /usr/local/lib/python3.9/site-packages (from stack-data->ipython>=6.1.0->ipywidgets) (2.0.8)\n", "Requirement already satisfied: executing in /usr/local/lib/python3.9/site-packages (from stack-data->ipython>=6.1.0->ipywidgets) (1.0.0)\n", "\u001b[33mWARNING: You are using pip version 22.0.4; however, version 23.2.1 is available.\n", "You should consider upgrading via the '/usr/local/bin/python -m pip install --upgrade pip' command.\u001b[0m\u001b[33m\n", "\u001b[0mNote: you may need to restart the kernel to use updated packages.\n" ] } ], "source": [ "# install the neccessary software\n", "%pip install numpy pandas matplotlib seaborn Pillow ipywidgets" ] }, { "cell_type": "code", "execution_count": 2, "id": "e68f7f84-3887-4464-99e2-9c759a4d778d", "metadata": {}, "outputs": [], "source": [ "#You don't need to change anything in this block, although the modules need to be installed to run this notebook\n", "\n", "#We import numpy to handle vectors and some math\n", "import numpy as np\n", "\n", "#We import pandas to create a data frame of the experiment data\n", "#Such a table can later be used for plotting our results\n", "import pandas as pd\n", "\n", "# Import plotly, which is used for visualization\n", "import plotly.express as px\n", "import plotly.io as pio\n", "pio.renderers.default = 'iframe'" ] }, { "cell_type": "code", "execution_count": 3, "id": "66e49112-405a-4e42-8346-37209a3eeff9", "metadata": {}, "outputs": [], "source": [ "#You don't need to change anything in this block\n", "\n", "def get_initial_followers_state():\n", " '''Generates the state of follower particles for t0'''\n", " followers_positions = 2 * (np.random.rand(num_followers, 2) - 0.5)\n", " followers_velocities = np.zeros((num_followers, 2))\n", " return followers_positions, followers_velocities\n", "\n", "def get_leader_position(t):\n", " '''Generates the position of the leader based on input time'''\n", " step_size = leader_speed\n", " distance_covered = step_size * t\n", " \n", " circle_radius = 1\n", " circle_circumference = 2 * circle_radius * np.pi\n", " percent_circle_completed = distance_covered / circle_circumference\n", " radians = percent_circle_completed * 2 * np.pi\n", " \n", " leader_x = np.sin(radians)\n", " leader_y = np.cos(radians)\n", " return np.array([leader_x, leader_y])\n", "\n", "def magnitude(vector):\n", " '''Returns the magnitude of a vector'''\n", " return np.linalg.norm(vector)\n", "\n", "def distance_between(position, goal):\n", " '''Returns the distance between two points'''\n", " return magnitude(goal - position)\n", "\n", "def vector_towards(position, goal):\n", " '''Returns the vector that points from the position to the goal'''\n", " return goal - position\n", "\n", "def direction_towards(position, goal):\n", " '''Returns a vector pointing from the position towards the goal with magnitude 1'''\n", " return (goal - position) / magnitude(goal - position)\n", "\n", "def to_dataframe(t, followers_positions, leader_position):\n", " '''Converts the particle state at one timestep to a dataframe'''\n", " df = pd.DataFrame()\n", " df['t'] = np.repeat(t, num_followers + 1)\n", " df['type'] = np.append(np.repeat('Follower', num_followers), 'Leader')\n", " df['x'] = np.append(followers_positions[:, 0], leader_position[0])\n", " df['y'] = np.append(followers_positions[:, 1], leader_position[1])\n", " df['particle_id'] = list( range( num_followers + 1) )\n", " return df" ] }, { "cell_type": "code", "execution_count": 4, "id": "13963669-d1ce-4d3d-8aa3-17e17fe3df9e", "metadata": {}, "outputs": [], "source": [ "#Here we define the possible force functions (attraction / repulsion)\n", "#You can play around with the parameters of the functions or create your own functions\n", " \n", "def force_random(followers_positions, leader_position, d=0.7, k_followers=0.01, k_leader=0.5): \n", " '''Force function with linear attraction and distance proportional, random offset'''\n", " force = np.zeros((num_followers, 2))\n", " \n", " for i in range(num_followers):\n", " vector_to_leader = vector_towards(followers_positions[i], leader_position)\n", " force[i] += k_leader * (d - np.random.rand(2)) * vector_to_leader\n", " \n", " for j in range(num_followers):\n", " if i == j:\n", " continue\n", " vector_to_follower_j = vector_towards(followers_positions[i], followers_positions[j])\n", " force[i] += k_followers * (d - np.random.rand(2)) * vector_to_follower_j\n", " return force\n", "\n", "def force_comfortable_distance(followers_positions, leader_position, d=0.5, k_followers=0.1, k_leader=0.5): \n", " '''Force function to keep a comfortable distance with linear attraction and repulsion'''\n", " force = np.zeros((num_followers, 2))\n", " \n", " for i in range(num_followers):\n", " distance_to_leader = distance_between(followers_positions[i], leader_position)\n", " direction_to_leader = direction_towards(followers_positions[i], leader_position)\n", " leader_force = k_leader * (distance_to_leader - d) * direction_to_leader\n", " \n", " cohesion_force = 0\n", " for j in range(num_followers):\n", " if i == j:\n", " continue\n", " distance_to_follower_j = distance_between(followers_positions[i], followers_positions[j])\n", " direction_to_follower_j = direction_towards(followers_positions[i], followers_positions[j])\n", " cohesion_force += k_followers * (distance_to_follower_j - d) * direction_to_follower_j\n", " \n", " force[i] = leader_force + cohesion_force\n", " return force" ] }, { "cell_type": "code", "execution_count": 5, "id": "46efc782-52ca-4acd-aa3d-6326609d992b", "metadata": {}, "outputs": [], "source": [ "# You have two modify this for the task\n", "\n", "def force_function3(follower_positions, leader_position, a=0.1, b=0.1, c=0.1):\n", " return np.zeros_like(follower_positions)\n" ] }, { "cell_type": "code", "execution_count": 6, "id": "c73b5de4-dbe6-4cd3-b5f8-97c2f04bfe03", "metadata": {}, "outputs": [], "source": [ "#You don't need to change anything in this block\n", "\n", "def update(followers_positions, followers_velocities, leader_position):\n", " '''Calculates new positions and velocities based on the state given as input'''\n", " new_velocities = inertia * followers_velocities + force_function(followers_positions, leader_position)\n", " new_positions = followers_positions + new_velocities\n", " return new_positions, new_velocities\n", "\n", "def run():\n", " '''Iterates over all time steps, updates the particle states, and writes each state to a dataframe'''\n", " data = []\n", " leader_position = get_leader_position(0)\n", " followers_positions, followers_velocities = get_initial_followers_state()\n", " data.append(to_dataframe(0, followers_positions, leader_position))\n", " \n", " for t in range(1, num_time_steps, 1):\n", " leader_position = get_leader_position(t)\n", " followers_positions, followers_velocities = update(followers_positions, followers_velocities, leader_position)\n", " data.append(to_dataframe(t, followers_positions, leader_position))\n", " \n", " return pd.concat(data)" ] }, { "cell_type": "code", "execution_count": 7, "id": "6c3fee9e-04fa-408c-b336-8a18a7bbd1d5", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
ttypexyparticle_id
00Follower0.0497780.6482590
10Follower0.3962590.1574711
20Follower-0.603413-0.6568862
30Follower-0.568764-0.9038613
40Follower-0.713796-0.2344864
\n", "
" ], "text/plain": [ " t type x y particle_id\n", "0 0 Follower 0.049778 0.648259 0\n", "1 0 Follower 0.396259 0.157471 1\n", "2 0 Follower -0.603413 -0.656886 2\n", "3 0 Follower -0.568764 -0.903861 3\n", "4 0 Follower -0.713796 -0.234486 4" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "#Here we define some global parameters used in the above functions\n", "#You can change these and observe how the behavior changes\n", "\n", "num_followers = 10\n", "leader_speed = 0.1\n", "num_time_steps = 100\n", "inertia = 0.0\n", "force_function = force_random\n", "\n", "df = run() #Generate the dataframe for plotting\n", "df.head() # display the first five rows of the dataframe" ] }, { "cell_type": "code", "execution_count": 9, "id": "b42d5b73-88f1-44d1-addb-e45ca092c314", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# visualize the data from the dataframe df\n", "# adjust width and hight according to your screen resolution\n", "force_function = force_random\n", "\n", "df = run() \n", "fig = px.scatter(df, x=\"x\", y=\"y\", color='type', symbol=\"type\", animation_frame='t', animation_group='particle_id', width=800, height=800)\n", "fig.update_layout(xaxis_range=(-2.0, 2.0), yaxis_range=(-2.0, 2.0))\n", "fig.show()" ] }, { "cell_type": "code", "execution_count": 10, "id": "5a84ed10", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# visualize the data from the dataframe df\n", "# adjust width and hight according to your screen resolution\n", "force_function = force_comfortable_distance\n", "\n", "df = run()\n", "fig = px.scatter(df, x=\"x\", y=\"y\", color='type', symbol=\"type\", animation_frame='t', animation_group='particle_id', width=800, height=800)\n", "fig.update_layout(xaxis_range=(-2.0, 2.0), yaxis_range=(-2.0, 2.0))\n", "fig.show()" ] }, { "cell_type": "code", "execution_count": 11, "id": "09921a6a-5797-4b41-ad45-99d82a4cb2b0", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# visualize the data from the dataframe df\n", "# adjust width and hight according to your screen resolution\n", "force_function = force_function3\n", "\n", "df = run() \n", "fig = px.scatter(df, x=\"x\", y=\"y\", color='type', symbol=\"type\", animation_frame='t', animation_group='particle_id', width=800, height=800)\n", "fig.update_layout(xaxis_range=(-2.0, 2.0), yaxis_range=(-2.0, 2.0))\n", "fig.show()" ] }, { "cell_type": "code", "execution_count": null, "id": "0cc2abd3-decf-4681-8f98-2cff0f207274", "metadata": {}, "outputs": [], "source": [] } ], "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.9.14" } }, "nbformat": 4, "nbformat_minor": 5 }