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 diff --git a/flow/core/kernel/network/traci.py b/flow/core/kernel/network/traci.py index c9ac80772..c3460ce89 100644 --- a/flow/core/kernel/network/traci.py +++ b/flow/core/kernel/network/traci.py @@ -765,6 +765,11 @@ def generate_cfg(self, net_params, traffic_lights, routes): # 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): @@ -780,7 +785,7 @@ def generate_cfg(self, net_params, traffic_lights, routes): sumo_inflow['name'] += str(i) sumo_inflow['route'] = 'route{}_{}'.format(edge, i) - for key in ['vehsPerHour', 'probability', 'period']: + for key in ['probability', 'period']: if key in sumo_inflow: sumo_inflow[key] = str(float(inflow[key]) * ft) 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/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 d9fc773cd..ebd348faa 100644 --- a/flow/core/kernel/vehicle/base.py +++ b/flow/core/kernel/vehicle/base.py @@ -35,9 +35,7 @@ class KernelVehicle(object): vehicle kernel of separate simulators. """ - def __init__(self, - master_kernel, - sim_params): + def __init__(self, master_kernel, sim_params): """Instantiate the Flow vehicle kernel. Parameters diff --git a/flow/core/kernel/vehicle/traci.py b/flow/core/kernel/vehicle/traci.py index 50cd106c9..84acabce0 100644 --- a/flow/core/kernel/vehicle/traci.py +++ b/flow/core/kernel/vehicle/traci.py @@ -13,6 +13,8 @@ from bisect import bisect_left import itertools from copy import deepcopy +import random +import math # colors for vehicles WHITE = (255, 255, 255) @@ -31,12 +33,20 @@ class TraCIVehicle(KernelVehicle): Extends flow.core.kernel.vehicle.base.KernelVehicle """ - def __init__(self, - master_kernel, - sim_params): + def __init__(self, master_kernel, sim_params): """See parent class.""" KernelVehicle.__init__(self, master_kernel, sim_params) + # 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 = None + # number of vehicles of a specific inflow that have entered the network + self._num_inflows = None + # time since the start of the simulation + self._total_time = None + self.__ids = [] # ids of all vehicles self.__human_ids = [] # ids of human-driven vehicles self.__controlled_ids = [] # ids of flow-controlled vehicles @@ -85,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 @@ -113,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. @@ -130,8 +182,72 @@ 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 edge in self._inflows_by_edge.keys(): + # This inflow is using a sumo-specific feature, so ignore. + if len(self._inflows_by_edge[edge]["cumsum"]) == 0: + continue + 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: + # 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( + 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) + ) + + for veh_num in range(num_vehicles): + total_time = self._total_time + self.add( + veh_id="{}_{}_{}".format(name, total_time, veh_num), + type_id=self._inflows[name]["vtype"], + edge=self._inflows[name]["edge"], + pos=0, + lane=self._inflows[name]["departLane"], + speed=depart_speed + ) + + # =================================================================== # + # 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) @@ -151,7 +267,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) @@ -366,6 +482,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/flow/envs/base.py b/flow/envs/base.py index 1abb8a3c9..052b974d3 100644 --- a/flow/envs/base.py +++ b/flow/envs/base.py @@ -163,7 +163,8 @@ def __init__(self, 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 @@ -258,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/flow/networks/highway.py b/flow/networks/highway.py index c63292067..821a53518 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): diff --git a/tests/fast_tests/test_environments.py b/tests/fast_tests/test_environments.py index 48628c4ec..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 = 1353.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