{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "cf10618f",
   "metadata": {},
   "source": [
    "# Run experiments with TraCi\n",
    "\n",
    "In the previous tutorials, we learned how to run simulations with custom networks, and custom parameters. However, when a simulation is launched, it's not possible to interact with SUMO __during__ the execution. To overcome this problem, SUMO integrates a library named TraCi, allowing you to control simulations, simulation step by simulation step. At each step, it is therefore possible to modify the behavior of the infrastructures, for example by adapting them to the traffic, or to gather data for each simulation step.\n",
    "\n",
    "In this tutorial, we'll learn how to use TraCi with sumo-experiments."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3d802d20",
   "metadata": {},
   "outputs": [],
   "source": [
    "from sumo_experiments import Experiment\n",
    "from sumo_experiments.preset_networks import OneCrossroadNetwork\n",
    "from sumo_experiments.strategies import MaxPressureStrategy\n",
    "from sumo_experiments.traci_util import TraciWrapper"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cd9bd397",
   "metadata": {},
   "source": [
    "## The TraCiWrapper\n",
    "\n",
    "The `run_traci` function from Experiment can only accept one TraCi function. As we said previously, TraCi functions are meant to change infrastructures behaviour, and to collect data. This can be a lot for a unique function, and it can be useful to split this work in multiple functions. That's why the TraciWrapper was created. The aim of this class is to aggregate TraCi functions to make only one. In this class, TraCi functions are divided in two types :\n",
    "- The behavioural functions are functions that modify the behaviour of the network. They return a new config dict, modified or not, to the TraCiWrapper that will use it for the next simulaton step.\n",
    "- The stats functions are functions that gather data from the network. They return a dict where the key in the name of the data, and where the value is the data. The data can't be an iterable object. The idea of this implementation is to collect a dict with the same data names for each simulation step and to build a Pandas DataFrame a the end of the simulation.\n",
    "\n",
    "Thus, there are two functions to add TraCi functions to the wrapper : `add_stats_function` to add a stats function, and `add_behavioural_function` to add a behavioural function. The aggregated function is named `final_function`.\n",
    "\n",
    "***Note :** The TraciWrapper final function first executes the stats functions, and then the behavioural functions. It executes functions of each group __in the order in which they have been added__.*"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3c0fd3fc",
   "metadata": {},
   "source": [
    "### Behavioral functions\n",
    "\n",
    "Behavioral functions are functions that directly modify the behavior of the network during a simulation. A behavioral function must work for **one and only one simulation step**, the TraciWrapper passes the simulation steps itself."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9e6030c0",
   "metadata": {},
   "source": [
    "#### Strategies\n",
    "\n",
    "Sumo-experiments has a specific package to implement strategies to manage the network. In a road network, each intersection can be considered as an agent, defined by a behavior to manage the intersection. With the expansion of the use of sensors, the agents can now have an intelligent behavior, using the collected data to select a phase for the traffic lights. The strategies implemented in sumo-experiments are a selection of the strategies produced in different scientific articles. For now, 8 strategies have been implemented, but we project to had a lot more in the future.\n",
    "\n",
    "To use a strategy, you have to instantiate it and add it to the traci wrapper, as a behavorial function. Only one strategy can be add to a traci wrapper, as multiple strategies will conflict and will lead to an unexpected behavior. Here is an example, where we manage a single intersection with a Max Pressure \\[1\\] strategy.\n",
    "\n",
    "\\[1\\] Varaiya, P. (2013). Max pressure control of a network of signalized intersections. Transportation Research Part C: Emerging Technologies, 36, 177-195."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3e5092fb",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Create the network\n",
    "network = OneCrossroadNetwork()\n",
    "\n",
    "# Create the infrastructures\n",
    "infrastructures = network.generate_infrastructures(\n",
    "                    lane_length=200,\n",
    "                    green_time=30, # Green time will here be overwriten by the Max Pressure strategy\n",
    "                    yellow_time=3, # Yellow time also\n",
    "                    max_speed=50)\n",
    "\n",
    "# Create the flows\n",
    "flows = network.generate_flows_all_directions(\n",
    "            stop_generation_time=300,\n",
    "            flow_frequency=500,\n",
    "            distribution='binomial')\n",
    "\n",
    "# Create the detectors\n",
    "detectors = network.generate_all_detectors(\n",
    "                boolean_detector_length=20)\n",
    "\n",
    "# Parameters for the Max Pressure strategy\n",
    "# Parameters are defined as a dict, where the key is the id of the intersection. \n",
    "# (Except for certain parameters that are defined for all the intersections) \n",
    "periods = {'c': 30}\n",
    "counted_vehicles = 'all'\n",
    "yellow_times = {'c': 3}\n",
    "\n",
    "# Create the strategy\n",
    "strategy = MaxPressureStrategy(infrastructures, detectors, periods, counted_vehicles, yellow_times)\n",
    "\n",
    "# Create the Traci Wrapper\n",
    "tw = TraciWrapper(\n",
    "        simulation_duration=360,\n",
    "        data_frequency=1) # Frequency, in simulation steps, of data gathering.\n",
    "tw.add_behavioural_function(strategy.run_all_agents)\n",
    "\n",
    "# Create the experiment\n",
    "exp = Experiment(\n",
    "        name='exp_traci',\n",
    "        infrastructures=infrastructures,\n",
    "        flows=flows,\n",
    "        detectors=detectors)\n",
    "\n",
    "# Run the experiment with traci. We use the run_traci function, with the final function of the Traci Wrapper.\n",
    "# The run_traci function returns the data collected by the Traci Wrapper.\n",
    "data = exp.run_traci(\n",
    "    traci_function=tw.final_function,\n",
    "    gui=True)\n",
    "\n",
    "exp.clean_files()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "30bc76cf",
   "metadata": {},
   "source": [
    "## TraCi stats functions\n",
    "\n",
    "A stats function is a function that gather data from the simulation, with the frequency defined with the Traci Wrapper. By default, the Traci Wrapper collects data : the simulation step, the mean travel time, the number of exiting vehicles and the mean CO2 emissions per travel. Moreover, sumo-experiments implements stats functions that can be used for any network. This functions iterate over all vehicles or all lanes to get data from them. They can aggregate this data differently (sum, mean, max, min, etc) according to type of function you choose. All of this functions can be imported from the `traci_util` package. Let's use the same example as previously."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4f1ac35e",
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "from sumo_experiments.traci_util import get_speed_data, get_co2_emissions_data, get_fuel_consumption_data\n",
    "\n",
    "tw = TraciWrapper(\n",
    "        simulation_duration=360,\n",
    "        data_frequency=1) # Frequency, in simulation steps, of data gathering.\n",
    "\n",
    "tw.add_behavioural_function(strategy.run_all_agents)\n",
    "tw.add_stats_function(get_speed_data)\n",
    "tw.add_stats_function(get_co2_emissions_data)\n",
    "tw.add_stats_function(get_fuel_consumption_data)\n",
    "\n",
    "data = exp.run_traci(\n",
    "    traci_function=tw.final_function,\n",
    "    gui=True)\n",
    "\n",
    "exp.clean_files()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2f7aa4b0",
   "metadata": {},
   "source": [
    "The data from the simulation are returned by the `run_traci` function, in a DataFrame object."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "920a45c7",
   "metadata": {},
   "outputs": [],
   "source": [
    "data"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c3269bd0",
   "metadata": {},
   "source": [
    "***Note :** The use of TraCi drastically decrease SUMO performances. We chose this way to collect data because of the size of self generated data files. Some files can weigh multiple GigaBytes, and are not in a table format. With our implementation, we can choose the data we want from the simulation, and they are returned directly in a table format, that can be plotted or stored easily.*"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d029575b",
   "metadata": {},
   "source": [
    "## Custom functions\n",
    "\n",
    "You can create your own TraCi functions, stats or behavioural, for any SUMO network. We won't describe in this tutorial how to use the TraCi library from python, we highly recommand you to check the [documentation](https://sumo.dlr.de/docs/TraCI/Interfacing_TraCI_from_Python.html). Just remember that a function used by the TraCiWrapper must work for one and only one simulation step. Obviously, you can also write functions that will overpass the TraCiWrapper, and in this case, they must work for the entire simulation.\n",
    "\n",
    "**This is the end of this tutorial. If you have any question or recommandation, you can ask anytime at *jules.bompard.etu@univ-lille.fr*.**"
   ]
  }
 ],
 "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.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
