{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "47f74868",
   "metadata": {},
   "source": [
    "# Custom networks\n",
    "\n",
    "In previous tutorials, we used the pre-set networks offered by the library. These networks are practical because they are ready to be configured. However, you may need to develop other forms of network than those available. That's why sumo-experiments offers a `components` subpackage that lets you create networks yourself (and with which the pre-set networks are designed).\n",
    "\n",
    "To create an experiment, you need at least one function returning a `FlowBuilder` object and another function returning an `InfrastructureBuilder` object. These two objects are located in the `components` package.\n",
    "\n",
    "In this example, we will create a network with a triangle shape. Each angle of the triangle will be connected to an edge that will serve as an entry and an exit for the vehicles.\n",
    "\n",
    "\n",
    "## Custom infrastructures\n",
    "\n",
    "First, we need to define a function that return an `InfrastructureBuilder` object. To be complete, a set of infrastructures must contain nodes, edges, edge type, connections and traffic light programs. Each of this element can be added with a method of `InfrastructureBuilder` : `add_node`, `add_edge`, `add_edge_type`, `add_connection` and `add_traffic_light_program`.\n",
    "\n",
    "To define a node, you need its x-coordinate and y-coordinate. There is another parameter `type` that define the type of the node. Leave it empty if the node is a normal node, and fill it with *\"traffic_light\"* if it's a traffic light. In this case, you must add the id of a traffic light program.\n",
    "\n",
    "Then you need to define an edge type. An edge type represent the characteritics of an edge : number of lanes, speed, etc. To define an edge type, you need to define each parameter into a dictionnary and pass it to `add_edge_type` with the parameter `params`. You can find a full list of available parameters [here](https://sumo.dlr.de/docs/SUMO_edge_type_file.html).\n",
    "\n",
    "You can add edges now. An edges is a connection between two nodes, __in one way only__. If you want to create a two-way road between a point *A* and a point *B*, you need to create two edges : *A*->*B* and *B*->*A*. The `edge_type` parameter must be filled with the id of an edge type.\n",
    "\n",
    "A connection is a relationship between two connected edges. If a connection is set between two edges, vehicles can pass from one edge to the other. Be sure that the edges are connected __in the right way__. The first edge must end by a node *A* and the other end must start from this same node. For example, if you have three aligned points *A*, *B* and *C*, and edges connecting *A* to *B* and *B* to *C*, you must connect the edges *A*->*B* and *B*->*C* (and *C*->*B* to *B*->*A* in the other way). If you try to connect *B*->*A* and *B*->*C*, an *interesting behaviour* will occur.\n",
    "\n",
    "A traffic light program is the fixed setting for a traffic light. You __can't__ define one program for multiple traffic lights. The `phases` parameter mus be a list of dictionnary, where each dictionnary represents a different phase of the traffic light. During the simulation, SUMO will iterate over all phases, and repeat it until the end. A phase is composed of two elements : a duration and a traffic light configuration. The duration is set with the key `duration` in the dictionnary, and the configuration with the key `state`. A configuration is set with a string, and must contain the state of traffic light for each possible direction that a vehicle can take at the intersection. You will find more informations about it [here](https://sumo.dlr.de/docs/Simulation/Traffic_Lights.html).\n",
    "\n",
    "Let's see an example with the triangle network that we want to build."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "dfe0689f",
   "metadata": {},
   "outputs": [],
   "source": [
    "from sumo_experiments.components import InfrastructureBuilder\n",
    "\n",
    "def triangle_infrastructures():\n",
    "    \n",
    "    # Instanciating object\n",
    "    infra = InfrastructureBuilder()\n",
    "    \n",
    "    # Adding nodes for the triangle\n",
    "    infra.add_node(id='left_angle', x=0, y=0, type='traffic_light', tl_program='left_tl') # left angle\n",
    "    infra.add_node(id='right_angle', x=100, y=0, type='traffic_light', tl_program='right_tl') # right angle\n",
    "    infra.add_node(id='top_angle', x=50, y=86.6, type='traffic_light', tl_program='top_tl') # top angle\n",
    "    \n",
    "    # Adding nodes for the entry/exit edges\n",
    "    infra.add_node(id='entry_exit_left_angle', x=-100, y=0) # to the left angle\n",
    "    infra.add_node(id='entry_exit_right_angle', x=200, y=0) # to the right angle\n",
    "    infra.add_node(id='entry_exit_top_angle', x=50, y=186.6) # to the top angle\n",
    "    \n",
    "    # Adding edge type\n",
    "    infra.add_edge_type(id='default_edge_type', params={'numLanes': '1', 'speed': 50})\n",
    "    \n",
    "    # Adding edges between nodes / Don't forget to create it in the two ways !\n",
    "        # Between left and right angles\n",
    "    infra.add_edge(id='left_right', from_node='left_angle', to_node='right_angle', edge_type='default_edge_type')\n",
    "    infra.add_edge(id='right_left', from_node='right_angle', to_node='left_angle', edge_type='default_edge_type')\n",
    "        # Between left and top angles\n",
    "    infra.add_edge(id='left_top', from_node='left_angle', to_node='top_angle', edge_type='default_edge_type')\n",
    "    infra.add_edge(id='top_left', from_node='top_angle', to_node='left_angle', edge_type='default_edge_type')\n",
    "        # Between right and top angles\n",
    "    infra.add_edge(id='right_top', from_node='right_angle', to_node='top_angle', edge_type='default_edge_type')\n",
    "    infra.add_edge(id='top_right', from_node='top_angle', to_node='right_angle', edge_type='default_edge_type')\n",
    "        # Between left angle and entry/exit\n",
    "    infra.add_edge(id='left_exit', from_node='left_angle', to_node='entry_exit_left_angle', edge_type='default_edge_type')\n",
    "    infra.add_edge(id='entry_left', from_node='entry_exit_left_angle', to_node='left_angle', edge_type='default_edge_type')\n",
    "        # Between right angle and entry/exit\n",
    "    infra.add_edge(id='right_exit', from_node='right_angle', to_node='entry_exit_right_angle', edge_type='default_edge_type')\n",
    "    infra.add_edge(id='entry_right', from_node='entry_exit_right_angle', to_node='right_angle', edge_type='default_edge_type')\n",
    "        # Between top angle and entry/exit\n",
    "    infra.add_edge(id='top_exit', from_node='top_angle', to_node='entry_exit_top_angle', edge_type='default_edge_type')\n",
    "    infra.add_edge(id='entry_top', from_node='entry_exit_top_angle', to_node='top_angle', edge_type='default_edge_type')\n",
    "    \n",
    "    \n",
    "    # Adding connections\n",
    "        # Between triangle edges, clockwise\n",
    "    infra.add_connection(from_edge='left_top', to_edge='top_right')\n",
    "    infra.add_connection(from_edge='top_right', to_edge='right_left')\n",
    "    infra.add_connection(from_edge='right_left', to_edge='left_top')\n",
    "        # Between triangle edges, counter-clockwise\n",
    "    infra.add_connection(from_edge='top_left', to_edge='left_right')\n",
    "    infra.add_connection(from_edge='left_right', to_edge='right_top')\n",
    "    infra.add_connection(from_edge='right_top', to_edge='top_left')\n",
    "        # Between left entry/exit edges and others\n",
    "    infra.add_connection(from_edge='entry_left', to_edge='left_right')\n",
    "    infra.add_connection(from_edge='entry_left', to_edge='left_top')\n",
    "    infra.add_connection(from_edge='right_left', to_edge='left_exit')\n",
    "    infra.add_connection(from_edge='top_left', to_edge='left_exit')\n",
    "        # Between right entry/exit edges and others\n",
    "    infra.add_connection(from_edge='entry_right', to_edge='right_left')\n",
    "    infra.add_connection(from_edge='entry_right', to_edge='right_top')\n",
    "    infra.add_connection(from_edge='left_right', to_edge='right_exit')\n",
    "    infra.add_connection(from_edge='top_right', to_edge='right_exit')\n",
    "        # Between top entry/exit edges and others\n",
    "    infra.add_connection(from_edge='entry_top', to_edge='top_left')\n",
    "    infra.add_connection(from_edge='entry_top', to_edge='top_right')\n",
    "    infra.add_connection(from_edge='left_top', to_edge='top_exit')\n",
    "    infra.add_connection(from_edge='right_top', to_edge='top_exit')\n",
    "    \n",
    "    \n",
    "    # Adding traffic light program for each traffic light\n",
    "    infra.add_traffic_light_program(id='left_tl',\n",
    "                                      phases=[{'duration': 30, 'state': 'rrGGGG'},\n",
    "                                              {'duration': 3, 'state': 'rryyyy'},\n",
    "                                              {'duration': 30, 'state': 'GGrrrr'},\n",
    "                                              {'duration': 3, 'state': 'yyrrrr'}])\n",
    "    \n",
    "    infra.add_traffic_light_program(id='right_tl',\n",
    "                                      phases=[{'duration': 30, 'state': 'GGGGrr'},\n",
    "                                              {'duration': 3, 'state': 'yyyyrr'},\n",
    "                                              {'duration': 30, 'state': 'rrrrGG'},\n",
    "                                              {'duration': 3, 'state': 'rrrryy'}])\n",
    "    \n",
    "    infra.add_traffic_light_program(id='top_tl',\n",
    "                                      phases=[{'duration': 20, 'state': 'GGrrrr'},\n",
    "                                              {'duration': 3, 'state': 'yyrrrr'},\n",
    "                                              {'duration': 20, 'state': 'rrGGrr'},\n",
    "                                              {'duration': 3, 'state': 'rryyrr'},\n",
    "                                              {'duration': 20, 'state': 'rrrrGG'},\n",
    "                                              {'duration': 3, 'state': 'rrrryy'}])\n",
    "    \n",
    "    return infra"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6674d215",
   "metadata": {},
   "source": [
    "## Custom flows\n",
    "\n",
    "Creating custom flows follows the same principle than the infrastructures. We must create a function that returns a `FlowBuilder` object, from the `component` subpackage.\n",
    "\n",
    "The `FlowBuilder` object has three methods :\n",
    "- The `add_v_type` creates a vehicle type that can be used to generate a flow.\n",
    "- The `add_route` is a deprecated method that was supposed to define a route that will be used to define a flow.\n",
    "- The `add_flow` method generates flows between two edges, with a defined frequency, v_type, beginning, ending and distribution. The distribution can be 'uniform' or 'binomial'. With 'uniform', vehicles will be generated following a Uniform Law, whereas with 'binomial', vehicles will be generated following a Binomial Law."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "2557c2d4",
   "metadata": {},
   "outputs": [],
   "source": [
    "from sumo_experiments.components import FlowBuilder\n",
    "\n",
    "def triangle_flows():\n",
    "    \n",
    "    # Instanciating object\n",
    "    flows = FlowBuilder()\n",
    "    \n",
    "    # Adding v_type\n",
    "    flows.add_v_type(id='car0') # Default car in SUMO.\n",
    "    \n",
    "    # Adding flows from left entry\n",
    "    flows.add_flow(id='f_left_top', \n",
    "                   from_edge='entry_left', \n",
    "                   to_edge='top_exit', \n",
    "                   begin=0, end=1000, \n",
    "                   frequency=300, \n",
    "                   v_type='car0', distribution=\"binomial\")\n",
    "    flows.add_flow(id='f_left_right', \n",
    "                   from_edge='entry_left', \n",
    "                   to_edge='right_exit', \n",
    "                   begin=0, end=1000, \n",
    "                   frequency=300, \n",
    "                   v_type='car0', distribution=\"binomial\")\n",
    "    \n",
    "    # Adding flows from right entry\n",
    "    flows.add_flow(id='f_right_top', \n",
    "                   from_edge='entry_right', \n",
    "                   to_edge='top_exit', \n",
    "                   begin=0, end=1000, \n",
    "                   frequency=300, \n",
    "                   v_type='car0', distribution=\"binomial\")\n",
    "    flows.add_flow(id='f_right_left', \n",
    "                   from_edge='entry_right', \n",
    "                   to_edge='left_exit', \n",
    "                   begin=0, end=1000, \n",
    "                   frequency=300, \n",
    "                   v_type='car0', distribution=\"binomial\")\n",
    "    \n",
    "    # Adding flows from top entry\n",
    "    flows.add_flow(id='f_top_left', \n",
    "                   from_edge='entry_top', \n",
    "                   to_edge='left_exit', \n",
    "                   begin=0, end=1000, \n",
    "                   frequency=300, \n",
    "                   v_type='car0', distribution=\"uniform\")\n",
    "    flows.add_flow(id='f_top_right', \n",
    "                   from_edge='entry_top', \n",
    "                   to_edge='right_exit', \n",
    "                   begin=0, end=1000, \n",
    "                   frequency=300, \n",
    "                   v_type='car0', distribution=\"uniform\")\n",
    "    \n",
    "    return flows"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f393efe8",
   "metadata": {},
   "source": [
    "Here, we use a `frequency` value of 300 vehicle per hour for each flow. Flows from left and right will be generated with a Binomial Law, wereas flows from top will be generated with an Uniform Law, to show you the difference.\n",
    "\n",
    "## Use custom networks\n",
    "\n",
    "Custom networks are used exactly like pre-set networks. We create an Experiment, and fill the parameters with the custom functions."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "739dce71",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Success.\n",
      "Success.\n"
     ]
    }
   ],
   "source": [
    "from sumo_experiments import Experiment\n",
    "\n",
    "infrastructures = triangle_infrastructures()\n",
    "flows = triangle_flows()\n",
    "\n",
    "exp = Experiment(\n",
    "    name='triangle',\n",
    "    infrastructures=infrastructures,\n",
    "    flows=flows\n",
    ")\n",
    "\n",
    "exp.run(\n",
    "    simulation_duration=1000,\n",
    "    gui=True\n",
    ")\n",
    "\n",
    "exp.clean_files()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aac7db0a",
   "metadata": {},
   "source": [
    "## Optional : Custom detectors\n",
    "\n",
    "The sumo-experiments library also allows to implement detectors. This detectors will be used by the TraCi module from SUMO, that allows you to control the infrastructures of a network simulation step by simulation step, and so to implement adaptative behaviours. This side of SUMO will be the subject of the next tutorial.\n",
    "\n",
    "Detectors are generated exactly like infrastructures and flows : by a function returning a `DetectorBuilder` object.\n",
    "\n",
    "A detector in sumo-experiments is a SUMO laneAreaDetector. It needs some elements to be defined :\n",
    "- A edge to place the detector\n",
    "- The lane on the edge to place the detector\n",
    "- A position on the lane, in [0, lane_length]\n",
    "- A type ('numerical' or 'boolean')\n",
    "\n",
    "Let's add detectors to the previous network. We're going to add short range detectors, that can only detect one car.\n",
    "\n",
    "***Note :** Find the right position for detectors is kind of tricky. For now, you have to tinker to find the right settings.*"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "6ab9b7e7",
   "metadata": {},
   "outputs": [],
   "source": [
    "from sumo_experiments.components import DetectorBuilder\n",
    "\n",
    "def triangle_detectors():\n",
    "    \n",
    "    # Instanciate the object\n",
    "    det = DetectorBuilder()\n",
    "    \n",
    "    # Add detectors to the left intersection\n",
    "    det.add_lane_area_detector(id='detector_left_left',\n",
    "                            edge='entry_left',\n",
    "                            lane=0, # edge_[lane_id]\n",
    "                            pos=(81 - 8 + 13), # lane length - detector length + offset due to intersection\n",
    "                            type='boolean')\n",
    "    det.add_lane_area_detector(id='detector_left_right',\n",
    "                            edge='right_left',\n",
    "                            lane=0,\n",
    "                            pos=(81 - 8),\n",
    "                            type='boolean')\n",
    "    det.add_lane_area_detector(id='detector_left_top',\n",
    "                            edge='top_left',\n",
    "                            lane=0,\n",
    "                            pos=(81 - 8),\n",
    "                            type='boolean')\n",
    "    \n",
    "    # Add detectors to the right intersection\n",
    "    det.add_lane_area_detector(id='detector_right_right',\n",
    "                            edge='entry_right', # edge_[lane_id]\n",
    "                            lane=0,\n",
    "                            pos=(81 - 8 + 13), # lane length - detector length + offset due to intersection\n",
    "                            type='boolean')\n",
    "    det.add_lane_area_detector(id='detector_right_left',\n",
    "                            edge='left_right',\n",
    "                            lane=0,\n",
    "                            pos=(81 - 8),\n",
    "                            type='boolean')\n",
    "    det.add_lane_area_detector(id='detector_right_top',\n",
    "                            edge='top_right',\n",
    "                            lane=0,\n",
    "                            pos=(81 - 8),\n",
    "                            type='boolean')\n",
    "    \n",
    "    # Add detectors to the top intersection\n",
    "    det.add_lane_area_detector(id='detector_top_top',\n",
    "                            edge='entry_top', # edge_[lane_id]\n",
    "                            lane=0,\n",
    "                            pos=(81 - 8 + 13), # lane length - detector length + offset due to intersection\n",
    "                            type='boolean')\n",
    "    det.add_lane_area_detector(id='detector_top_left',\n",
    "                            edge='left_top',\n",
    "                            lane=0,\n",
    "                            pos=(81 - 8),\n",
    "                            type='boolean')\n",
    "    det.add_lane_area_detector(id='detector_top_right',\n",
    "                            edge='right_top',\n",
    "                            lane=0,\n",
    "                            pos=(81 - 8),\n",
    "                            type='boolean')\n",
    "    \n",
    "    return det"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "a5e52361",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Success.\n",
      "Success.\n"
     ]
    }
   ],
   "source": [
    "from sumo_experiments import Experiment\n",
    "\n",
    "detectors = triangle_detectors()\n",
    "\n",
    "exp = Experiment(\n",
    "    name='triangle',\n",
    "    infrastructures=infrastructures,\n",
    "    flows=flows,\n",
    "    detectors=detectors\n",
    ")\n",
    "\n",
    "exp.run(\n",
    "    simulation_duration=1000,\n",
    "    gui=True\n",
    ")\n",
    "\n",
    "exp.clean_files()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "306da04d",
   "metadata": {},
   "source": [
    "**You can now create every network you want, or use pre-set networks to experiment. To experiment multiple systems, and get data from the simulation, we need to use the Traci package. This is the subject of th next tutorial.**"
   ]
  }
 ],
 "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
}
