From e60d82ac00cb75ae5d799d1e3984137970cc4d15 Mon Sep 17 00:00:00 2001
From: AboudyKreidieh <akreidieh@gmail.com>
Date: Fri, 17 Apr 2020 16:21:34 -0700
Subject: [PATCH 1/9] modified the edgestarts attribute

---
 flow/networks/highway.py | 26 +++++++++++++++++++++++---
 1 file changed, 23 insertions(+), 3 deletions(-)

diff --git a/flow/networks/highway.py b/flow/networks/highway.py
index c63292067..286022ec8 100644
--- a/flow/networks/highway.py
+++ b/flow/networks/highway.py
@@ -128,9 +128,29 @@ def specify_routes(self, net_params):
 
     def specify_edge_starts(self):
         """See parent class."""
-        edgestarts = [("highway_{}".format(i), 0)
-                      for i in range(self.num_edges)]
-        return edgestarts
+        junction_length = 0.1
+
+        # Add the main edges.
+        edge_starts = [
+            ("highway_{}".format(i),
+             i * (self.length / self.num_edges + junction_length))
+            for i in range(self.num_edges)
+        ]
+
+        return edge_starts
+
+    def specify_internal_edge_starts(self):
+        """See parent class."""
+        junction_length = 0.1
+
+        # Add the junctions.
+        edge_starts = [
+             (":edge_{}".format(i + 1),
+              (i + 1) * self.length / self.num_edges + i * junction_length)
+            for i in range(self.num_edges - 1)
+        ]
+
+        return edge_starts
 
     @staticmethod
     def gen_custom_start_pos(cls, net_params, initial_config, num_vehicles):

From 04c8aad824042fac51a736bc82350a07bc7e2487 Mon Sep 17 00:00:00 2001
From: AboudyKreidieh <akreidieh@gmail.com>
Date: Fri, 17 Apr 2020 16:22:23 -0700
Subject: [PATCH 2/9] added ignore_noise option

---
 flow/controllers/base_controller.py      | 27 +++++++-
 flow/controllers/car_following_models.py | 86 ++++++++++++++++++++----
 2 files changed, 98 insertions(+), 15 deletions(-)

diff --git a/flow/controllers/base_controller.py b/flow/controllers/base_controller.py
index 41780826b..4b7396394 100755
--- a/flow/controllers/base_controller.py
+++ b/flow/controllers/base_controller.py
@@ -37,6 +37,12 @@ class BaseController:
         Should be either "instantaneous" or "safe_velocity"
     noise : double
         variance of the gaussian from which to sample a noisy acceleration
+    ignore_noise : list of (float, float)
+        a list of (min_pos, max_pos) positions where noise should not be
+        applied to the accelerations. For example, if you would not like to
+        apply acceleration noise within the positions (0, 100) and (200, 300),
+        then this term is written as: [(0, 100), (200, 300)]. If set to None,
+        noise is applied to the accelerations everywhere.
     """
 
     def __init__(self,
@@ -44,9 +50,11 @@ def __init__(self,
                  car_following_params,
                  delay=0,
                  fail_safe=None,
-                 noise=0):
+                 noise=0,
+                 ignore_noise=None):
         """Instantiate the base class for acceleration behavior."""
         self.veh_id = veh_id
+        self.ignore_noise = ignore_noise or []
 
         # magnitude of gaussian noise
         self.accel_noise = noise
@@ -107,7 +115,22 @@ def get_action(self, env):
 
         # add noise to the accelerations, if requested
         if self.accel_noise > 0:
-            accel += np.random.normal(0, self.accel_noise)
+            if self.ignore_noise is None:
+                # Add noise to the vehicle for all positions in this case.
+                accel += np.random.normal(0, self.accel_noise)
+            else:
+                pos = env.k.vehicle.get_x_by_id(self.veh_id)
+
+                # Check whether to apply the acceleration. If you are within
+                # one of the ignore_pos positions, noise is not applied to the
+                # accelerations.
+                apply_noise = True
+                for (min_pos, max_pos) in self.ignore_noise:
+                    if min_pos <= pos < max_pos:
+                        apply_noise = False
+
+                if apply_noise:
+                    accel += np.random.normal(0, self.accel_noise)
 
         # run the failsafes, if requested
         if self.fail_safe == 'instantaneous':
diff --git a/flow/controllers/car_following_models.py b/flow/controllers/car_following_models.py
index f86c546e8..1ee1d8886 100755
--- a/flow/controllers/car_following_models.py
+++ b/flow/controllers/car_following_models.py
@@ -41,6 +41,12 @@ class CFMController(BaseController):
         time delay (default: 0.0)
     noise : float
         std dev of normal perturbation to the acceleration (default: 0)
+    ignore_noise : list of (float, float)
+        a list of (min_pos, max_pos) positions where noise should not be
+        applied to the accelerations. For example, if you would not like to
+        apply acceleration noise within the positions (0, 100) and (200, 300),
+        then this term is written as: [(0, 100), (200, 300)]. If set to None,
+        noise is applied to the accelerations everywhere.
     fail_safe : str
         type of flow-imposed failsafe the vehicle should posses, defaults
         to no failsafe (None)
@@ -56,6 +62,7 @@ def __init__(self,
                  v_des=8,
                  time_delay=0.0,
                  noise=0,
+                 ignore_noise=None,
                  fail_safe=None):
         """Instantiate a CFM controller."""
         BaseController.__init__(
@@ -64,9 +71,10 @@ def __init__(self,
             car_following_params,
             delay=time_delay,
             fail_safe=fail_safe,
-            noise=noise)
+            noise=noise,
+            ignore_noise=ignore_noise
+        )
 
-        self.veh_id = veh_id
         self.k_d = k_d
         self.k_v = k_v
         self.k_c = k_c
@@ -117,6 +125,12 @@ class BCMController(BaseController):
         time delay (default: 0.5)
     noise : float
         std dev of normal perturbation to the acceleration (default: 0)
+    ignore_noise : list of (float, float)
+        a list of (min_pos, max_pos) positions where noise should not be
+        applied to the accelerations. For example, if you would not like to
+        apply acceleration noise within the positions (0, 100) and (200, 300),
+        then this term is written as: [(0, 100), (200, 300)]. If set to None,
+        noise is applied to the accelerations everywhere.
     fail_safe : str
         type of flow-imposed failsafe the vehicle should posses, defaults
         to no failsafe (None)
@@ -132,6 +146,7 @@ def __init__(self,
                  v_des=8,
                  time_delay=0.0,
                  noise=0,
+                 ignore_noise=None,
                  fail_safe=None):
         """Instantiate a Bilateral car-following model controller."""
         BaseController.__init__(
@@ -140,9 +155,10 @@ def __init__(self,
             car_following_params,
             delay=time_delay,
             fail_safe=fail_safe,
-            noise=noise)
+            noise=noise,
+            ignore_noise=ignore_noise
+        )
 
-        self.veh_id = veh_id
         self.k_d = k_d
         self.k_v = k_v
         self.k_c = k_c
@@ -197,6 +213,12 @@ class LACController(BaseController):
         time delay (default: 0.5)
     noise : float
         std dev of normal perturbation to the acceleration (default: 0)
+    ignore_noise : list of (float, float)
+        a list of (min_pos, max_pos) positions where noise should not be
+        applied to the accelerations. For example, if you would not like to
+        apply acceleration noise within the positions (0, 100) and (200, 300),
+        then this term is written as: [(0, 100), (200, 300)]. If set to None,
+        noise is applied to the accelerations everywhere.
     fail_safe : str
         type of flow-imposed failsafe the vehicle should posses, defaults
         to no failsafe (None)
@@ -212,6 +234,7 @@ def __init__(self,
                  a=0,
                  time_delay=0.0,
                  noise=0,
+                 ignore_noise=None,
                  fail_safe=None):
         """Instantiate a Linear Adaptive Cruise controller."""
         BaseController.__init__(
@@ -220,9 +243,10 @@ def __init__(self,
             car_following_params,
             delay=time_delay,
             fail_safe=fail_safe,
-            noise=noise)
+            noise=noise,
+            ignore_noise=ignore_noise
+        )
 
-        self.veh_id = veh_id
         self.k_1 = k_1
         self.k_2 = k_2
         self.h = h
@@ -274,6 +298,12 @@ class OVMController(BaseController):
         time delay (default: 0.5)
     noise : float
         std dev of normal perturbation to the acceleration (default: 0)
+    ignore_noise : list of (float, float)
+        a list of (min_pos, max_pos) positions where noise should not be
+        applied to the accelerations. For example, if you would not like to
+        apply acceleration noise within the positions (0, 100) and (200, 300),
+        then this term is written as: [(0, 100), (200, 300)]. If set to None,
+        noise is applied to the accelerations everywhere.
     fail_safe : str
         type of flow-imposed failsafe the vehicle should posses, defaults
         to no failsafe (None)
@@ -289,6 +319,7 @@ def __init__(self,
                  v_max=30,
                  time_delay=0,
                  noise=0,
+                 ignore_noise=None,
                  fail_safe=None):
         """Instantiate an Optimal Vehicle Model controller."""
         BaseController.__init__(
@@ -297,8 +328,10 @@ def __init__(self,
             car_following_params,
             delay=time_delay,
             fail_safe=fail_safe,
-            noise=noise)
-        self.veh_id = veh_id
+            noise=noise,
+            ignore_noise=ignore_noise
+        )
+
         self.v_max = v_max
         self.alpha = alpha
         self.beta = beta
@@ -351,6 +384,12 @@ class LinearOVM(BaseController):
         time delay (default: 0.5)
     noise : float
         std dev of normal perturbation to the acceleration (default: 0)
+    ignore_noise : list of (float, float)
+        a list of (min_pos, max_pos) positions where noise should not be
+        applied to the accelerations. For example, if you would not like to
+        apply acceleration noise within the positions (0, 100) and (200, 300),
+        then this term is written as: [(0, 100), (200, 300)]. If set to None,
+        noise is applied to the accelerations everywhere.
     fail_safe : str
         type of flow-imposed failsafe the vehicle should posses, defaults
         to no failsafe (None)
@@ -364,6 +403,7 @@ def __init__(self,
                  h_st=5,
                  time_delay=0.0,
                  noise=0,
+                 ignore_noise=None,
                  fail_safe=None):
         """Instantiate a Linear OVM controller."""
         BaseController.__init__(
@@ -372,8 +412,10 @@ def __init__(self,
             car_following_params,
             delay=time_delay,
             fail_safe=fail_safe,
-            noise=noise)
-        self.veh_id = veh_id
+            noise=noise,
+            ignore_noise=ignore_noise
+        )
+
         # 4.8*1.85 for case I, 3.8*1.85 for case II, per Nakayama
         self.v_max = v_max
         # TAU in Traffic Flow Dynamics textbook
@@ -429,6 +471,12 @@ class IDMController(BaseController):
         linear jam distance, in m (default: 2)
     noise : float
         std dev of normal perturbation to the acceleration (default: 0)
+    ignore_noise : list of (float, float)
+        a list of (min_pos, max_pos) positions where noise should not be
+        applied to the accelerations. For example, if you would not like to
+        apply acceleration noise within the positions (0, 100) and (200, 300),
+        then this term is written as: [(0, 100), (200, 300)]. If set to None,
+        noise is applied to the accelerations everywhere.
     fail_safe : str
         type of flow-imposed failsafe the vehicle should posses, defaults
         to no failsafe (None)
@@ -444,6 +492,7 @@ def __init__(self,
                  s0=2,
                  time_delay=0.0,
                  noise=0,
+                 ignore_noise=None,
                  fail_safe=None,
                  car_following_params=None):
         """Instantiate an IDM controller."""
@@ -453,7 +502,10 @@ def __init__(self,
             car_following_params,
             delay=time_delay,
             fail_safe=fail_safe,
-            noise=noise)
+            noise=noise,
+            ignore_noise=ignore_noise
+        )
+
         self.v0 = v0
         self.T = T
         self.a = a
@@ -530,6 +582,12 @@ class GippsController(BaseController):
         reaction time in s (default: 1)
     noise : float
         std dev of normal perturbation to the acceleration (default: 0)
+    ignore_noise : list of (float, float)
+        a list of (min_pos, max_pos) positions where noise should not be
+        applied to the accelerations. For example, if you would not like to
+        apply acceleration noise within the positions (0, 100) and (200, 300),
+        then this term is written as: [(0, 100), (200, 300)]. If set to None,
+        noise is applied to the accelerations everywhere.
     fail_safe : str
         type of flow-imposed failsafe the vehicle should posses, defaults
         to no failsafe (None)
@@ -546,6 +604,7 @@ def __init__(self,
                  tau=1,
                  delay=0,
                  noise=0,
+                 ignore_noise=None,
                  fail_safe=None):
         """Instantiate a Gipps' controller."""
         BaseController.__init__(
@@ -554,8 +613,9 @@ def __init__(self,
             car_following_params,
             delay=delay,
             fail_safe=fail_safe,
-            noise=noise
-            )
+            noise=noise,
+            ignore_noise=ignore_noise
+        )
 
         self.v_desired = v0
         self.acc = acc

From cae0270fc6f0e103d1613842a55a56f8366db823 Mon Sep 17 00:00:00 2001
From: AboudyKreidieh <akreidieh@gmail.com>
Date: Sun, 19 Apr 2020 19:44:08 -0700
Subject: [PATCH 3/9] added new inflow to vehicle class

---
 flow/core/kernel/kernel.py           |  4 +-
 flow/core/kernel/network/traci.py    | 66 ++++++++++++++--------------
 flow/core/kernel/simulation/traci.py |  3 +-
 flow/core/kernel/vehicle/base.py     |  4 +-
 flow/core/kernel/vehicle/traci.py    | 60 +++++++++++++++++++++++--
 flow/envs/base.py                    |  3 +-
 6 files changed, 98 insertions(+), 42 deletions(-)

diff --git a/flow/core/kernel/kernel.py b/flow/core/kernel/kernel.py
index abf7494b9..336c7cfa5 100644
--- a/flow/core/kernel/kernel.py
+++ b/flow/core/kernel/kernel.py
@@ -45,7 +45,7 @@ class Kernel(object):
     traffic simulators, e.g. SUMO, AIMSUN, TruckSim, etc...
     """
 
-    def __init__(self, simulator, sim_params):
+    def __init__(self, simulator, sim_params, net_params):
         """Instantiate a Flow kernel object.
 
         Parameters
@@ -65,7 +65,7 @@ def __init__(self, simulator, sim_params):
         if simulator == "traci":
             self.simulation = TraCISimulation(self)
             self.network = TraCIKernelNetwork(self, sim_params)
-            self.vehicle = TraCIVehicle(self, sim_params)
+            self.vehicle = TraCIVehicle(self, sim_params, net_params)
             self.traffic_light = TraCITrafficLight(self)
         elif simulator == 'aimsun':
             self.simulation = AimsunKernelSimulation(self)
diff --git a/flow/core/kernel/network/traci.py b/flow/core/kernel/network/traci.py
index c9ac80772..2ff3023b8 100644
--- a/flow/core/kernel/network/traci.py
+++ b/flow/core/kernel/network/traci.py
@@ -758,39 +758,39 @@ def generate_cfg(self, net_params, traffic_lights, routes):
                     edges=' '.join(r)
                 ))
 
-        # add the inflows from various edges to the xml file
-        if self.network.net_params.inflows is not None:
-            total_inflows = self.network.net_params.inflows.get()
-            for inflow in total_inflows:
-                # do not want to affect the original values
-                sumo_inflow = deepcopy(inflow)
-
-                # convert any non-string element in the inflow dict to a string
-                for key in sumo_inflow:
-                    if not isinstance(sumo_inflow[key], str):
-                        sumo_inflow[key] = repr(sumo_inflow[key])
-
-                edge = sumo_inflow['edge']
-                del sumo_inflow['edge']
-
-                if 'route' not in sumo_inflow:
-                    # distribute the inflow rates across all routes from a
-                    # given edge w.r.t. the provided fractions for each route
-                    for i, (_, ft) in enumerate(routes[edge]):
-                        sumo_inflow['name'] += str(i)
-                        sumo_inflow['route'] = 'route{}_{}'.format(edge, i)
-
-                        for key in ['vehsPerHour', 'probability', 'period']:
-                            if key in sumo_inflow:
-                                sumo_inflow[key] = str(float(inflow[key]) * ft)
-
-                        if 'number' in sumo_inflow:
-                            sumo_inflow['number'] = str(
-                                int(float(inflow['number']) * ft))
-
-                        routes_data.append(_flow(**sumo_inflow))
-                else:
-                    routes_data.append(_flow(**sumo_inflow))
+        # # add the inflows from various edges to the xml file
+        # if self.network.net_params.inflows is not None:
+        #     total_inflows = self.network.net_params.inflows.get()
+        #     for inflow in total_inflows:
+        #         # do not want to affect the original values
+        #         sumo_inflow = deepcopy(inflow)
+        #
+        #         # convert any non-string element in the inflow dict to a string
+        #         for key in sumo_inflow:
+        #             if not isinstance(sumo_inflow[key], str):
+        #                 sumo_inflow[key] = repr(sumo_inflow[key])
+        #
+        #         edge = sumo_inflow['edge']
+        #         del sumo_inflow['edge']
+        #
+        #         if 'route' not in sumo_inflow:
+        #             # distribute the inflow rates across all routes from a
+        #             # given edge w.r.t. the provided fractions for each route
+        #             for i, (_, ft) in enumerate(routes[edge]):
+        #                 sumo_inflow['name'] += str(i)
+        #                 sumo_inflow['route'] = 'route{}_{}'.format(edge, i)
+        #
+        #                 for key in ['vehsPerHour', 'probability', 'period']:
+        #                     if key in sumo_inflow:
+        #                         sumo_inflow[key] = str(float(inflow[key]) * ft)
+        #
+        #                 if 'number' in sumo_inflow:
+        #                     sumo_inflow['number'] = str(
+        #                         int(float(inflow['number']) * ft))
+        #
+        #                 routes_data.append(_flow(**sumo_inflow))
+        #         else:
+        #             routes_data.append(_flow(**sumo_inflow))
 
         printxml(routes_data, self.cfg_path + self.roufn)
 
diff --git a/flow/core/kernel/simulation/traci.py b/flow/core/kernel/simulation/traci.py
index 0ee29ada6..f71900d98 100644
--- a/flow/core/kernel/simulation/traci.py
+++ b/flow/core/kernel/simulation/traci.py
@@ -88,7 +88,8 @@ def start_simulation(self, network, sim_params):
                     sumo_binary, "-c", network.cfg,
                     "--remote-port", str(sim_params.port),
                     "--num-clients", str(sim_params.num_clients),
-                    "--step-length", str(sim_params.sim_step)
+                    "--step-length", str(sim_params.sim_step),
+                    "--max-depart-delay", "0",
                 ]
 
                 # use a ballistic integration step (if request)
diff --git a/flow/core/kernel/vehicle/base.py b/flow/core/kernel/vehicle/base.py
index d9fc773cd..9bc500b00 100644
--- a/flow/core/kernel/vehicle/base.py
+++ b/flow/core/kernel/vehicle/base.py
@@ -37,7 +37,8 @@ class KernelVehicle(object):
 
     def __init__(self,
                  master_kernel,
-                 sim_params):
+                 sim_params,
+                 net_params):
         """Instantiate the Flow vehicle kernel.
 
         Parameters
@@ -51,6 +52,7 @@ def __init__(self,
         self.master_kernel = master_kernel
         self.kernel_api = None
         self.sim_step = sim_params.sim_step
+        self.net_params = net_params
 
     def pass_api(self, kernel_api):
         """Acquire the kernel api that was generated by the simulation kernel.
diff --git a/flow/core/kernel/vehicle/traci.py b/flow/core/kernel/vehicle/traci.py
index 50cd106c9..15a8bd1bc 100644
--- a/flow/core/kernel/vehicle/traci.py
+++ b/flow/core/kernel/vehicle/traci.py
@@ -33,10 +33,37 @@ class TraCIVehicle(KernelVehicle):
 
     def __init__(self,
                  master_kernel,
-                 sim_params):
+                 sim_params,
+                 net_params):
         """See parent class."""
-        KernelVehicle.__init__(self, master_kernel, sim_params)
-
+        KernelVehicle.__init__(self, master_kernel, sim_params, net_params)
+
+        self.__inflows = {x["name"]: x for x in self.net_params.inflows.get()}
+
+        # TODO: add random inflow option
+        # cumulative inflow rate for all vehicles in a given edge
+        self.__inflows_by_edge = {
+            k: {"cumsum": [], "type": []}
+            for k in np.unique([
+                self.__inflows[key]["edge"] for key in self.__inflows.keys()
+            ])
+        }
+
+        for key in self.__inflows.keys():
+            edge = self.__inflows[key]["edge"]
+            inflow = self.__inflows[key]["vehsPerHour"]
+
+            # Add the cumulative inflow rates and the type of inflow.
+            self.__inflows_by_edge[edge]["type"].append(key)
+            self.__inflows_by_edge[edge]["cumsum"].append(inflow)
+            if len(self.__inflows_by_edge[edge]["cumsum"]) > 1:
+                self.__inflows_by_edge[edge]["cumsum"][-1] += \
+                    self.__inflows_by_edge[edge]["cumsum"][-2]
+
+        # number of vehicles of a specific inflow that have entered the network
+        self.__num_inflows = {name: 0 for name in self.__inflows.keys()}
+
+        self.total_time = 0
         self.__ids = []  # ids of all vehicles
         self.__human_ids = []  # ids of human-driven vehicles
         self.__controlled_ids = []  # ids of flow-controlled vehicles
@@ -130,8 +157,33 @@ def update(self, reset):
             specifies whether the simulator was reset in the last simulation
             step
         """
-        # copy over the previous speeds
+        # =================================================================== #
+        # Add the inflow vehicles.                                            #
+        # =================================================================== #
+        self.total_time += 1
+
+        for key in self.__inflows.keys():
+            veh_per_hour = self.__inflows[key]["vehsPerHour"]
+            steps_per_veh = int(3600 / (self.sim_step * veh_per_hour))
+
+            # Add a vehicle if the inflow rate requires it.
+            if self.total_time % steps_per_veh == 0:
+                name = self.__inflows[key]["name"]
+                self.add(
+                    veh_id="{}_{}".format(name, self.__num_inflows[name]),
+                    type_id=self.__inflows[key]["vtype"],
+                    edge=self.__inflows[key]["edge"],
+                    pos=0,
+                    lane=self.__inflows[key]["departLane"],
+                    speed=self.__inflows[key]["departSpeed"]
+                )
+                self.__num_inflows[name] += 1
+
+        # =================================================================== #
+        # Update the vehicle states.                                          #
+        # =================================================================== #
 
+        # copy over the previous speeds
         vehicle_obs = {}
         for veh_id in self.__ids:
             self.previous_speeds[veh_id] = self.get_speed(veh_id)
diff --git a/flow/envs/base.py b/flow/envs/base.py
index 1abb8a3c9..d246638ef 100644
--- a/flow/envs/base.py
+++ b/flow/envs/base.py
@@ -156,7 +156,8 @@ def __init__(self,
 
         # create the Flow kernel
         self.k = Kernel(simulator=self.simulator,
-                        sim_params=self.sim_params)
+                        sim_params=self.sim_params,
+                        net_params=self.net_params)
 
         # use the network class's network parameters to generate the necessary
         # network components within the network kernel

From 1b9265df1a533b9975b11a5e4d34ed979992046b Mon Sep 17 00:00:00 2001
From: AboudyKreidieh <akreidieh@gmail.com>
Date: Sun, 19 Apr 2020 22:05:04 -0700
Subject: [PATCH 4/9] some cleanup

---
 flow/core/kernel/network/traci.py | 35 -------------------------------
 flow/networks/highway.py          |  2 +-
 2 files changed, 1 insertion(+), 36 deletions(-)

diff --git a/flow/core/kernel/network/traci.py b/flow/core/kernel/network/traci.py
index 2ff3023b8..7c575adcb 100644
--- a/flow/core/kernel/network/traci.py
+++ b/flow/core/kernel/network/traci.py
@@ -7,7 +7,6 @@
 import subprocess
 import xml.etree.ElementTree as ElementTree
 from lxml import etree
-from copy import deepcopy
 
 E = etree.Element
 
@@ -758,40 +757,6 @@ def generate_cfg(self, net_params, traffic_lights, routes):
                     edges=' '.join(r)
                 ))
 
-        # # add the inflows from various edges to the xml file
-        # if self.network.net_params.inflows is not None:
-        #     total_inflows = self.network.net_params.inflows.get()
-        #     for inflow in total_inflows:
-        #         # do not want to affect the original values
-        #         sumo_inflow = deepcopy(inflow)
-        #
-        #         # convert any non-string element in the inflow dict to a string
-        #         for key in sumo_inflow:
-        #             if not isinstance(sumo_inflow[key], str):
-        #                 sumo_inflow[key] = repr(sumo_inflow[key])
-        #
-        #         edge = sumo_inflow['edge']
-        #         del sumo_inflow['edge']
-        #
-        #         if 'route' not in sumo_inflow:
-        #             # distribute the inflow rates across all routes from a
-        #             # given edge w.r.t. the provided fractions for each route
-        #             for i, (_, ft) in enumerate(routes[edge]):
-        #                 sumo_inflow['name'] += str(i)
-        #                 sumo_inflow['route'] = 'route{}_{}'.format(edge, i)
-        #
-        #                 for key in ['vehsPerHour', 'probability', 'period']:
-        #                     if key in sumo_inflow:
-        #                         sumo_inflow[key] = str(float(inflow[key]) * ft)
-        #
-        #                 if 'number' in sumo_inflow:
-        #                     sumo_inflow['number'] = str(
-        #                         int(float(inflow['number']) * ft))
-        #
-        #                 routes_data.append(_flow(**sumo_inflow))
-        #         else:
-        #             routes_data.append(_flow(**sumo_inflow))
-
         printxml(routes_data, self.cfg_path + self.roufn)
 
         # this is the data that we will pass to the *.sumo.cfg file
diff --git a/flow/networks/highway.py b/flow/networks/highway.py
index 286022ec8..821a53518 100644
--- a/flow/networks/highway.py
+++ b/flow/networks/highway.py
@@ -147,7 +147,7 @@ def specify_internal_edge_starts(self):
         edge_starts = [
              (":edge_{}".format(i + 1),
               (i + 1) * self.length / self.num_edges + i * junction_length)
-            for i in range(self.num_edges - 1)
+             for i in range(self.num_edges - 1)
         ]
 
         return edge_starts

From d891974740ef5edb1550af4fbd8558cc11b59b6f Mon Sep 17 00:00:00 2001
From: AboudyKreidieh <akreidieh@gmail.com>
Date: Tue, 21 Apr 2020 13:56:35 -0700
Subject: [PATCH 5/9] some bug fixes

---
 flow/core/kernel/network/traci.py | 40 +++++++++++++++++++++++++++++++
 flow/core/kernel/vehicle/traci.py | 17 +++++++++++--
 2 files changed, 55 insertions(+), 2 deletions(-)

diff --git a/flow/core/kernel/network/traci.py b/flow/core/kernel/network/traci.py
index 7c575adcb..c3460ce89 100644
--- a/flow/core/kernel/network/traci.py
+++ b/flow/core/kernel/network/traci.py
@@ -7,6 +7,7 @@
 import subprocess
 import xml.etree.ElementTree as ElementTree
 from lxml import etree
+from copy import deepcopy
 
 E = etree.Element
 
@@ -757,6 +758,45 @@ def generate_cfg(self, net_params, traffic_lights, routes):
                     edges=' '.join(r)
                 ))
 
+        # add the inflows from various edges to the xml file
+        if self.network.net_params.inflows is not None:
+            total_inflows = self.network.net_params.inflows.get()
+            for inflow in total_inflows:
+                # do not want to affect the original values
+                sumo_inflow = deepcopy(inflow)
+
+                # The vehsPerHour feature has been moved to the VehicleParams
+                # class.
+                if "vehsPerHour" in sumo_inflow.keys():
+                    continue
+
+                # convert any non-string element in the inflow dict to a string
+                for key in sumo_inflow:
+                    if not isinstance(sumo_inflow[key], str):
+                        sumo_inflow[key] = repr(sumo_inflow[key])
+
+                edge = sumo_inflow['edge']
+                del sumo_inflow['edge']
+
+                if 'route' not in sumo_inflow:
+                    # distribute the inflow rates across all routes from a
+                    # given edge w.r.t. the provided fractions for each route
+                    for i, (_, ft) in enumerate(routes[edge]):
+                        sumo_inflow['name'] += str(i)
+                        sumo_inflow['route'] = 'route{}_{}'.format(edge, i)
+
+                        for key in ['probability', 'period']:
+                            if key in sumo_inflow:
+                                sumo_inflow[key] = str(float(inflow[key]) * ft)
+
+                        if 'number' in sumo_inflow:
+                            sumo_inflow['number'] = str(
+                                int(float(inflow['number']) * ft))
+
+                        routes_data.append(_flow(**sumo_inflow))
+                else:
+                    routes_data.append(_flow(**sumo_inflow))
+
         printxml(routes_data, self.cfg_path + self.roufn)
 
         # this is the data that we will pass to the *.sumo.cfg file
diff --git a/flow/core/kernel/vehicle/traci.py b/flow/core/kernel/vehicle/traci.py
index 15a8bd1bc..705ffffce 100644
--- a/flow/core/kernel/vehicle/traci.py
+++ b/flow/core/kernel/vehicle/traci.py
@@ -13,6 +13,7 @@
 from bisect import bisect_left
 import itertools
 from copy import deepcopy
+import random
 
 # colors for vehicles
 WHITE = (255, 255, 255)
@@ -163,18 +164,30 @@ def update(self, reset):
         self.total_time += 1
 
         for key in self.__inflows.keys():
+            # This inflow is using a sumo-specific feature, so ignore.
+            if "vehsPerHour" not in self.__inflows[key].keys():
+                continue
+
             veh_per_hour = self.__inflows[key]["vehsPerHour"]
             steps_per_veh = int(3600 / (self.sim_step * veh_per_hour))
 
             # Add a vehicle if the inflow rate requires it.
             if self.total_time % steps_per_veh == 0:
                 name = self.__inflows[key]["name"]
+                edge = self.__inflows[key]["edge"]
+                depart_lane = self.__inflows[key]["departLane"]
+
+                # Choose a random lane to depart from.
+                if depart_lane == "free":
+                    depart_lane = random.randint(
+                        0, self.master_kernel.network.num_lanes(edge) - 1)
+
                 self.add(
                     veh_id="{}_{}".format(name, self.__num_inflows[name]),
                     type_id=self.__inflows[key]["vtype"],
-                    edge=self.__inflows[key]["edge"],
+                    edge=edge,
                     pos=0,
-                    lane=self.__inflows[key]["departLane"],
+                    lane=depart_lane,
                     speed=self.__inflows[key]["departSpeed"]
                 )
                 self.__num_inflows[name] += 1

From 02c01bc6cdce1569438b1d6626d3b54f1f86a160 Mon Sep 17 00:00:00 2001
From: AboudyKreidieh <akreidieh@gmail.com>
Date: Tue, 21 Apr 2020 14:50:13 -0700
Subject: [PATCH 6/9] more fixes

---
 flow/core/kernel/vehicle/traci.py | 42 ++++++++++++++++++-------------
 1 file changed, 24 insertions(+), 18 deletions(-)

diff --git a/flow/core/kernel/vehicle/traci.py b/flow/core/kernel/vehicle/traci.py
index 705ffffce..7f879c560 100644
--- a/flow/core/kernel/vehicle/traci.py
+++ b/flow/core/kernel/vehicle/traci.py
@@ -14,6 +14,7 @@
 import itertools
 from copy import deepcopy
 import random
+import math
 
 # colors for vehicles
 WHITE = (255, 255, 255)
@@ -169,28 +170,33 @@ def update(self, reset):
                 continue
 
             veh_per_hour = self.__inflows[key]["vehsPerHour"]
-            steps_per_veh = int(3600 / (self.sim_step * veh_per_hour))
+            steps_per_veh = 3600 / (self.sim_step * veh_per_hour)
 
             # Add a vehicle if the inflow rate requires it.
-            if self.total_time % steps_per_veh == 0:
+            if steps_per_veh < 1 or self.total_time % int(steps_per_veh) == 0:
                 name = self.__inflows[key]["name"]
-                edge = self.__inflows[key]["edge"]
-                depart_lane = self.__inflows[key]["departLane"]
-
-                # Choose a random lane to depart from.
-                if depart_lane == "free":
-                    depart_lane = random.randint(
-                        0, self.master_kernel.network.num_lanes(edge) - 1)
-
-                self.add(
-                    veh_id="{}_{}".format(name, self.__num_inflows[name]),
-                    type_id=self.__inflows[key]["vtype"],
-                    edge=edge,
-                    pos=0,
-                    lane=depart_lane,
-                    speed=self.__inflows[key]["departSpeed"]
+
+                # number of vehicles to add
+                num_vehicles = max(
+                    1,
+                    # number of vehicles to add if inflow rate is greater than
+                    # the simulation update time per step
+                    math.floor(1/steps_per_veh)
+                    + (1 if random.uniform(0, 1) <
+                       ((1/steps_per_veh) - math.floor(1/steps_per_veh))
+                       else 0)
                 )
-                self.__num_inflows[name] += 1
+
+                for _ in range(num_vehicles):
+                    self.add(
+                        veh_id="{}_{}".format(name, self.__num_inflows[name]),
+                        type_id=self.__inflows[key]["vtype"],
+                        edge=self.__inflows[key]["edge"],
+                        pos=0,
+                        lane=self.__inflows[key]["departLane"],
+                        speed=self.__inflows[key]["departSpeed"]
+                    )
+                    self.__num_inflows[name] += 1
 
         # =================================================================== #
         # Update the vehicle states.                                          #

From 694714e1acfe0cdfaf5d6b02721dc8124480de97 Mon Sep 17 00:00:00 2001
From: AboudyKreidieh <akreidieh@gmail.com>
Date: Tue, 21 Apr 2020 19:18:28 -0700
Subject: [PATCH 7/9] bug fixes

---
 flow/core/kernel/kernel.py            |   4 +-
 flow/core/kernel/simulation/traci.py  |   2 +-
 flow/core/kernel/vehicle/aimsun.py    |   7 +-
 flow/core/kernel/vehicle/base.py      |   6 +-
 flow/core/kernel/vehicle/traci.py     | 109 ++++++++++++++++----------
 flow/envs/base.py                     |   9 ++-
 flow/envs/traffic_light_grid.py       |   8 +-
 tests/fast_tests/test_environments.py |   2 +-
 8 files changed, 85 insertions(+), 62 deletions(-)

diff --git a/flow/core/kernel/kernel.py b/flow/core/kernel/kernel.py
index 336c7cfa5..abf7494b9 100644
--- a/flow/core/kernel/kernel.py
+++ b/flow/core/kernel/kernel.py
@@ -45,7 +45,7 @@ class Kernel(object):
     traffic simulators, e.g. SUMO, AIMSUN, TruckSim, etc...
     """
 
-    def __init__(self, simulator, sim_params, net_params):
+    def __init__(self, simulator, sim_params):
         """Instantiate a Flow kernel object.
 
         Parameters
@@ -65,7 +65,7 @@ def __init__(self, simulator, sim_params, net_params):
         if simulator == "traci":
             self.simulation = TraCISimulation(self)
             self.network = TraCIKernelNetwork(self, sim_params)
-            self.vehicle = TraCIVehicle(self, sim_params, net_params)
+            self.vehicle = TraCIVehicle(self, sim_params)
             self.traffic_light = TraCITrafficLight(self)
         elif simulator == 'aimsun':
             self.simulation = AimsunKernelSimulation(self)
diff --git a/flow/core/kernel/simulation/traci.py b/flow/core/kernel/simulation/traci.py
index f71900d98..2ca73725f 100644
--- a/flow/core/kernel/simulation/traci.py
+++ b/flow/core/kernel/simulation/traci.py
@@ -89,7 +89,7 @@ def start_simulation(self, network, sim_params):
                     "--remote-port", str(sim_params.port),
                     "--num-clients", str(sim_params.num_clients),
                     "--step-length", str(sim_params.sim_step),
-                    "--max-depart-delay", "0",
+                    # "--max-depart-delay", "0",  TODO (ak): maybe use later
                 ]
 
                 # use a ballistic integration step (if request)
diff --git a/flow/core/kernel/vehicle/aimsun.py b/flow/core/kernel/vehicle/aimsun.py
index 3320d1515..c9bb238c1 100644
--- a/flow/core/kernel/vehicle/aimsun.py
+++ b/flow/core/kernel/vehicle/aimsun.py
@@ -97,17 +97,20 @@ def __init__(self,
         # FIXME lots of these used in simulation/aimsun.py, used when
         # we want to store the values in an emission file (necessary?)
 
-    def initialize(self, vehicles):
+    def initialize(self, vehicles, net_params):
         """Initialize vehicle state information.
 
         This is responsible for collecting vehicle type information from the
-        VehicleParams object and placing them within the Vehicles kernel.
+        VehicleParams object and inflow information from the NetParams object
+        and placing them within the Vehicles kernel.
 
         Parameters
         ----------
         vehicles : flow.core.params.VehicleParams
             initial vehicle parameter information, including the types of
             individual vehicles and their initial speeds
+        net_params : flow.core.params.NetParams
+            network-specific parameters
         """
         self.type_parameters = vehicles.type_parameters
         self.num_vehicles = 0
diff --git a/flow/core/kernel/vehicle/base.py b/flow/core/kernel/vehicle/base.py
index 9bc500b00..ebd348faa 100644
--- a/flow/core/kernel/vehicle/base.py
+++ b/flow/core/kernel/vehicle/base.py
@@ -35,10 +35,7 @@ class KernelVehicle(object):
     vehicle kernel of separate simulators.
     """
 
-    def __init__(self,
-                 master_kernel,
-                 sim_params,
-                 net_params):
+    def __init__(self, master_kernel, sim_params):
         """Instantiate the Flow vehicle kernel.
 
         Parameters
@@ -52,7 +49,6 @@ def __init__(self,
         self.master_kernel = master_kernel
         self.kernel_api = None
         self.sim_step = sim_params.sim_step
-        self.net_params = net_params
 
     def pass_api(self, kernel_api):
         """Acquire the kernel api that was generated by the simulation kernel.
diff --git a/flow/core/kernel/vehicle/traci.py b/flow/core/kernel/vehicle/traci.py
index 7f879c560..dd18cbb8a 100644
--- a/flow/core/kernel/vehicle/traci.py
+++ b/flow/core/kernel/vehicle/traci.py
@@ -33,39 +33,20 @@ class TraCIVehicle(KernelVehicle):
     Extends flow.core.kernel.vehicle.base.KernelVehicle
     """
 
-    def __init__(self,
-                 master_kernel,
-                 sim_params,
-                 net_params):
+    def __init__(self, master_kernel, sim_params):
         """See parent class."""
-        KernelVehicle.__init__(self, master_kernel, sim_params, net_params)
+        KernelVehicle.__init__(self, master_kernel, sim_params)
 
-        self.__inflows = {x["name"]: x for x in self.net_params.inflows.get()}
-
-        # TODO: add random inflow option
+        # a dictionary object containing information on the inflows of every
+        # inflow type
+        self._inflows = None
         # cumulative inflow rate for all vehicles in a given edge
-        self.__inflows_by_edge = {
-            k: {"cumsum": [], "type": []}
-            for k in np.unique([
-                self.__inflows[key]["edge"] for key in self.__inflows.keys()
-            ])
-        }
-
-        for key in self.__inflows.keys():
-            edge = self.__inflows[key]["edge"]
-            inflow = self.__inflows[key]["vehsPerHour"]
-
-            # Add the cumulative inflow rates and the type of inflow.
-            self.__inflows_by_edge[edge]["type"].append(key)
-            self.__inflows_by_edge[edge]["cumsum"].append(inflow)
-            if len(self.__inflows_by_edge[edge]["cumsum"]) > 1:
-                self.__inflows_by_edge[edge]["cumsum"][-1] += \
-                    self.__inflows_by_edge[edge]["cumsum"][-2]
-
+        self._inflows_by_edge = None
         # number of vehicles of a specific inflow that have entered the network
-        self.__num_inflows = {name: 0 for name in self.__inflows.keys()}
+        self._num_inflows = None
+        # time since the start of the simulation
+        self._total_time = None
 
-        self.total_time = 0
         self.__ids = []  # ids of all vehicles
         self.__human_ids = []  # ids of human-driven vehicles
         self.__controlled_ids = []  # ids of flow-controlled vehicles
@@ -114,18 +95,25 @@ def __init__(self,
         # old speeds used to compute accelerations
         self.previous_speeds = {}
 
-    def initialize(self, vehicles):
+    def initialize(self, vehicles, net_params):
         """Initialize vehicle state information.
 
         This is responsible for collecting vehicle type information from the
-        VehicleParams object and placing them within the Vehicles kernel.
+        VehicleParams object and inflow information from the NetParams object
+        and placing them within the Vehicles kernel.
 
         Parameters
         ----------
         vehicles : flow.core.params.VehicleParams
             initial vehicle parameter information, including the types of
             individual vehicles and their initial speeds
+        net_params : flow.core.params.NetParams
+            network-specific parameters
         """
+        # =================================================================== #
+        # Add the vehicle features.                                           #
+        # =================================================================== #
+
         self.type_parameters = vehicles.type_parameters
         self.minGap = vehicles.minGap
         self.num_vehicles = 0
@@ -142,6 +130,41 @@ def initialize(self, vehicles):
                 if typ['acceleration_controller'][0] == RLController:
                     self.num_rl_vehicles += 1
 
+        # =================================================================== #
+        # Add the inflow features.                                            #
+        # =================================================================== #
+
+        self._total_time = 0
+
+        self._inflows = {x["name"]: x for x in net_params.inflows.get()}
+
+        # TODO: add random inflow option
+        # cumulative inflow rate for all vehicles in a given edge
+        self._inflows_by_edge = {
+            k: {"cumsum": [], "type": []}
+            for k in np.unique([
+                self._inflows[key]["edge"] for key in self._inflows.keys()
+            ])
+        }
+
+        for key in self._inflows.keys():
+            # This inflow is using a sumo-specific feature, so ignore.
+            if "vehsPerHour" not in self._inflows[key].keys():
+                continue
+
+            edge = self._inflows[key]["edge"]
+            inflow = self._inflows[key]["vehsPerHour"]
+
+            # Add the cumulative inflow rates and the type of inflow.
+            self._inflows_by_edge[edge]["type"].append(key)
+            self._inflows_by_edge[edge]["cumsum"].append(inflow)
+            if len(self._inflows_by_edge[edge]["cumsum"]) > 1:
+                self._inflows_by_edge[edge]["cumsum"][-1] += \
+                    self._inflows_by_edge[edge]["cumsum"][-2]
+
+        # number of vehicles of a specific inflow that have entered the network
+        self._num_inflows = {name: 0 for name in self._inflows.keys()}
+
     def update(self, reset):
         """See parent class.
 
@@ -162,19 +185,19 @@ def update(self, reset):
         # =================================================================== #
         # Add the inflow vehicles.                                            #
         # =================================================================== #
-        self.total_time += 1
+        self._total_time += 1
 
-        for key in self.__inflows.keys():
+        for key in self._inflows.keys():
             # This inflow is using a sumo-specific feature, so ignore.
-            if "vehsPerHour" not in self.__inflows[key].keys():
+            if "vehsPerHour" not in self._inflows[key].keys():
                 continue
 
-            veh_per_hour = self.__inflows[key]["vehsPerHour"]
+            veh_per_hour = self._inflows[key]["vehsPerHour"]
             steps_per_veh = 3600 / (self.sim_step * veh_per_hour)
 
             # Add a vehicle if the inflow rate requires it.
-            if steps_per_veh < 1 or self.total_time % int(steps_per_veh) == 0:
-                name = self.__inflows[key]["name"]
+            if steps_per_veh < 1 or self._total_time % int(steps_per_veh) == 0:
+                name = self._inflows[key]["name"]
 
                 # number of vehicles to add
                 num_vehicles = max(
@@ -189,14 +212,14 @@ def update(self, reset):
 
                 for _ in range(num_vehicles):
                     self.add(
-                        veh_id="{}_{}".format(name, self.__num_inflows[name]),
-                        type_id=self.__inflows[key]["vtype"],
-                        edge=self.__inflows[key]["edge"],
+                        veh_id="{}_{}".format(name, self._num_inflows[name]),
+                        type_id=self._inflows[key]["vtype"],
+                        edge=self._inflows[key]["edge"],
                         pos=0,
-                        lane=self.__inflows[key]["departLane"],
-                        speed=self.__inflows[key]["departSpeed"]
+                        lane=self._inflows[key]["departLane"],
+                        speed=self._inflows[key]["departSpeed"]
                     )
-                    self.__num_inflows[name] += 1
+                    self._num_inflows[name] += 1
 
         # =================================================================== #
         # Update the vehicle states.                                          #
@@ -222,7 +245,7 @@ def update(self, reset):
             self.remove(veh_id)
             # remove exiting vehicles from the vehicle subscription if they
             # haven't been removed already
-            if vehicle_obs[veh_id] is None:
+            if veh_id in vehicle_obs and vehicle_obs[veh_id] is None:
                 vehicle_obs.pop(veh_id, None)
         self._arrived_rl_ids.append(arrived_rl_ids)
 
diff --git a/flow/envs/base.py b/flow/envs/base.py
index d246638ef..052b974d3 100644
--- a/flow/envs/base.py
+++ b/flow/envs/base.py
@@ -156,15 +156,15 @@ def __init__(self,
 
         # create the Flow kernel
         self.k = Kernel(simulator=self.simulator,
-                        sim_params=self.sim_params,
-                        net_params=self.net_params)
+                        sim_params=self.sim_params)
 
         # use the network class's network parameters to generate the necessary
         # network components within the network kernel
         self.k.network.generate_network(self.network)
 
         # initial the vehicles kernel using the VehicleParams object
-        self.k.vehicle.initialize(deepcopy(self.network.vehicles))
+        self.k.vehicle.initialize(deepcopy(self.network.vehicles),
+                                  deepcopy(self.network.net_params))
 
         # initialize the simulation using the simulation kernel. This will use
         # the network kernel as an input in order to determine what network
@@ -259,7 +259,8 @@ def restart_simulation(self, sim_params, render=None):
             self.sim_params.emission_path = sim_params.emission_path
 
         self.k.network.generate_network(self.network)
-        self.k.vehicle.initialize(deepcopy(self.network.vehicles))
+        self.k.vehicle.initialize(deepcopy(self.network.vehicles),
+                                  deepcopy(self.network.net_params))
         kernel_api = self.k.simulation.start_simulation(
             network=self.k.network, sim_params=self.sim_params)
         self.k.pass_api(kernel_api)
diff --git a/flow/envs/traffic_light_grid.py b/flow/envs/traffic_light_grid.py
index 53391a329..953555885 100644
--- a/flow/envs/traffic_light_grid.py
+++ b/flow/envs/traffic_light_grid.py
@@ -477,10 +477,10 @@ def _reroute_if_final_edge(self, veh_id):
             self.k.vehicle.add(
                 veh_id=veh_id,
                 edge=route_id,
-                type_id=str(type_id),
-                lane=str(lane_index),
-                pos="0",
-                speed="max")
+                type_id=type_id,
+                lane=lane_index,
+                pos=0,
+                speed=20)
 
     def get_closest_to_intersection(self, edges, num_closest, padding=False):
         """Return the IDs of the vehicles that are closest to an intersection.
diff --git a/tests/fast_tests/test_environments.py b/tests/fast_tests/test_environments.py
index 48628c4ec..9b76bdff5 100644
--- a/tests/fast_tests/test_environments.py
+++ b/tests/fast_tests/test_environments.py
@@ -937,7 +937,7 @@ def test_reset_inflows(self):
 
         # reset the environment and get a new inflow rate
         env.reset()
-        expected_inflow = 1353.6  # just from checking the new inflow
+        expected_inflow = 1569.6  # just from checking the new inflow
 
         # check that the first inflow rate is approximately what the seeded
         # value expects it to be

From 6dc187d6ad279b5c867c1b3d7f526f4dcd606cd3 Mon Sep 17 00:00:00 2001
From: AboudyKreidieh <akreidieh@gmail.com>
Date: Tue, 21 Apr 2020 21:01:33 -0700
Subject: [PATCH 8/9] added uniformly distributed inflows

---
 flow/core/kernel/simulation/traci.py  |  2 +-
 flow/core/kernel/vehicle/traci.py     | 54 +++++++++++++++++++++------
 tests/fast_tests/test_environments.py |  2 +-
 3 files changed, 45 insertions(+), 13 deletions(-)

diff --git a/flow/core/kernel/simulation/traci.py b/flow/core/kernel/simulation/traci.py
index 2ca73725f..f71900d98 100644
--- a/flow/core/kernel/simulation/traci.py
+++ b/flow/core/kernel/simulation/traci.py
@@ -89,7 +89,7 @@ def start_simulation(self, network, sim_params):
                     "--remote-port", str(sim_params.port),
                     "--num-clients", str(sim_params.num_clients),
                     "--step-length", str(sim_params.sim_step),
-                    # "--max-depart-delay", "0",  TODO (ak): maybe use later
+                    "--max-depart-delay", "0",
                 ]
 
                 # use a ballistic integration step (if request)
diff --git a/flow/core/kernel/vehicle/traci.py b/flow/core/kernel/vehicle/traci.py
index dd18cbb8a..834bdcd33 100644
--- a/flow/core/kernel/vehicle/traci.py
+++ b/flow/core/kernel/vehicle/traci.py
@@ -165,6 +165,10 @@ def initialize(self, vehicles, net_params):
         # number of vehicles of a specific inflow that have entered the network
         self._num_inflows = {name: 0 for name in self._inflows.keys()}
 
+    def _congested(self):
+        """Check if the network is congested."""
+        return self.num_vehicles >= 40
+
     def update(self, reset):
         """See parent class.
 
@@ -187,17 +191,39 @@ def update(self, reset):
         # =================================================================== #
         self._total_time += 1
 
-        for key in self._inflows.keys():
+        for edge in self._inflows_by_edge.keys():
             # This inflow is using a sumo-specific feature, so ignore.
-            if "vehsPerHour" not in self._inflows[key].keys():
+            if len(self._inflows_by_edge[edge]["cumsum"]) == 0:
                 continue
 
-            veh_per_hour = self._inflows[key]["vehsPerHour"]
+            veh_per_hour = self._inflows_by_edge[edge]["cumsum"][-1]
             steps_per_veh = 3600 / (self.sim_step * veh_per_hour)
 
             # Add a vehicle if the inflow rate requires it.
             if steps_per_veh < 1 or self._total_time % int(steps_per_veh) == 0:
-                name = self._inflows[key]["name"]
+                # Choose the type of vehicle to push to this edge
+                names = self._inflows_by_edge[edge]["type"]
+                name = names[0]
+                cumsum = self._inflows_by_edge[edge]["cumsum"]
+                for i in range(len(names) - 1):
+                    # Deal with cases with no vehicles.
+                    if self._num_inflows[names[i]] == 0:
+                        break
+
+                    # This is used in order to maintain the inflow rate ratio.
+                    exp_inflow_ratio = (cumsum[i+1] - cumsum[i]) / cumsum[i]
+                    act_inflow_ratio = self._num_inflows[names[i+1]] \
+                        / sum(self._num_inflows[names[j]] for j in range(i+1))
+
+                    # If not enough vehicles of a specific type has been pushed
+                    # to the network yet, add it to the network.
+                    if exp_inflow_ratio < act_inflow_ratio:
+                        break
+                    else:
+                        name = names[i + 1]
+
+                # Choose the departure speed.
+                depart_speed = self._inflows[name]["departSpeed"]
 
                 # number of vehicles to add
                 num_vehicles = max(
@@ -210,16 +236,16 @@ def update(self, reset):
                        else 0)
                 )
 
-                for _ in range(num_vehicles):
+                for veh_num in range(num_vehicles):
+                    total_time = self._total_time
                     self.add(
-                        veh_id="{}_{}".format(name, self._num_inflows[name]),
-                        type_id=self._inflows[key]["vtype"],
-                        edge=self._inflows[key]["edge"],
+                        veh_id="{}_{}_{}".format(name, total_time, veh_num),
+                        type_id=self._inflows[name]["vtype"],
+                        edge=self._inflows[name]["edge"],
                         pos=0,
-                        lane=self._inflows[key]["departLane"],
-                        speed=self._inflows[key]["departSpeed"]
+                        lane=self._inflows[name]["departLane"],
+                        speed=depart_speed
                     )
-                    self._num_inflows[name] += 1
 
         # =================================================================== #
         # Update the vehicle states.                                          #
@@ -460,6 +486,12 @@ def _add_departed(self, veh_id, veh_type):
         # get the subscription results from the new vehicle
         new_obs = self.kernel_api.vehicle.getSubscriptionResults(veh_id)
 
+        # Increment the vehicle counter of inflow vehicles of a specific type,
+        # if this is an inflow vehicle.
+        for key in self._num_inflows.keys():
+            if veh_id.startswith(key):
+                self._num_inflows[key] += 1
+
         return new_obs
 
     def reset(self):
diff --git a/tests/fast_tests/test_environments.py b/tests/fast_tests/test_environments.py
index 9b76bdff5..48b404d0e 100644
--- a/tests/fast_tests/test_environments.py
+++ b/tests/fast_tests/test_environments.py
@@ -937,7 +937,7 @@ def test_reset_inflows(self):
 
         # reset the environment and get a new inflow rate
         env.reset()
-        expected_inflow = 1569.6  # just from checking the new inflow
+        expected_inflow = 1440.0  # just from checking the new inflow
 
         # check that the first inflow rate is approximately what the seeded
         # value expects it to be

From 03f2208070d1e0f1e2d8210c6da4242f9b54e0cc Mon Sep 17 00:00:00 2001
From: AboudyKreidieh <akreidieh@gmail.com>
Date: Tue, 21 Apr 2020 21:02:08 -0700
Subject: [PATCH 9/9] minor cleanup

---
 flow/core/kernel/vehicle/traci.py | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/flow/core/kernel/vehicle/traci.py b/flow/core/kernel/vehicle/traci.py
index 834bdcd33..84acabce0 100644
--- a/flow/core/kernel/vehicle/traci.py
+++ b/flow/core/kernel/vehicle/traci.py
@@ -165,10 +165,6 @@ def initialize(self, vehicles, net_params):
         # number of vehicles of a specific inflow that have entered the network
         self._num_inflows = {name: 0 for name in self._inflows.keys()}
 
-    def _congested(self):
-        """Check if the network is congested."""
-        return self.num_vehicles >= 40
-
     def update(self, reset):
         """See parent class.