diff --git a/docs/img/figure8-corl2018.png b/docs/img/figure_eight-corl2018.png similarity index 100% rename from docs/img/figure8-corl2018.png rename to docs/img/figure_eight-corl2018.png diff --git a/docs/img/loop_merge.gif b/docs/img/two_rings_one_merge.gif similarity index 100% rename from docs/img/loop_merge.gif rename to docs/img/two_rings_one_merge.gif diff --git a/docs/source/examples.rst b/docs/source/examples.rst index 82ef3bfa5e..01945c53bc 100644 --- a/docs/source/examples.rst +++ b/docs/source/examples.rst @@ -19,7 +19,7 @@ A few points of clarification: Figure Eight ------------ -The figure-eight is a closed-loop version of an intersection. The goal is to maximize +The figure-eight is a closed-ring version of an intersection. The goal is to maximize the system-wide velocity for fourteen vehicles, which necessitates spacing the vehicles so that they don't run into conflicts at the merging points. The scenario is fully observed: all vehicles @@ -32,7 +32,7 @@ is relatively light-weight and can be trained the quickest. It can serve both as that the training process is working correctly and as a study of the difficulty of controlling many vehicles simultaneously. -.. image:: ../img/figure8-corl2018.png +.. image:: ../img/figure_eight-corl2018.png :width: 400 :align: center @@ -63,7 +63,7 @@ Flow comes with a New York City style, configurable grid of traffic lights where control both the traffic lights and the autonomous vehicles. The build-in reward is to minimize the total system delay. The number of rows and columns can be adjusted by changing the values in `N_ROWS` and `N_COLUMNS` and the inflows adjusted by changing `EDGE_INFLOW`. Relevant files are -`green_wave.py` in the examples folder and `grid_0.py` and `grid_1.py` in the benchmark folder. +`traffic_light_grid.py` in the examples folder and `grid_0.py` and `grid_1.py` in the benchmark folder. .. image:: ../img/grid-corl2018.png :width: 400 diff --git a/docs/source/flow.envs.bay_bridge.rst b/docs/source/flow.envs.bay_bridge.rst index 4c22ff837a..fdbed4bdf4 100644 --- a/docs/source/flow.envs.bay_bridge.rst +++ b/docs/source/flow.envs.bay_bridge.rst @@ -4,10 +4,10 @@ flow.envs.bay\_bridge package Submodules ---------- -flow.envs.bay\_bridge.base module +flow.envs.bay\_bridge module --------------------------------- -.. automodule:: flow.envs.bay_bridge.base +.. automodule:: flow.envs.bay_bridge :members: :undoc-members: :show-inheritance: diff --git a/docs/source/flow.envs.rst b/docs/source/flow.envs.rst index ff71f71901..fb383c11c0 100644 --- a/docs/source/flow.envs.rst +++ b/docs/source/flow.envs.rst @@ -14,7 +14,7 @@ Submodules flow.envs.base\_env module -------------------------- -.. automodule:: flow.envs.base_env +.. automodule:: flow.envs.base :members: :undoc-members: :show-inheritance: @@ -22,15 +22,15 @@ flow.envs.base\_env module flow.envs.bottleneck\_env module -------------------------------- -.. automodule:: flow.envs.bottleneck_env +.. automodule:: flow.envs.bottleneck :members: :undoc-members: :show-inheritance: -flow.envs.green\_wave\_env module +flow.envs.traffic\_light\_grid module --------------------------------- -.. automodule:: flow.envs.green_wave_env +.. automodule:: flow.envs.traffic_light_grid :members: :undoc-members: :show-inheritance: diff --git a/docs/source/multiagent.rst b/docs/source/multiagent.rst index bb307cf593..1e4fa458a1 100644 --- a/docs/source/multiagent.rst +++ b/docs/source/multiagent.rst @@ -16,7 +16,7 @@ The keys of the dictionary are IDs of the agent policies. A brief example of a multiagent environment: :: - from flow.envs.multiagent_env import MultiEnv + from flow.envs.multiagent.base import MultiEnv class MultiAgentAccelEnv(AccelEnv, MultiEnv): """Example multiagent environment""" diff --git a/examples/README.md b/examples/README.md index ce33fbded1..f612bb1fb1 100644 --- a/examples/README.md +++ b/examples/README.md @@ -61,9 +61,9 @@ average speed of vehicles in the network. ![](https://raw.githubusercontent.com/flow-project/flow/master/docs/img/figure_eight.gif) -### grid.py +### traffic_light_grid.py -Performs a simulation of vehicles on a grid. +Performs a simulation of vehicles on a traffic light grid. ![](https://raw.githubusercontent.com/flow-project/flow/master/docs/img/grid.gif) @@ -73,12 +73,6 @@ Example of an open multi-lane network with human-driven vehicles. ![](https://raw.githubusercontent.com/flow-project/flow/master/docs/img/highway.gif) -### loop_merges.py - -Example of ring road with larger merging ring. - -![](https://raw.githubusercontent.com/flow-project/flow/master/docs/img/loop_merge.gif) - ### merge.py Example of a merge network with human-driven vehicles. diff --git a/examples/aimsun/bottlenecks.py b/examples/aimsun/bottlenecks.py index 8950aeb923..71c15bded0 100644 --- a/examples/aimsun/bottlenecks.py +++ b/examples/aimsun/bottlenecks.py @@ -6,7 +6,7 @@ from flow.core.params import TrafficLightParams from flow.scenarios.bottleneck import BottleneckScenario -from flow.envs.bottleneck_env import BottleneckEnv +from flow.envs.bottleneck import BottleneckEnv from flow.core.experiment import Experiment SCALING = 1 diff --git a/examples/aimsun/figure_eight.py b/examples/aimsun/figure_eight.py index 51639a6a0f..058af88af7 100644 --- a/examples/aimsun/figure_eight.py +++ b/examples/aimsun/figure_eight.py @@ -9,7 +9,7 @@ from flow.core.params import AimsunParams, EnvParams, NetParams from flow.core.params import VehicleParams from flow.envs import TestEnv -from flow.scenarios.figure_eight import Figure8Scenario, ADDITIONAL_NET_PARAMS +from flow.scenarios.figure_eight import FigureEightScenario, ADDITIONAL_NET_PARAMS def figure_eight_example(render=None): @@ -42,8 +42,8 @@ def figure_eight_example(render=None): net_params = NetParams( additional_params=ADDITIONAL_NET_PARAMS.copy()) - scenario = Figure8Scenario( - name="figure8", + scenario = FigureEightScenario( + name="FigureEight", vehicles=vehicles, net_params=net_params) diff --git a/examples/aimsun/grid.py b/examples/aimsun/grid.py index 1a8ff04db7..442d30ca25 100644 --- a/examples/aimsun/grid.py +++ b/examples/aimsun/grid.py @@ -1,15 +1,15 @@ -"""Grid example.""" +"""Traffic Light Grid example.""" from flow.core.experiment import Experiment from flow.core.params import AimsunParams, EnvParams, InitialConfig, NetParams from flow.core.params import VehicleParams from flow.core.params import TrafficLightParams -from flow.envs.loop.loop_accel import AccelEnv, ADDITIONAL_ENV_PARAMS -from flow.scenarios.grid import SimpleGridScenario +from flow.envs.ring.accel import AccelEnv, ADDITIONAL_ENV_PARAMS +from flow.scenarios.traffic_light_grid import TrafficLightGridScenario -def grid_example(render=None): +def traffic_light_grid_example(render=None): """ - Perform a simulation of vehicles on a grid. + Perform a simulation of vehicles on a traffic light grid. Parameters ---------- @@ -20,7 +20,7 @@ def grid_example(render=None): ------- exp: flow.core.experiment.Experiment A non-rl experiment demonstrating the performance of human-driven - vehicles and balanced traffic lights on a grid. + vehicles and balanced traffic lights on a traffic light grid. """ inner_length = 300 long_length = 500 @@ -101,7 +101,7 @@ def grid_example(render=None): initial_config = InitialConfig(spacing='custom') - scenario = SimpleGridScenario( + scenario = TrafficLightGridScenario( name="grid-intersection", vehicles=vehicles, net_params=net_params, @@ -115,7 +115,7 @@ def grid_example(render=None): if __name__ == "__main__": # import the experiment variable - exp = grid_example() + exp = traffic_light_grid_example() # run for a set number of rollouts / time steps exp.run(1, 1500) diff --git a/examples/aimsun/merge.py b/examples/aimsun/merge.py index 6653e5b072..5b610309e2 100644 --- a/examples/aimsun/merge.py +++ b/examples/aimsun/merge.py @@ -9,7 +9,7 @@ from flow.core.experiment import Experiment from flow.scenarios.merge import MergeScenario, ADDITIONAL_NET_PARAMS from flow.controllers import IDMController -from flow.envs.merge import WaveAttenuationMergePOEnv, ADDITIONAL_ENV_PARAMS +from flow.envs.merge import MergePOEnv, ADDITIONAL_ENV_PARAMS # inflow rate at the highway HIGHWAY_RATE = 2000 @@ -83,7 +83,7 @@ def merge_example(render=None): net_params=net_params, initial_config=initial_config) - env = WaveAttenuationMergePOEnv( + env = MergePOEnv( env_params, sim_params, scenario, simulator='aimsun') return Experiment(env) diff --git a/examples/aimsun/small_template.py b/examples/aimsun/small_template.py index 040c126b34..32e89e43b7 100644 --- a/examples/aimsun/small_template.py +++ b/examples/aimsun/small_template.py @@ -4,7 +4,7 @@ from flow.core.params import AimsunParams, EnvParams, NetParams from flow.core.params import VehicleParams from flow.envs import TestEnv -from flow.scenarios.loop import Scenario +from flow.scenarios.ring import Scenario from flow.core.params import InFlows import flow.config as config import os diff --git a/examples/aimsun/sugiyama.py b/examples/aimsun/sugiyama.py index d5fd7b6749..e97e19a20f 100644 --- a/examples/aimsun/sugiyama.py +++ b/examples/aimsun/sugiyama.py @@ -8,7 +8,7 @@ from flow.core.params import AimsunParams, EnvParams, InitialConfig, NetParams from flow.core.params import VehicleParams from flow.envs import TestEnv -from flow.scenarios.loop import LoopScenario, ADDITIONAL_NET_PARAMS +from flow.scenarios.ring import RingScenario, ADDITIONAL_NET_PARAMS def sugiyama_example(render=None): @@ -43,7 +43,7 @@ def sugiyama_example(render=None): initial_config = InitialConfig(bunching=20) - scenario = LoopScenario( + scenario = RingScenario( name="sugiyama", vehicles=vehicles, net_params=net_params, diff --git a/examples/rllib/cooperative_merge.py b/examples/rllib/cooperative_merge.py deleted file mode 100644 index 4416c7269c..0000000000 --- a/examples/rllib/cooperative_merge.py +++ /dev/null @@ -1,199 +0,0 @@ -"""Trains vehicles to facilitate cooperative merging in a loop merge. - -This examples consists of 1 learning agent and 6 additional vehicles in an -inner ring, and 10 vehicles in an outer ring attempting to -merge into the inner ring. -""" - -import json - -import ray -try: - from ray.rllib.agents.agent import get_agent_class -except ImportError: - from ray.rllib.agents.registry import get_agent_class -from ray.tune import run_experiments -from ray.tune.registry import register_env - -from flow.controllers import RLController -from flow.controllers import IDMController -from flow.controllers import ContinuousRouter -from flow.controllers import SimLaneChangeController -from flow.core.params import SumoCarFollowingParams -from flow.core.params import SumoLaneChangeParams -from flow.core.params import SumoParams -from flow.core.params import EnvParams -from flow.core.params import InitialConfig -from flow.core.params import NetParams - -from flow.utils.registry import make_create_env -from flow.utils.rllib import FlowParamsEncoder -from flow.core.params import VehicleParams - -# time horizon of a single rollout -HORIZON = 100 -# number of rollouts per training iteration -N_ROLLOUTS = 10 -# number of parallel workers -N_CPUS = 2 - -RING_RADIUS = 100 -NUM_MERGE_HUMANS = 9 -NUM_MERGE_RL = 1 - -# note that the vehicles are added sequentially by the scenario, -# so place the merging vehicles after the vehicles in the ring -vehicles = VehicleParams() -# Inner ring vehicles -vehicles.add( - veh_id='human', - acceleration_controller=(IDMController, { - 'noise': 0.2 - }), - lane_change_controller=(SimLaneChangeController, {}), - routing_controller=(ContinuousRouter, {}), - num_vehicles=6, - car_following_params=SumoCarFollowingParams(minGap=0.0, tau=0.5), - lane_change_params=SumoLaneChangeParams()) -# A single learning agent in the inner ring -vehicles.add( - veh_id='rl', - acceleration_controller=(RLController, {}), - lane_change_controller=(SimLaneChangeController, {}), - routing_controller=(ContinuousRouter, {}), - num_vehicles=1, - car_following_params=SumoCarFollowingParams( - minGap=0.01, - tau=0.5, - speed_mode="obey_safe_speed", - ), - lane_change_params=SumoLaneChangeParams()) -# Outer ring vehicles -vehicles.add( - veh_id='merge-human', - acceleration_controller=(IDMController, { - 'noise': 0.2 - }), - lane_change_controller=(SimLaneChangeController, {}), - routing_controller=(ContinuousRouter, {}), - num_vehicles=10, - car_following_params=SumoCarFollowingParams(minGap=0.0, tau=0.5), - lane_change_params=SumoLaneChangeParams()) - -flow_params = dict( - # name of the experiment - exp_tag='cooperative_merge', - - # name of the flow environment the experiment is running on - env_name='AccelEnv', - - # name of the scenario class the experiment is running on - scenario='TwoLoopsOneMergingScenario', - - # simulator that is used by the experiment - simulator='traci', - - # sumo-related parameters (see flow.core.params.SumoParams) - sim=SumoParams( - sim_step=0.1, - render=False, - ), - - # environment related parameters (see flow.core.params.EnvParams) - env=EnvParams( - horizon=HORIZON, - additional_params={ - "target_velocity": 10, - "max_accel": 3, - "max_decel": 3, - "sort_vehicles": False - }, - ), - - # network-related parameters (see flow.core.params.NetParams and the - # scenario's documentation or ADDITIONAL_NET_PARAMS component) - net=NetParams( - additional_params={ - 'ring_radius': 50, - 'lane_length': 75, - 'inner_lanes': 1, - 'outer_lanes': 1, - 'speed_limit': 30, - 'resolution': 40, - }, - ), - - # vehicles to be placed in the network at the start of a rollout (see - # flow.core.params.VehicleParams) - veh=vehicles, - - # parameters specifying the positioning of vehicles upon initialization/ - # reset (see flow.core.params.InitialConfig) - initial=InitialConfig( - x0=50, - spacing='uniform', - additional_params={ - 'merge_bunching': 0, - }, - ), -) - - -def setup_exps(): - """Return the relevant components of an RLlib experiment. - - Returns - ------- - str - name of the training algorithm - str - name of the gym environment to be trained - dict - training configuration parameters - """ - alg_run = 'PPO' - - agent_cls = get_agent_class(alg_run) - config = agent_cls._default_config.copy() - config['num_workers'] = N_CPUS - config['train_batch_size'] = HORIZON * N_ROLLOUTS - config['gamma'] = 0.999 # discount rate - config['model'].update({'fcnet_hiddens': [16, 16, 16]}) - config['use_gae'] = True - config['lambda'] = 0.97 - config['kl_target'] = 0.02 - config['num_sgd_iter'] = 10 - config['clip_actions'] = False # FIXME(ev) temporary ray bug - config['horizon'] = HORIZON - - # save the flow params for replay - flow_json = json.dumps( - flow_params, cls=FlowParamsEncoder, sort_keys=True, indent=4) - config['env_config']['flow_params'] = flow_json - config['env_config']['run'] = alg_run - - create_env, gym_name = make_create_env(params=flow_params, version=0) - - # Register as rllib env - register_env(gym_name, create_env) - return alg_run, gym_name, config - - -if __name__ == '__main__': - alg_run, gym_name, config = setup_exps() - ray.init(num_cpus=N_CPUS+1, redirect_output=False) - trials = run_experiments({ - flow_params['exp_tag']: { - 'run': alg_run, - 'env': gym_name, - 'config': { - **config - }, - 'checkpoint_freq': 20, - "checkpoint_at_end": True, - 'max_failures': 999, - 'stop': { - 'training_iteration': 200, - }, - } - }) diff --git a/examples/rllib/figure_eight.py b/examples/rllib/figure_eight.py index f4c20e0a8f..55bfcd0522 100644 --- a/examples/rllib/figure_eight.py +++ b/examples/rllib/figure_eight.py @@ -56,7 +56,7 @@ env_name='AccelEnv', # name of the scenario class the experiment is running on - scenario='Figure8Scenario', + scenario='FigureEightScenario', # simulator that is used by the experiment simulator='traci', diff --git a/examples/rllib/multiagent_exps/multiagent_figure_eight.py b/examples/rllib/multiagent_exps/multiagent_figure_eight.py index ca6d4ffe77..dc23e17271 100644 --- a/examples/rllib/multiagent_exps/multiagent_figure_eight.py +++ b/examples/rllib/multiagent_exps/multiagent_figure_eight.py @@ -69,7 +69,7 @@ env_name='MultiAgentAccelEnv', # name of the scenario class the experiment is running on - scenario='Figure8Scenario', + scenario='FigureEightScenario', # simulator that is used by the experiment simulator='traci', diff --git a/examples/rllib/multiagent_exps/multiagent_highway.py b/examples/rllib/multiagent_exps/multiagent_highway.py index 08732e9a84..a15580d6eb 100644 --- a/examples/rllib/multiagent_exps/multiagent_highway.py +++ b/examples/rllib/multiagent_exps/multiagent_highway.py @@ -22,7 +22,7 @@ from flow.utils.registry import make_create_env from flow.utils.rllib import FlowParamsEncoder -from flow.envs.loop.loop_accel import ADDITIONAL_ENV_PARAMS +from flow.envs.ring.accel import ADDITIONAL_ENV_PARAMS from flow.scenarios.highway_ramps import ADDITIONAL_NET_PARAMS diff --git a/examples/rllib/multiagent_exps/multiagent_stabilizing_the_ring.py b/examples/rllib/multiagent_exps/multiagent_stabilizing_the_ring.py index d8e621f04b..2b79b3bb25 100644 --- a/examples/rllib/multiagent_exps/multiagent_stabilizing_the_ring.py +++ b/examples/rllib/multiagent_exps/multiagent_stabilizing_the_ring.py @@ -61,7 +61,7 @@ env_name='MultiWaveAttenuationPOEnv', # name of the scenario class the experiment is running on - scenario='MultiLoopScenario', + scenario='MultiRingScenario', # simulator that is used by the experiment simulator='traci', diff --git a/examples/rllib/multiagent_exps/multiagent_traffic_light_grid.py b/examples/rllib/multiagent_exps/multiagent_traffic_light_grid.py index ca9a75df36..b3030e5172 100644 --- a/examples/rllib/multiagent_exps/multiagent_traffic_light_grid.py +++ b/examples/rllib/multiagent_exps/multiagent_traffic_light_grid.py @@ -27,7 +27,7 @@ # Environment parameters HORIZON = 400 # time horizon of a single rollout V_ENTER = 30 # enter speed for departing vehicles -INNER_LENGTH = 300 # length of inner edges in the grid network +INNER_LENGTH = 300 # length of inner edges in the traffic light grid network LONG_LENGTH = 100 # length of final edge in route SHORT_LENGTH = 300 # length of edges that vehicles start on # number of vehicles originating in the left, right, top, and bottom edges @@ -41,9 +41,9 @@ def make_flow_params(n_rows, n_columns, edge_inflow): Parameters ---------- n_rows : int - number of rows in the grid + number of rows in the traffic light grid n_columns : int - number of columns in the grid + number of columns in the traffic light grid edge_inflow : float @@ -95,7 +95,7 @@ def make_flow_params(n_rows, n_columns, edge_inflow): env_name='MultiTrafficLightGridPOEnv', # name of the scenario class the experiment is running on - scenario="SimpleGridScenario", + scenario="TrafficLightGridScenario", # simulator that is used by the experiment simulator='traci', @@ -243,9 +243,9 @@ def policy_mapping_fn(_): parser.add_argument('--algo', type=str, default='PPO', help="RL method to use (PPO)") parser.add_argument('--num_rows', type=int, default=3, - help="The number of rows in the grid network.") + help="The number of rows in the traffic light grid network.") parser.add_argument('--num_cols', type=int, default=3, - help="The number of columns in the grid network.") + help="The number of columns in the traffic light grid network.") parser.add_argument('--inflow_rate', type=int, default=300, help="The inflow rate (veh/hr) per edge.") args = parser.parse_args() diff --git a/examples/rllib/stabilizing_highway.py b/examples/rllib/stabilizing_highway.py index 8bf8144e09..c5740eee6e 100644 --- a/examples/rllib/stabilizing_highway.py +++ b/examples/rllib/stabilizing_highway.py @@ -94,7 +94,7 @@ exp_tag="stabilizing_open_network_merges", # name of the flow environment the experiment is running on - env_name="WaveAttenuationMergePOEnv", + env_name="MergePOEnv", # name of the scenario class the experiment is running on scenario="MergeScenario", diff --git a/examples/rllib/stabilizing_the_ring.py b/examples/rllib/stabilizing_the_ring.py index e5d2479a04..b4731e9c32 100644 --- a/examples/rllib/stabilizing_the_ring.py +++ b/examples/rllib/stabilizing_the_ring.py @@ -53,7 +53,7 @@ env_name="WaveAttenuationPOEnv", # name of the scenario class the experiment is running on - scenario="LoopScenario", + scenario="RingScenario", # simulator that is used by the experiment simulator='traci', diff --git a/examples/rllib/green_wave.py b/examples/rllib/traffic_light_grid.py similarity index 93% rename from examples/rllib/green_wave.py rename to examples/rllib/traffic_light_grid.py index fead73c6a3..867a474f51 100644 --- a/examples/rllib/green_wave.py +++ b/examples/rllib/traffic_light_grid.py @@ -1,4 +1,4 @@ -"""Grid/green wave example.""" +"""Traffic Light Grid example.""" import json @@ -26,14 +26,14 @@ def gen_edges(col_num, row_num): - """Generate the names of the outer edges in the grid network. + """Generate the names of the outer edges in the traffic light grid network. Parameters ---------- col_num : int - number of columns in the grid + number of columns in the traffic light grid row_num : int - number of rows in the grid + number of rows in the traffic light grid Returns ------- @@ -59,11 +59,11 @@ def get_flow_params(col_num, row_num, additional_net_params): Parameters ---------- col_num : int - number of columns in the grid + number of columns in the traffic light grid row_num : int - number of rows in the grid + number of rows in the traffic light grid additional_net_params : dict - network-specific parameters that are unique to the grid + network-specific parameters that are unique to the traffic light grid Returns ------- @@ -105,7 +105,7 @@ def get_non_flow_params(enter_speed, add_net_params): enter_speed : float initial speed of vehicles as they enter the network. add_net_params: dict - additional network-specific parameters (unique to the grid) + additional network-specific parameters (unique to the traffic light grid) Returns ------- @@ -178,13 +178,13 @@ def get_non_flow_params(enter_speed, add_net_params): flow_params = dict( # name of the experiment - exp_tag='green_wave', + exp_tag='traffic_light_grid', # name of the flow environment the experiment is running on - env_name='PO_TrafficLightGridEnv', + env_name='TrafficLightGridPOEnv', # name of the scenario class the experiment is running on - scenario='SimpleGridScenario', + scenario='TrafficLightGridScenario', # simulator that is used by the experiment simulator='traci', diff --git a/examples/rllib/velocity_bottleneck.py b/examples/rllib/velocity_bottleneck.py index 274d4d4381..77c2c7195a 100644 --- a/examples/rllib/velocity_bottleneck.py +++ b/examples/rllib/velocity_bottleneck.py @@ -111,7 +111,7 @@ exp_tag="DesiredVelocity", # name of the flow environment the experiment is running on - env_name="DesiredVelocityEnv", + env_name="BottleneckDesiredVelocityEnv", # name of the scenario class the experiment is running on scenario="BottleneckScenario", diff --git a/examples/stable_baselines/figure_eight.py b/examples/stable_baselines/figure_eight.py index 6695cc9e55..b46095305f 100644 --- a/examples/stable_baselines/figure_eight.py +++ b/examples/stable_baselines/figure_eight.py @@ -54,7 +54,7 @@ env_name='AccelEnv', # name of the scenario class the experiment is running on - scenario='Figure8Scenario', + scenario='FigureEightScenario', # simulator that is used by the experiment simulator='traci', diff --git a/examples/stable_baselines/stabilizing_highway.py b/examples/stable_baselines/stabilizing_highway.py index 0baff4dd12..0b9c8af4b6 100644 --- a/examples/stable_baselines/stabilizing_highway.py +++ b/examples/stable_baselines/stabilizing_highway.py @@ -90,7 +90,7 @@ exp_tag="stabilizing_open_network_merges", # name of the flow environment the experiment is running on - env_name="WaveAttenuationMergePOEnv", + env_name="MergePOEnv", # name of the scenario class the experiment is running on scenario="MergeScenario", diff --git a/examples/stable_baselines/stabilizing_the_ring.py b/examples/stable_baselines/stabilizing_the_ring.py index dd4d170077..74bd2f9672 100644 --- a/examples/stable_baselines/stabilizing_the_ring.py +++ b/examples/stable_baselines/stabilizing_the_ring.py @@ -46,7 +46,7 @@ env_name="WaveAttenuationPOEnv", # name of the scenario class the experiment is running on - scenario="LoopScenario", + scenario="RingScenario", # simulator that is used by the experiment simulator='traci', diff --git a/examples/stable_baselines/green_wave.py b/examples/stable_baselines/traffic_light_grid.py similarity index 92% rename from examples/stable_baselines/green_wave.py rename to examples/stable_baselines/traffic_light_grid.py index 768284d40b..e88d84ea85 100644 --- a/examples/stable_baselines/green_wave.py +++ b/examples/stable_baselines/traffic_light_grid.py @@ -1,4 +1,4 @@ -"""Grid/green wave example.""" +"""Traffic Light Grid example.""" import argparse import json @@ -19,14 +19,14 @@ def gen_edges(col_num, row_num): - """Generate the names of the outer edges in the grid network. + """Generate the names of the outer edges in the traffic light grid network. Parameters ---------- col_num : int - number of columns in the grid + number of columns in the traffic light grid row_num : int - number of rows in the grid + number of rows in the traffic light grid Returns ------- @@ -52,11 +52,11 @@ def get_inflow_params(col_num, row_num, additional_net_params): Parameters ---------- col_num : int - number of columns in the grid + number of columns in the traffic light grid row_num : int - number of rows in the grid + number of rows in the traffic light grid additional_net_params : dict - network-specific parameters that are unique to the grid + network-specific parameters that are unique to the traffic light grid Returns ------- @@ -98,7 +98,7 @@ def get_non_flow_params(enter_speed, add_net_params): enter_speed : float initial speed of vehicles as they enter the network. add_net_params: dict - additional network-specific parameters (unique to the grid) + additional network-specific parameters (unique to the traffic light grid) Returns ------- @@ -172,13 +172,13 @@ def get_non_flow_params(enter_speed, add_net_params): flow_params = dict( # name of the experiment - exp_tag='green_wave', + exp_tag='traffic_light_grid', # name of the flow environment the experiment is running on - env_name='PO_TrafficLightGridEnv', + env_name='TrafficLightGridPOEnv', # name of the scenario class the experiment is running on - scenario='SimpleGridScenario', + scenario='TrafficLightGridScenario', # simulator that is used by the experiment simulator='traci', @@ -266,7 +266,7 @@ def run_model(num_cpus=1, rollout_size=50, num_steps=50, use_inflows=False): parser.add_argument('--num_cpus', type=int, default=1, help='How many CPUs to use') parser.add_argument('--num_steps', type=int, default=5000, help='How many total steps to perform learning over') parser.add_argument('--rollout_size', type=int, default=1000, help='How many steps are in a training batch.') - parser.add_argument('--result_name', type=str, default='green_wave', help='Name of saved model') + parser.add_argument('--result_name', type=str, default='traffic_light_grid', help='Name of saved model') parser.add_argument('--use_inflows', action='store_true') args = parser.parse_args() model = run_model(args.num_cpus, args.rollout_size, args.num_steps, args.use_inflows) diff --git a/examples/stable_baselines/velocity_bottleneck.py b/examples/stable_baselines/velocity_bottleneck.py index 1cebe2eb0a..dea86a3a5c 100644 --- a/examples/stable_baselines/velocity_bottleneck.py +++ b/examples/stable_baselines/velocity_bottleneck.py @@ -108,7 +108,7 @@ exp_tag="DesiredVelocity", # name of the flow environment the experiment is running on - env_name="DesiredVelocityEnv", + env_name="BottleneckDesiredVelocityEnv", # name of the scenario class the experiment is running on scenario="BottleneckScenario", diff --git a/examples/sumo/bay_bridge.py b/examples/sumo/bay_bridge.py index 86da880342..8acf826297 100644 --- a/examples/sumo/bay_bridge.py +++ b/examples/sumo/bay_bridge.py @@ -9,7 +9,7 @@ from flow.core.params import TrafficLightParams from flow.core.experiment import Experiment -from flow.envs.bay_bridge.base import BayBridgeEnv +from flow.envs.bay_bridge import BayBridgeEnv from flow.scenarios.bay_bridge import BayBridgeScenario, EDGES_DISTRIBUTION from flow.controllers import SimCarFollowingController, BayBridgeRouter diff --git a/examples/sumo/bay_bridge_toll.py b/examples/sumo/bay_bridge_toll.py index b41a40ac80..944ea990ed 100644 --- a/examples/sumo/bay_bridge_toll.py +++ b/examples/sumo/bay_bridge_toll.py @@ -8,7 +8,7 @@ from flow.core.params import VehicleParams from flow.core.experiment import Experiment -from flow.envs.bay_bridge.base import BayBridgeEnv +from flow.envs.bay_bridge import BayBridgeEnv from flow.scenarios.bay_bridge_toll import BayBridgeTollScenario from flow.scenarios.bay_bridge_toll import EDGES_DISTRIBUTION diff --git a/examples/sumo/bottlenecks.py b/examples/sumo/bottlenecks.py index b414a6c23c..1edd0eaa67 100644 --- a/examples/sumo/bottlenecks.py +++ b/examples/sumo/bottlenecks.py @@ -7,7 +7,7 @@ from flow.scenarios.bottleneck import BottleneckScenario from flow.controllers import SimLaneChangeController, ContinuousRouter -from flow.envs.bottleneck_env import BottleneckEnv +from flow.envs.bottleneck import BottleneckEnv from flow.core.experiment import Experiment import logging diff --git a/examples/sumo/figure_eight.py b/examples/sumo/figure_eight.py index 44f53fe779..3200f13226 100755 --- a/examples/sumo/figure_eight.py +++ b/examples/sumo/figure_eight.py @@ -9,8 +9,8 @@ from flow.core.params import SumoParams, EnvParams, NetParams, \ SumoCarFollowingParams from flow.core.params import VehicleParams -from flow.envs.loop.loop_accel import AccelEnv, ADDITIONAL_ENV_PARAMS -from flow.scenarios.figure_eight import Figure8Scenario, ADDITIONAL_NET_PARAMS +from flow.envs.ring.accel import AccelEnv, ADDITIONAL_ENV_PARAMS +from flow.scenarios.figure_eight import FigureEightScenario, ADDITIONAL_NET_PARAMS def figure_eight_example(render=None): @@ -51,8 +51,8 @@ def figure_eight_example(render=None): additional_net_params = ADDITIONAL_NET_PARAMS.copy() net_params = NetParams(additional_params=additional_net_params) - scenario = Figure8Scenario( - name="figure8", + scenario = FigureEightScenario( + name="FigureEight", vehicles=vehicles, net_params=net_params) diff --git a/examples/sumo/highway.py b/examples/sumo/highway.py index d8c675393b..9476902e7e 100644 --- a/examples/sumo/highway.py +++ b/examples/sumo/highway.py @@ -5,7 +5,7 @@ from flow.core.params import SumoParams, EnvParams, \ NetParams, InitialConfig, InFlows from flow.core.params import VehicleParams -from flow.envs.loop.lane_changing import LaneChangeAccelEnv, \ +from flow.envs.ring.lane_change_accel import LaneChangeAccelEnv, \ ADDITIONAL_ENV_PARAMS from flow.scenarios.highway import HighwayScenario, ADDITIONAL_NET_PARAMS diff --git a/examples/sumo/highway_ramps.py b/examples/sumo/highway_ramps.py index 4db47be76f..40663dcd01 100644 --- a/examples/sumo/highway_ramps.py +++ b/examples/sumo/highway_ramps.py @@ -7,7 +7,7 @@ from flow.core.experiment import Experiment from flow.scenarios.highway_ramps import HighwayRampsScenario, \ ADDITIONAL_NET_PARAMS -from flow.envs.loop.loop_accel import AccelEnv, ADDITIONAL_ENV_PARAMS +from flow.envs.ring.accel import AccelEnv, ADDITIONAL_ENV_PARAMS additional_net_params = ADDITIONAL_NET_PARAMS.copy() diff --git a/examples/sumo/loop_merge.py b/examples/sumo/loop_merge.py deleted file mode 100755 index 5202e7f0ee..0000000000 --- a/examples/sumo/loop_merge.py +++ /dev/null @@ -1,91 +0,0 @@ -"""Example of ring road with larger merging ring.""" - -from flow.controllers import IDMController, SimLaneChangeController, \ - ContinuousRouter -from flow.core.experiment import Experiment -from flow.core.params import SumoParams, EnvParams, InitialConfig, NetParams, \ - SumoCarFollowingParams, SumoLaneChangeParams -from flow.core.params import VehicleParams -from flow.envs.loop.loop_accel import AccelEnv, ADDITIONAL_ENV_PARAMS -from flow.scenarios.loop_merge import TwoLoopsOneMergingScenario, \ - ADDITIONAL_NET_PARAMS - - -def loop_merge_example(render=None): - """ - Perform a simulation of vehicles on a loop merge. - - Parameters - ---------- - render : bool, optional - specifies whether to use the gui during execution - - Returns - ------- - exp: flow.core.experiment.Experiment - A non-rl experiment demonstrating the performance of human-driven - vehicles on a loop merge. - """ - sim_params = SumoParams( - sim_step=0.1, emission_path="./data/", render=True) - - if render is not None: - sim_params.render = render - - # note that the vehicles are added sequentially by the scenario, - # so place the merging vehicles after the vehicles in the ring - vehicles = VehicleParams() - vehicles.add( - veh_id="idm", - acceleration_controller=(IDMController, {}), - lane_change_controller=(SimLaneChangeController, {}), - routing_controller=(ContinuousRouter, {}), - num_vehicles=7, - car_following_params=SumoCarFollowingParams( - minGap=0.0, - tau=0.5, - speed_mode="obey_safe_speed", - ), - lane_change_params=SumoLaneChangeParams()) - vehicles.add( - veh_id="merge-idm", - acceleration_controller=(IDMController, {}), - lane_change_controller=(SimLaneChangeController, {}), - routing_controller=(ContinuousRouter, {}), - num_vehicles=10, - car_following_params=SumoCarFollowingParams( - minGap=0.01, - tau=0.5, - speed_mode="obey_safe_speed", - ), - lane_change_params=SumoLaneChangeParams()) - - env_params = EnvParams(additional_params=ADDITIONAL_ENV_PARAMS) - - additional_net_params = ADDITIONAL_NET_PARAMS.copy() - additional_net_params["ring_radius"] = 50 - additional_net_params["inner_lanes"] = 1 - additional_net_params["outer_lanes"] = 1 - additional_net_params["lane_length"] = 75 - net_params = NetParams(additional_params=additional_net_params) - - initial_config = InitialConfig( - x0=50, spacing="uniform", additional_params={"merge_bunching": 0}) - - scenario = TwoLoopsOneMergingScenario( - name="two-loop-one-merging", - vehicles=vehicles, - net_params=net_params, - initial_config=initial_config) - - env = AccelEnv(env_params, sim_params, scenario) - - return Experiment(env) - - -if __name__ == "__main__": - # import the experiment variable - exp = loop_merge_example() - - # run for a set number of rollouts / time steps - exp.run(1, 1500, convert_to_csv=True) diff --git a/examples/sumo/merge.py b/examples/sumo/merge.py index 51614decf0..a1868072c9 100644 --- a/examples/sumo/merge.py +++ b/examples/sumo/merge.py @@ -11,7 +11,7 @@ from flow.core.experiment import Experiment from flow.scenarios.merge import MergeScenario, ADDITIONAL_NET_PARAMS from flow.controllers import IDMController -from flow.envs.merge import WaveAttenuationMergePOEnv, ADDITIONAL_ENV_PARAMS +from flow.envs.merge import MergePOEnv, ADDITIONAL_ENV_PARAMS # inflow rate at the highway FLOW_RATE = 2000 @@ -87,7 +87,7 @@ def merge_example(render=None): net_params=net_params, initial_config=initial_config) - env = WaveAttenuationMergePOEnv(env_params, sim_params, scenario) + env = MergePOEnv(env_params, sim_params, scenario) return Experiment(env) diff --git a/examples/sumo/minicity.py b/examples/sumo/minicity.py index 4733037e38..a1c25e8027 100644 --- a/examples/sumo/minicity.py +++ b/examples/sumo/minicity.py @@ -5,7 +5,7 @@ from flow.core.params import SumoParams, EnvParams, NetParams, InitialConfig from flow.core.params import SumoCarFollowingParams, SumoLaneChangeParams from flow.core.params import VehicleParams -from flow.envs.loop.loop_accel import AccelEnv, ADDITIONAL_ENV_PARAMS +from flow.envs.ring.accel import AccelEnv, ADDITIONAL_ENV_PARAMS from flow.scenarios.minicity import MiniCityScenario from flow.controllers.routing_controllers import MinicityRouter import numpy as np diff --git a/examples/sumo/sugiyama.py b/examples/sumo/sugiyama.py index 5a3eb31b80..586a7c6961 100755 --- a/examples/sumo/sugiyama.py +++ b/examples/sumo/sugiyama.py @@ -8,8 +8,8 @@ from flow.core.params import SumoParams, EnvParams, \ InitialConfig, NetParams, SumoCarFollowingParams from flow.core.params import VehicleParams -from flow.envs.loop.loop_accel import AccelEnv, ADDITIONAL_ENV_PARAMS -from flow.scenarios.loop import LoopScenario, ADDITIONAL_NET_PARAMS +from flow.envs.ring.accel import AccelEnv, ADDITIONAL_ENV_PARAMS +from flow.scenarios.ring import RingScenario, ADDITIONAL_NET_PARAMS def sugiyama_example(render=None): @@ -49,7 +49,7 @@ def sugiyama_example(render=None): initial_config = InitialConfig(bunching=20) - scenario = LoopScenario( + scenario = RingScenario( name="sugiyama", vehicles=vehicles, net_params=net_params, diff --git a/examples/sumo/grid.py b/examples/sumo/traffic_light_grid.py similarity index 87% rename from examples/sumo/grid.py rename to examples/sumo/traffic_light_grid.py index 3a3ffccd63..2206f18fd2 100644 --- a/examples/sumo/grid.py +++ b/examples/sumo/traffic_light_grid.py @@ -1,4 +1,4 @@ -"""Grid example.""" +"""Traffic Light Grid example.""" from flow.controllers import GridRouter from flow.core.experiment import Experiment from flow.core.params import SumoParams, EnvParams, InitialConfig, NetParams @@ -6,19 +6,19 @@ from flow.core.params import TrafficLightParams from flow.core.params import SumoCarFollowingParams from flow.core.params import InFlows -from flow.envs.loop.loop_accel import AccelEnv, ADDITIONAL_ENV_PARAMS -from flow.scenarios.grid import SimpleGridScenario +from flow.envs.ring.accel import AccelEnv, ADDITIONAL_ENV_PARAMS +from flow.scenarios.traffic_light_grid import TrafficLightGridScenario def gen_edges(col_num, row_num): - """Generate the names of the outer edges in the grid network. + """Generate the names of the outer edges in the traffic light grid network. Parameters ---------- col_num : int - number of columns in the grid + number of columns in the traffic light grid row_num : int - number of rows in the grid + number of rows in the traffic light grid Returns ------- @@ -46,11 +46,11 @@ def get_flow_params(col_num, row_num, additional_net_params): Parameters ---------- col_num : int - number of columns in the grid + number of columns in the traffic light grid row_num : int - number of rows in the grid + number of rows in the traffic light grid additional_net_params : dict - network-specific parameters that are unique to the grid + network-specific parameters that are unique to the traffic light grid Returns ------- @@ -92,7 +92,7 @@ def get_non_flow_params(enter_speed, add_net_params): enter_speed : float initial speed of vehicles as they enter the network. add_net_params: dict - additional network-specific parameters (unique to the grid) + additional network-specific parameters (unique to the traffic light grid) Returns ------- @@ -110,9 +110,9 @@ def get_non_flow_params(enter_speed, add_net_params): return initial, net -def grid_example(render=None, use_inflows=False): +def traffic_light_grid_example(render=None, use_inflows=False): """ - Perform a simulation of vehicles on a grid. + Perform a simulation of vehicles on a traffic light grid. Parameters ---------- @@ -126,7 +126,7 @@ def grid_example(render=None, use_inflows=False): ------- exp: flow.core.experiment.Experiment A non-rl experiment demonstrating the performance of human-driven - vehicles and balanced traffic lights on a grid. + vehicles and balanced traffic lights on a traffic light grid. """ v_enter = 10 inner_length = 300 @@ -213,7 +213,7 @@ def grid_example(render=None, use_inflows=False): enter_speed=v_enter, add_net_params=additional_net_params) - scenario = SimpleGridScenario( + scenario = TrafficLightGridScenario( name="grid-intersection", vehicles=vehicles, net_params=net_params, @@ -227,7 +227,7 @@ def grid_example(render=None, use_inflows=False): if __name__ == "__main__": # import the experiment variable - exp = grid_example() + exp = traffic_light_grid_example() # run for a set number of rollouts / time steps exp.run(1, 1500) diff --git a/flow/benchmarks/README.md b/flow/benchmarks/README.md index f7851a51c6..963ad5b706 100644 --- a/flow/benchmarks/README.md +++ b/flow/benchmarks/README.md @@ -25,13 +25,13 @@ tasked with the objective of dissipating the formation and propagation of - `flow.benchmarks.merge1` 25% CAV penetration rate, S=(65,), A=(13,), T=750. - `flow.benchmarks.merge2` 33.3% CAV penetration rate, S=(85,), A=(17,), T=750. -**Grid (improving traffic signal timing schedules):** Traffic lights in a an -idealized representation of a city with a grid-like structure such as Manhattan -are controlled in intervals of 2 seconds, with the objective of minimizing -delays for drivers. -- `flow.benchmarks.grid0` 3x3 grid (9 traffic lights), +**Traffic Light Grid (improving traffic signal timing schedules):** Traffic +lights in a an idealized representation of a city with a grid-like structure +such as Manhattan are controlled in intervals of 2 seconds, with the objective +of minimizing delays for drivers. +- `flow.benchmarks.grid0` 3x3 traffic light grid (9 traffic lights), inflow = 300 veh/hour/lane S=(339,), A=(9,), T=400. -- `flow.benchmarks.grid1` 5x5 grid (25 traffic lights), +- `flow.benchmarks.grid1` 5x5 traffic light grid (25 traffic lights), inflow = 300 veh/hour/lane S=(915,), A=(25,), T=400. **Bottleneck (maximizing throughput in a bottleneck structure):** The goal of diff --git a/flow/benchmarks/bottleneck0.py b/flow/benchmarks/bottleneck0.py index d0179c23bb..84e8789e29 100644 --- a/flow/benchmarks/bottleneck0.py +++ b/flow/benchmarks/bottleneck0.py @@ -98,7 +98,7 @@ exp_tag="bottleneck_0", # name of the flow environment the experiment is running on - env_name="DesiredVelocityEnv", + env_name="BottleneckDesiredVelocityEnv", # name of the scenario class the experiment is running on scenario="BottleneckScenario", diff --git a/flow/benchmarks/bottleneck1.py b/flow/benchmarks/bottleneck1.py index bbe284e8fd..c9179e0faf 100644 --- a/flow/benchmarks/bottleneck1.py +++ b/flow/benchmarks/bottleneck1.py @@ -98,7 +98,7 @@ exp_tag="bottleneck_1", # name of the flow environment the experiment is running on - env_name="DesiredVelocityEnv", + env_name="BottleneckDesiredVelocityEnv", # name of the scenario class the experiment is running on scenario="BottleneckScenario", diff --git a/flow/benchmarks/bottleneck2.py b/flow/benchmarks/bottleneck2.py index bce104e11c..33c6955161 100644 --- a/flow/benchmarks/bottleneck2.py +++ b/flow/benchmarks/bottleneck2.py @@ -98,7 +98,7 @@ exp_tag="bottleneck_2", # name of the flow environment the experiment is running on - env_name="DesiredVelocityEnv", + env_name="BottleneckDesiredVelocityEnv", # name of the scenario class the experiment is running on scenario="BottleneckScenario", diff --git a/flow/benchmarks/figureeight0.py b/flow/benchmarks/figureeight0.py index 21c3feefe1..13f069b19a 100644 --- a/flow/benchmarks/figureeight0.py +++ b/flow/benchmarks/figureeight0.py @@ -49,7 +49,7 @@ env_name="AccelEnv", # name of the scenario class the experiment is running on - scenario="Figure8Scenario", + scenario="FigureEightScenario", # simulator that is used by the experiment simulator='traci', diff --git a/flow/benchmarks/figureeight1.py b/flow/benchmarks/figureeight1.py index 804aeb6e41..14800da83e 100644 --- a/flow/benchmarks/figureeight1.py +++ b/flow/benchmarks/figureeight1.py @@ -50,7 +50,7 @@ env_name="AccelEnv", # name of the scenario class the experiment is running on - scenario="Figure8Scenario", + scenario="FigureEightScenario", # simulator that is used by the experiment simulator='traci', diff --git a/flow/benchmarks/figureeight2.py b/flow/benchmarks/figureeight2.py index 53ed9463ea..612837d0e2 100644 --- a/flow/benchmarks/figureeight2.py +++ b/flow/benchmarks/figureeight2.py @@ -38,7 +38,7 @@ env_name="AccelEnv", # name of the scenario class the experiment is running on - scenario="Figure8Scenario", + scenario="FigureEightScenario", # simulator that is used by the experiment simulator='traci', diff --git a/flow/benchmarks/grid0.py b/flow/benchmarks/grid0.py index 7de96af8b0..5e3d083219 100644 --- a/flow/benchmarks/grid0.py +++ b/flow/benchmarks/grid0.py @@ -67,10 +67,10 @@ exp_tag="grid_0", # name of the flow environment the experiment is running on - env_name="PO_TrafficLightGridEnv", + env_name="TrafficLightGridPOEnv", # name of the scenario class the experiment is running on - scenario="SimpleGridScenario", + scenario="TrafficLightGridScenario", # simulator that is used by the experiment simulator='traci', diff --git a/flow/benchmarks/grid1.py b/flow/benchmarks/grid1.py index aaf683d685..b6043ded5d 100644 --- a/flow/benchmarks/grid1.py +++ b/flow/benchmarks/grid1.py @@ -67,10 +67,10 @@ exp_tag="grid_1", # name of the flow environment the experiment is running on - env_name="PO_TrafficLightGridEnv", + env_name="TrafficLightGridPOEnv", # name of the scenario class the experiment is running on - scenario="SimpleGridScenario", + scenario="TrafficLightGridScenario", # simulator that is used by the experiment simulator='traci', diff --git a/flow/benchmarks/merge0.py b/flow/benchmarks/merge0.py index 6f7b8cde63..70ab67962f 100644 --- a/flow/benchmarks/merge0.py +++ b/flow/benchmarks/merge0.py @@ -76,7 +76,7 @@ exp_tag="merge_0", # name of the flow environment the experiment is running on - env_name="WaveAttenuationMergePOEnv", + env_name="MergePOEnv", # name of the scenario class the experiment is running on scenario="MergeScenario", diff --git a/flow/benchmarks/merge1.py b/flow/benchmarks/merge1.py index 27a47de370..dad66996f3 100644 --- a/flow/benchmarks/merge1.py +++ b/flow/benchmarks/merge1.py @@ -76,7 +76,7 @@ exp_tag="merge_1", # name of the flow environment the experiment is running on - env_name="WaveAttenuationMergePOEnv", + env_name="MergePOEnv", # name of the scenario class the experiment is running on scenario="MergeScenario", diff --git a/flow/benchmarks/merge2.py b/flow/benchmarks/merge2.py index 7ab7898089..e77fe6daf2 100644 --- a/flow/benchmarks/merge2.py +++ b/flow/benchmarks/merge2.py @@ -76,7 +76,7 @@ exp_tag="merge_2", # name of the flow environment the experiment is running on - env_name="WaveAttenuationMergePOEnv", + env_name="MergePOEnv", # name of the scenario class the experiment is running on scenario="MergeScenario", diff --git a/flow/controllers/base_routing_controller.py b/flow/controllers/base_routing_controller.py index f5f5b34870..b001dc62ee 100755 --- a/flow/controllers/base_routing_controller.py +++ b/flow/controllers/base_routing_controller.py @@ -36,7 +36,7 @@ def choose_route(self, env): Parameters ---------- env : flow.envs.Env - see flow/envs/base_env.py + see flow/envs/base.py Returns ------- diff --git a/flow/controllers/routing_controllers.py b/flow/controllers/routing_controllers.py index ec00eda3f7..ca273289e4 100755 --- a/flow/controllers/routing_controllers.py +++ b/flow/controllers/routing_controllers.py @@ -7,7 +7,7 @@ class ContinuousRouter(BaseRouter): - """A router used to continuously re-route of the vehicle in a closed loop. + """A router used to continuously re-route of the vehicle in a closed ring. This class is useful if vehicles are expected to continuously follow the same route, and repeat said route once it reaches its end. @@ -83,7 +83,7 @@ def choose_route(self, env): class GridRouter(BaseRouter): - """A router used to re-route a vehicle within a grid environment. + """A router used to re-route a vehicle in a traffic light grid environment. Usage ----- diff --git a/flow/controllers/velocity_controllers.py b/flow/controllers/velocity_controllers.py index 6d671dbc7d..999672d7b5 100644 --- a/flow/controllers/velocity_controllers.py +++ b/flow/controllers/velocity_controllers.py @@ -53,7 +53,7 @@ def find_intersection_dist(self, env): Parameters ---------- env : flow.envs.Env - see flow/envs/base_env.py + see flow/envs/base.py Returns ------- diff --git a/flow/core/kernel/scenario/base.py b/flow/core/kernel/scenario/base.py index 946c48713c..795ea555ec 100644 --- a/flow/core/kernel/scenario/base.py +++ b/flow/core/kernel/scenario/base.py @@ -17,7 +17,7 @@ class KernelScenario(object): components needed to simulate a traffic network. This may include network creating configuration files that support the generating of certain traffic networks in a simulator (e.g. sumo), or may be as simple as passing network - features from the scenario class (see flow/scenarios/base_scenario.py) and + features from the scenario class (see flow/scenarios/base.py) and transferring them to the simulator kernel later on. In addition to generating files for network initialization, the scenario @@ -31,7 +31,7 @@ class KernelScenario(object): travel within the network, this can be done by calling the following command: - >>> from flow.envs.base_env import Env + >>> from flow.envs.base import Env >>> env = Env(...) >>> max_speed = env.k.scenario.max_speed() diff --git a/flow/core/kernel/traffic_light/base.py b/flow/core/kernel/traffic_light/base.py index c3fc95c6e1..c89cda9192 100644 --- a/flow/core/kernel/traffic_light/base.py +++ b/flow/core/kernel/traffic_light/base.py @@ -12,7 +12,7 @@ class KernelTrafficLight(object): certain traffic lights at a given time step. This can be done by calling the following method: - >>> from flow.envs.base_env import Env + >>> from flow.envs.base import Env >>> env = Env(...) >>> node_id = 'test_intersection' # name of the node >>> env.k.traffic_light.set_state(node_id, 'r') diff --git a/flow/core/kernel/vehicle/base.py b/flow/core/kernel/vehicle/base.py index b39849cbf9..b1db255aa8 100644 --- a/flow/core/kernel/vehicle/base.py +++ b/flow/core/kernel/vehicle/base.py @@ -26,7 +26,7 @@ class KernelVehicle(object): would like to get the speed of a vehicle from the environment, that can be done by calling: - >>> from flow.envs.base_env import Env + >>> from flow.envs.base import Env >>> env = Env(...) >>> veh_id = "test_car" # name of the vehicle >>> speed = env.k.vehicle.get_speed(veh_id) diff --git a/flow/core/kernel/vehicle/traci.py b/flow/core/kernel/vehicle/traci.py index 12f9d5573e..000f5c1eaf 100644 --- a/flow/core/kernel/vehicle/traci.py +++ b/flow/core/kernel/vehicle/traci.py @@ -143,7 +143,7 @@ def update(self, reset): if veh_id in self.get_ids() and vehicle_obs[veh_id] is not None: # this occurs when a vehicle is actively being removed and # placed again in the network to ensure a constant number of - # total vehicles (e.g. GreenWaveEnv). In this case, the vehicle + # total vehicles (e.g. TrafficLightGridEnv). In this case, the vehicle # is already in the class; its state data just needs to be # updated pass diff --git a/flow/core/params.py b/flow/core/params.py index 021f0340cf..cc1181de26 100755 --- a/flow/core/params.py +++ b/flow/core/params.py @@ -3,7 +3,7 @@ import logging import collections -from flow.utils.flow_warnings import deprecation_warning +from flow.utils.flow_warnings import deprecated_attribute from flow.controllers.car_following_models import SimCarFollowingController from flow.controllers.rlcontroller import RLController from flow.controllers.lane_change_controllers import SimLaneChangeController @@ -828,27 +828,27 @@ def __init__( """Instantiate SumoCarFollowingParams.""" # check for deprecations (minGap) if "minGap" in kwargs: - deprecation_warning(self, "minGap", "min_gap") + deprecated_attribute(self, "minGap", "min_gap") min_gap = kwargs["minGap"] # check for deprecations (maxSpeed) if "maxSpeed" in kwargs: - deprecation_warning(self, "maxSpeed", "max_speed") + deprecated_attribute(self, "maxSpeed", "max_speed") max_speed = kwargs["maxSpeed"] # check for deprecations (speedFactor) if "speedFactor" in kwargs: - deprecation_warning(self, "speedFactor", "speed_factor") + deprecated_attribute(self, "speedFactor", "speed_factor") speed_factor = kwargs["speedFactor"] # check for deprecations (speedDev) if "speedDev" in kwargs: - deprecation_warning(self, "speedDev", "speed_dev") + deprecated_attribute(self, "speedDev", "speed_dev") speed_dev = kwargs["speedDev"] # check for deprecations (carFollowModel) if "carFollowModel" in kwargs: - deprecation_warning(self, "carFollowModel", "car_follow_model") + deprecated_attribute(self, "carFollowModel", "car_follow_model") car_follow_model = kwargs["carFollowModel"] # create a controller_params dict with all the specified parameters @@ -952,69 +952,69 @@ def __init__(self, """Instantiate SumoLaneChangeParams.""" # check for deprecations (lcStrategic) if "lcStrategic" in kwargs: - deprecation_warning(self, "lcStrategic", "lc_strategic") + deprecated_attribute(self, "lcStrategic", "lc_strategic") lc_strategic = kwargs["lcStrategic"] # check for deprecations (lcCooperative) if "lcCooperative" in kwargs: - deprecation_warning(self, "lcCooperative", "lc_cooperative") + deprecated_attribute(self, "lcCooperative", "lc_cooperative") lc_cooperative = kwargs["lcCooperative"] # check for deprecations (lcSpeedGain) if "lcSpeedGain" in kwargs: - deprecation_warning(self, "lcSpeedGain", "lc_speed_gain") + deprecated_attribute(self, "lcSpeedGain", "lc_speed_gain") lc_speed_gain = kwargs["lcSpeedGain"] # check for deprecations (lcKeepRight) if "lcKeepRight" in kwargs: - deprecation_warning(self, "lcKeepRight", "lc_keep_right") + deprecated_attribute(self, "lcKeepRight", "lc_keep_right") lc_keep_right = kwargs["lcKeepRight"] # check for deprecations (lcLookaheadLeft) if "lcLookaheadLeft" in kwargs: - deprecation_warning(self, "lcLookaheadLeft", "lc_look_ahead_left") + deprecated_attribute(self, "lcLookaheadLeft", "lc_look_ahead_left") lc_look_ahead_left = kwargs["lcLookaheadLeft"] # check for deprecations (lcSpeedGainRight) if "lcSpeedGainRight" in kwargs: - deprecation_warning(self, "lcSpeedGainRight", - "lc_speed_gain_right") + deprecated_attribute(self, "lcSpeedGainRight", + "lc_speed_gain_right") lc_speed_gain_right = kwargs["lcSpeedGainRight"] # check for deprecations (lcSublane) if "lcSublane" in kwargs: - deprecation_warning(self, "lcSublane", "lc_sublane") + deprecated_attribute(self, "lcSublane", "lc_sublane") lc_sublane = kwargs["lcSublane"] # check for deprecations (lcPushy) if "lcPushy" in kwargs: - deprecation_warning(self, "lcPushy", "lc_pushy") + deprecated_attribute(self, "lcPushy", "lc_pushy") lc_pushy = kwargs["lcPushy"] # check for deprecations (lcPushyGap) if "lcPushyGap" in kwargs: - deprecation_warning(self, "lcPushyGap", "lc_pushy_gap") + deprecated_attribute(self, "lcPushyGap", "lc_pushy_gap") lc_pushy_gap = kwargs["lcPushyGap"] # check for deprecations (lcAssertive) if "lcAssertive" in kwargs: - deprecation_warning(self, "lcAssertive", "lc_assertive") + deprecated_attribute(self, "lcAssertive", "lc_assertive") lc_assertive = kwargs["lcAssertive"] # check for deprecations (lcImpatience) if "lcImpatience" in kwargs: - deprecation_warning(self, "lcImpatience", "lc_impatience") + deprecated_attribute(self, "lcImpatience", "lc_impatience") lc_impatience = kwargs["lcImpatience"] # check for deprecations (lcTimeToImpatience) if "lcTimeToImpatience" in kwargs: - deprecation_warning(self, "lcTimeToImpatience", - "lc_time_to_impatience") + deprecated_attribute(self, "lcTimeToImpatience", + "lc_time_to_impatience") lc_time_to_impatience = kwargs["lcTimeToImpatience"] # check for deprecations (lcAccelLat) if "lcAccelLat" in kwargs: - deprecation_warning(self, "lcAccelLat", "lc_accel_lat") + deprecated_attribute(self, "lcAccelLat", "lc_accel_lat") lc_accel_lat = kwargs["lcAccelLat"] # check for valid model @@ -1154,7 +1154,7 @@ def add(self, """ # check for deprecations def deprecate(old, new): - deprecation_warning(self, old, new) + deprecated_attribute(self, old, new) new_val = kwargs[old] del kwargs[old] return new_val diff --git a/flow/envs/__init__.py b/flow/envs/__init__.py index 4d4e088858..5befe6a33b 100755 --- a/flow/envs/__init__.py +++ b/flow/envs/__init__.py @@ -1,23 +1,44 @@ """Contains all callable environments in Flow.""" -from flow.envs.base_env import Env -from flow.envs.bay_bridge.base import BayBridgeEnv -from flow.envs.bottleneck_env import BottleNeckAccelEnv, BottleneckEnv, \ - DesiredVelocityEnv -from flow.envs.green_wave_env import TrafficLightGridEnv, \ - PO_TrafficLightGridEnv, GreenWaveTestEnv -from flow.envs.loop.lane_changing import LaneChangeAccelEnv, \ +from flow.envs.base import Env +from flow.envs.bay_bridge import BayBridgeEnv +from flow.envs.bottleneck import BottleneckAccelEnv, BottleneckEnv, \ + BottleneckDesiredVelocityEnv +from flow.envs.traffic_light_grid import TrafficLightGridEnv, \ + TrafficLightGridPOEnv, TrafficLightGridTestEnv +from flow.envs.ring.lane_change_accel import LaneChangeAccelEnv, \ LaneChangeAccelPOEnv -from flow.envs.loop.loop_accel import AccelEnv -from flow.envs.loop.wave_attenuation import WaveAttenuationEnv, \ +from flow.envs.ring.accel import AccelEnv +from flow.envs.ring.wave_attenuation import WaveAttenuationEnv, \ WaveAttenuationPOEnv -from flow.envs.merge import WaveAttenuationMergePOEnv +from flow.envs.merge import MergePOEnv from flow.envs.test import TestEnv +# deprecated classes whose names have changed +from flow.envs.bottleneck_env import BottleNeckAccelEnv +from flow.envs.bottleneck_env import DesiredVelocityEnv +from flow.envs.green_wave_env import PO_TrafficLightGridEnv +from flow.envs.green_wave_env import GreenWaveTestEnv + + __all__ = [ - 'Env', 'AccelEnv', 'LaneChangeAccelEnv', - 'LaneChangeAccelPOEnv', 'GreenWaveTestEnv', 'GreenWaveTestEnv', - 'WaveAttenuationMergePOEnv', 'BottleneckEnv', - 'BottleNeckAccelEnv', 'WaveAttenuationEnv', 'WaveAttenuationPOEnv', - 'TrafficLightGridEnv', 'PO_TrafficLightGridEnv', 'DesiredVelocityEnv', - 'TestEnv', 'BayBridgeEnv', + 'Env', + 'AccelEnv', + 'LaneChangeAccelEnv', + 'LaneChangeAccelPOEnv', + 'TrafficLightGridTestEnv', + 'MergePOEnv', + 'BottleneckEnv', + 'BottleneckAccelEnv', + 'WaveAttenuationEnv', + 'WaveAttenuationPOEnv', + 'TrafficLightGridEnv', + 'TrafficLightGridPOEnv', + 'BottleneckDesiredVelocityEnv', + 'TestEnv', + 'BayBridgeEnv', + # deprecated classes + 'BottleNeckAccelEnv', + 'DesiredVelocityEnv', + 'PO_TrafficLightGridEnv', + 'GreenWaveTestEnv', ] diff --git a/flow/envs/base.py b/flow/envs/base.py new file mode 100755 index 0000000000..0497781178 --- /dev/null +++ b/flow/envs/base.py @@ -0,0 +1,760 @@ +"""Base environment class. This is the parent of all other environments.""" + +from copy import deepcopy +import os +import atexit +import time +import traceback +import numpy as np +import random +from flow.renderer.pyglet_renderer import PygletRenderer as Renderer + +import gym +from gym.spaces import Box +from gym.spaces import Tuple +from traci.exceptions import FatalTraCIError +from traci.exceptions import TraCIException + +import sumolib + + +from flow.core.util import ensure_dir +from flow.core.kernel import Kernel +from flow.utils.exceptions import FatalFlowError + + +class Env(gym.Env): + """Base environment class. + + Provides the interface for interacting with various aspects of a traffic + simulation. Using this class, you can start a simulation instance, provide + a scenario to specify a configuration and controllers, perform simulation + steps, and reset the simulation to an initial configuration. + + Env is Serializable to allow for pickling and replaying of the policy. + + This class cannot be used as is: you must extend it to implement an + action applicator method, and properties to define the MDP if you + choose to use it with an rl library (e.g. RLlib). This can be done by + overloading the following functions in a child class: + + * action_space + * observation_space + * apply_rl_action + * get_state + * compute_reward + + Attributes + ---------- + env_params : flow.core.params.EnvParams + see flow/core/params.py + sim_params : flow.core.params.SimParams + see flow/core/params.py + net_params : flow.core.params.NetParams + see flow/core/params.py + initial_config : flow.core.params.InitialConfig + see flow/core/params.py + scenario : flow.scenarios.Scenario + see flow/scenarios/base.py + simulator : str + the simulator used, one of {'traci', 'aimsun'} + k : flow.core.kernel.Kernel + Flow kernel object, using for state acquisition and issuing commands to + the certain components of the simulator. For more information, see: + flow/core/kernel/kernel.py + state : to be defined in observation space + state of the simulation + obs_var_labels : list + optional labels for each entries in observed state + sim_step : float optional + seconds per simulation step; 0.1 by default + time_counter : int + number of steps taken since the start of a rollout + step_counter : int + number of steps taken since the environment was initialized, or since + `restart_simulation` was called + initial_state : dict + initial state information for all vehicles. The network is always + initialized with the number of vehicles originally specified in + VehicleParams + + * Key = Vehicle ID, + * Entry = (vehicle type, starting edge, starting lane index, starting + position on edge, starting speed) + + initial_ids : list of str + name of the vehicles that will originally available in the network at + the start of a rollout (i.e. after `env.reset()` is called). This also + corresponds to `self.initial_state.keys()`. + available_routes : dict + the available_routes variable contains a dictionary of routes vehicles + can traverse; to be used when routes need to be chosen dynamically. + Equivalent to `scenario.rts`. + renderer : flow.renderer.pyglet_renderer.PygletRenderer or None + renderer class, used to collect image-based representations of the + traffic network. This attribute is set to None if `sim_params.render` + is set to True or False. + """ + + def __init__(self, env_params, sim_params, scenario, simulator='traci'): + """Initialize the environment class. + + Parameters + ---------- + env_params : flow.core.params.EnvParams + see flow/core/params.py + sim_params : flow.core.params.SimParams + see flow/core/params.py + scenario : flow.scenarios.Scenario + see flow/scenarios/base.py + simulator : str + the simulator used, one of {'traci', 'aimsun'}. Defaults to 'traci' + + Raises + ------ + flow.utils.exceptions.FatalFlowError + if the render mode is not set to a valid value + """ + self.env_params = env_params + self.scenario = scenario + self.net_params = scenario.net_params + self.initial_config = scenario.initial_config + self.sim_params = sim_params + time_stamp = ''.join(str(time.time()).split('.')) + if os.environ.get("TEST_FLAG", 0): + # 1.0 works with stress_test_start 10k times + time.sleep(1.0 * int(time_stamp[-6:]) / 1e6) + # FIXME: this is sumo-specific + self.sim_params.port = sumolib.miscutils.getFreeSocketPort() + # time_counter: number of steps taken since the start of a rollout + self.time_counter = 0 + # step_counter: number of total steps taken + self.step_counter = 0 + # initial_state: + self.initial_state = {} + self.state = None + self.obs_var_labels = [] + + # simulation step size + self.sim_step = sim_params.sim_step + + # the simulator used by this environment + self.simulator = simulator + + # create the Flow kernel + self.k = Kernel(simulator=self.simulator, + sim_params=sim_params) + + # use the scenario class's network parameters to generate the necessary + # scenario components within the scenario kernel + self.k.scenario.generate_network(scenario) + + # initial the vehicles kernel using the VehicleParams object + self.k.vehicle.initialize(deepcopy(scenario.vehicles)) + + # initialize the simulation using the simulation kernel. This will use + # the scenario kernel as an input in order to determine what network + # needs to be simulated. + kernel_api = self.k.simulation.start_simulation( + scenario=self.k.scenario, sim_params=sim_params) + + # pass the kernel api to the kernel and it's subclasses + self.k.pass_api(kernel_api) + + # the available_routes variable contains a dictionary of routes + # vehicles can traverse; to be used when routes need to be chosen + # dynamically + self.available_routes = self.k.scenario.rts + + # store the initial vehicle ids + self.initial_ids = deepcopy(scenario.vehicles.ids) + + # store the initial state of the vehicles kernel (needed for restarting + # the simulation) + self.k.vehicle.kernel_api = None + self.k.vehicle.master_kernel = None + self.initial_vehicles = deepcopy(self.k.vehicle) + self.k.vehicle.kernel_api = self.k.kernel_api + self.k.vehicle.master_kernel = self.k + + self.setup_initial_state() + + # use pyglet to render the simulation + if self.sim_params.render in ['gray', 'dgray', 'rgb', 'drgb']: + save_render = self.sim_params.save_render + sight_radius = self.sim_params.sight_radius + pxpm = self.sim_params.pxpm + show_radius = self.sim_params.show_radius + + # get network polygons + network = [] + # FIXME: add to scenario kernel instead of hack + for lane_id in self.k.kernel_api.lane.getIDList(): + _lane_poly = self.k.kernel_api.lane.getShape(lane_id) + lane_poly = [i for pt in _lane_poly for i in pt] + network.append(lane_poly) + + # instantiate a pyglet renderer + self.renderer = Renderer( + network, + self.sim_params.render, + save_render, + sight_radius=sight_radius, + pxpm=pxpm, + show_radius=show_radius) + + # render a frame + self.render(reset=True) + elif self.sim_params.render in [True, False]: + pass # default to sumo-gui (if True) or sumo (if False) + else: + raise FatalFlowError( + 'Mode %s is not supported!' % self.sim_params.render) + atexit.register(self.terminate) + + def restart_simulation(self, sim_params, render=None): + """Restart an already initialized simulation instance. + + This is used when visualizing a rollout, in order to update the + rendering with potentially a gui and export emission data from sumo. + + This is also used to handle cases when the runtime of an experiment is + too long, causing the sumo instance + + Parameters + ---------- + sim_params : flow.core.params.SimParams + simulation-specific parameters + render : bool, optional + specifies whether to use the gui + """ + self.k.close() + + # killed the sumo process if using sumo/TraCI + if self.simulator == 'traci': + self.k.simulation.sumo_proc.kill() + + if render is not None: + self.sim_params.render = render + + if sim_params.emission_path is not None: + ensure_dir(sim_params.emission_path) + self.sim_params.emission_path = sim_params.emission_path + + self.k.scenario.generate_network(self.scenario) + self.k.vehicle.initialize(deepcopy(self.scenario.vehicles)) + kernel_api = self.k.simulation.start_simulation( + scenario=self.k.scenario, sim_params=self.sim_params) + self.k.pass_api(kernel_api) + + self.setup_initial_state() + + def setup_initial_state(self): + """Store information on the initial state of vehicles in the network. + + This information is to be used upon reset. This method also adds this + information to the self.vehicles class and starts a subscription with + sumo to collect state information each step. + """ + # determine whether to shuffle the vehicles + if self.initial_config.shuffle: + random.shuffle(self.initial_ids) + + # generate starting position for vehicles in the network + start_pos, start_lanes = self.k.scenario.generate_starting_positions( + initial_config=self.initial_config, + num_vehicles=len(self.initial_ids)) + + # save the initial state. This is used in the _reset function + for i, veh_id in enumerate(self.initial_ids): + type_id = self.k.vehicle.get_type(veh_id) + pos = start_pos[i][1] + lane = start_lanes[i] + speed = self.k.vehicle.get_initial_speed(veh_id) + edge = start_pos[i][0] + + self.initial_state[veh_id] = (type_id, edge, lane, pos, speed) + + def step(self, rl_actions): + """Advance the environment by one step. + + Assigns actions to autonomous and human-driven agents (i.e. vehicles, + traffic lights, etc...). Actions that are not assigned are left to the + control of the simulator. The actions are then used to advance the + simulator by the number of time steps requested per environment step. + + Results from the simulations are processed through various classes, + such as the Vehicle and TrafficLight kernels, to produce standardized + methods for identifying specific network state features. Finally, + results from the simulator are used to generate appropriate + observations. + + Parameters + ---------- + rl_actions : array_like + an list of actions provided by the rl algorithm + + Returns + ------- + observation : array_like + agent's observation of the current environment + reward : float + amount of reward associated with the previous state/action pair + done : bool + indicates whether the episode has ended + info : dict + contains other diagnostic information from the previous action + """ + for _ in range(self.env_params.sims_per_step): + self.time_counter += 1 + self.step_counter += 1 + + # perform acceleration actions for controlled human-driven vehicles + if len(self.k.vehicle.get_controlled_ids()) > 0: + accel = [] + for veh_id in self.k.vehicle.get_controlled_ids(): + action = self.k.vehicle.get_acc_controller( + veh_id).get_action(self) + accel.append(action) + self.k.vehicle.apply_acceleration( + self.k.vehicle.get_controlled_ids(), accel) + + # perform lane change actions for controlled human-driven vehicles + if len(self.k.vehicle.get_controlled_lc_ids()) > 0: + direction = [] + for veh_id in self.k.vehicle.get_controlled_lc_ids(): + target_lane = self.k.vehicle.get_lane_changing_controller( + veh_id).get_action(self) + direction.append(target_lane) + self.k.vehicle.apply_lane_change( + self.k.vehicle.get_controlled_lc_ids(), + direction=direction) + + # perform (optionally) routing actions for all vehicles in the + # network, including RL and SUMO-controlled vehicles + routing_ids = [] + routing_actions = [] + for veh_id in self.k.vehicle.get_ids(): + if self.k.vehicle.get_routing_controller(veh_id) \ + is not None: + routing_ids.append(veh_id) + route_contr = self.k.vehicle.get_routing_controller( + veh_id) + routing_actions.append(route_contr.choose_route(self)) + + self.k.vehicle.choose_routes(routing_ids, routing_actions) + + self.apply_rl_actions(rl_actions) + + self.additional_command() + + # advance the simulation in the simulator by one step + self.k.simulation.simulation_step() + + # store new observations in the vehicles and traffic lights class + self.k.update(reset=False) + + # update the colors of vehicles + if self.sim_params.render: + self.k.vehicle.update_vehicle_colors() + + # crash encodes whether the simulator experienced a collision + crash = self.k.simulation.check_collision() + + # stop collecting new simulation steps if there is a collision + if crash: + break + + # render a frame + self.render() + + states = self.get_state() + + # collect information of the state of the network based on the + # environment class used + self.state = np.asarray(states).T + + # collect observation new state associated with action + next_observation = np.copy(states) + + # test if the environment should terminate due to a collision or the + # time horizon being met + done = (self.time_counter >= self.env_params.warmup_steps + + self.env_params.horizon) # or crash + + # compute the info for each agent + infos = {} + + # compute the reward + if self.env_params.clip_actions: + rl_clipped = self.clip_actions(rl_actions) + reward = self.compute_reward(rl_clipped, fail=crash) + else: + reward = self.compute_reward(rl_actions, fail=crash) + + return next_observation, reward, done, infos + + def reset(self): + """Reset the environment. + + This method is performed in between rollouts. It resets the state of + the environment, and re-initializes the vehicles in their starting + positions. + + If "shuffle" is set to True in InitialConfig, the initial positions of + vehicles is recalculated and the vehicles are shuffled. + + Returns + ------- + observation : array_like + the initial observation of the space. The initial reward is assumed + to be zero. + """ + # reset the time counter + self.time_counter = 0 + + # warn about not using restart_instance when using inflows + if len(self.net_params.inflows.get()) > 0 and \ + not self.sim_params.restart_instance: + print( + "**********************************************************\n" + "**********************************************************\n" + "**********************************************************\n" + "WARNING: Inflows will cause computational performance to\n" + "significantly decrease after large number of rollouts. In \n" + "order to avoid this, set SumoParams(restart_instance=True).\n" + "**********************************************************\n" + "**********************************************************\n" + "**********************************************************" + ) + + if self.sim_params.restart_instance or \ + (self.step_counter > 2e6 and self.simulator != 'aimsun'): + self.step_counter = 0 + # issue a random seed to induce randomness into the next rollout + self.sim_params.seed = random.randint(0, 1e5) + + self.k.vehicle = deepcopy(self.initial_vehicles) + self.k.vehicle.master_kernel = self.k + # restart the sumo instance + self.restart_simulation(self.sim_params) + + # perform shuffling (if requested) + elif self.initial_config.shuffle: + self.setup_initial_state() + + # clear all vehicles from the network and the vehicles class + if self.simulator == 'traci': + for veh_id in self.k.kernel_api.vehicle.getIDList(): # FIXME: hack + try: + self.k.vehicle.remove(veh_id) + except (FatalTraCIError, TraCIException): + print(traceback.format_exc()) + + # clear all vehicles from the network and the vehicles class + # FIXME (ev, ak) this is weird and shouldn't be necessary + for veh_id in list(self.k.vehicle.get_ids()): + # do not try to remove the vehicles from the network in the first + # step after initializing the network, as there will be no vehicles + if self.step_counter == 0: + continue + try: + self.k.vehicle.remove(veh_id) + except (FatalTraCIError, TraCIException): + print("Error during start: {}".format(traceback.format_exc())) + + # reintroduce the initial vehicles to the network + for veh_id in self.initial_ids: + type_id, edge, lane_index, pos, speed = \ + self.initial_state[veh_id] + + try: + self.k.vehicle.add( + veh_id=veh_id, + type_id=type_id, + edge=edge, + lane=lane_index, + pos=pos, + speed=speed) + except (FatalTraCIError, TraCIException): + # if a vehicle was not removed in the first attempt, remove it + # now and then reintroduce it + self.k.vehicle.remove(veh_id) + if self.simulator == 'traci': + self.k.kernel_api.vehicle.remove(veh_id) # FIXME: hack + self.k.vehicle.add( + veh_id=veh_id, + type_id=type_id, + edge=edge, + lane=lane_index, + pos=pos, + speed=speed) + + # advance the simulation in the simulator by one step + self.k.simulation.simulation_step() + + # update the information in each kernel to match the current state + self.k.update(reset=True) + + # update the colors of vehicles + if self.sim_params.render: + self.k.vehicle.update_vehicle_colors() + + if self.simulator == 'traci': + initial_ids = self.k.kernel_api.vehicle.getIDList() + else: + initial_ids = self.initial_ids + + # check to make sure all vehicles have been spawned + if len(self.initial_ids) > len(initial_ids): + missing_vehicles = list(set(self.initial_ids) - set(initial_ids)) + msg = '\nNot enough vehicles have spawned! Bad start?\n' \ + 'Missing vehicles / initial state:\n' + for veh_id in missing_vehicles: + msg += '- {}: {}\n'.format(veh_id, self.initial_state[veh_id]) + raise FatalFlowError(msg=msg) + + states = self.get_state() + + # collect information of the state of the network based on the + # environment class used + self.state = np.asarray(states).T + + # observation associated with the reset (no warm-up steps) + observation = np.copy(states) + + # perform (optional) warm-up steps before training + for _ in range(self.env_params.warmup_steps): + observation, _, _, _ = self.step(rl_actions=None) + + # render a frame + self.render(reset=True) + + return observation + + def additional_command(self): + """Additional commands that may be performed by the step method.""" + pass + + def clip_actions(self, rl_actions=None): + """Clip the actions passed from the RL agent. + + Parameters + ---------- + rl_actions : array_like + list of actions provided by the RL algorithm + + Returns + ------- + array_like + The rl_actions clipped according to the box or boxes + """ + # ignore if no actions are issued + if rl_actions is None: + return + + # clip according to the action space requirements + if isinstance(self.action_space, Box): + rl_actions = np.clip( + rl_actions, + a_min=self.action_space.low, + a_max=self.action_space.high) + elif isinstance(self.action_space, Tuple): + for idx, action in enumerate(rl_actions): + subspace = self.action_space[idx] + if isinstance(subspace, Box): + rl_actions[idx] = np.clip( + action, + a_min=subspace.low, + a_max=subspace.high) + return rl_actions + + def apply_rl_actions(self, rl_actions=None): + """Specify the actions to be performed by the rl agent(s). + + If no actions are provided at any given step, the rl agents default to + performing actions specified by SUMO. + + Parameters + ---------- + rl_actions : array_like + list of actions provided by the RL algorithm + """ + # ignore if no actions are issued + if rl_actions is None: + return + + rl_clipped = self.clip_actions(rl_actions) + self._apply_rl_actions(rl_clipped) + + def _apply_rl_actions(self, rl_actions): + raise NotImplementedError + + def get_state(self): + """Return the state of the simulation as perceived by the RL agent. + + MUST BE implemented in new environments. + + Returns + ------- + state : array_like + information on the state of the vehicles, which is provided to the + agent + """ + raise NotImplementedError + + @property + def action_space(self): + """Identify the dimensions and bounds of the action space. + + MUST BE implemented in new environments. + + Returns + ------- + gym Box or Tuple type + a bounded box depicting the shape and bounds of the action space + """ + raise NotImplementedError + + @property + def observation_space(self): + """Identify the dimensions and bounds of the observation space. + + MUST BE implemented in new environments. + + Returns + ------- + gym Box or Tuple type + a bounded box depicting the shape and bounds of the observation + space + """ + raise NotImplementedError + + def compute_reward(self, rl_actions, **kwargs): + """Reward function for the RL agent(s). + + MUST BE implemented in new environments. + Defaults to 0 for non-implemented environments. + + Parameters + ---------- + rl_actions : array_like + actions performed by rl vehicles + kwargs : dict + other parameters of interest. Contains a "fail" element, which + is True if a vehicle crashed, and False otherwise + + Returns + ------- + reward : float or list of float + """ + return 0 + + def terminate(self): + """Close the TraCI I/O connection. + + Should be done at end of every experiment. Must be in Env because the + environment opens the TraCI connection. + """ + try: + # close everything within the kernel + self.k.close() + # close pyglet renderer + if self.sim_params.render in ['gray', 'dgray', 'rgb', 'drgb']: + self.renderer.close() + except FileNotFoundError: + # Skip automatic termination. Connection is probably already closed + print(traceback.format_exc()) + + def render(self, reset=False, buffer_length=5): + """Render a frame. + + Parameters + ---------- + reset : bool + set to True to reset the buffer + buffer_length : int + length of the buffer + """ + if self.sim_params.render in ['gray', 'dgray', 'rgb', 'drgb']: + # render a frame + self.pyglet_render() + + # cache rendering + if reset: + self.frame_buffer = [self.frame.copy() for _ in range(5)] + self.sights_buffer = [self.sights.copy() for _ in range(5)] + else: + if self.step_counter % int(1/self.sim_step) == 0: + self.frame_buffer.append(self.frame.copy()) + self.sights_buffer.append(self.sights.copy()) + if len(self.frame_buffer) > buffer_length: + self.frame_buffer.pop(0) + self.sights_buffer.pop(0) + + def pyglet_render(self): + """Render a frame using pyglet.""" + # get human and RL simulation status + human_idlist = self.k.vehicle.get_human_ids() + machine_idlist = self.k.vehicle.get_rl_ids() + human_logs = [] + human_orientations = [] + human_dynamics = [] + machine_logs = [] + machine_orientations = [] + machine_dynamics = [] + max_speed = self.k.scenario.max_speed() + for id in human_idlist: + # Force tracking human vehicles by adding "track" in vehicle id. + # The tracked human vehicles will be treated as machine vehicles. + if 'track' in id: + machine_logs.append( + [self.k.vehicle.get_timestep(id), + self.k.vehicle.get_timedelta(id), + id]) + machine_orientations.append( + self.k.vehicle.get_orientation(id)) + machine_dynamics.append( + self.k.vehicle.get_speed(id)/max_speed) + else: + human_logs.append( + [self.k.vehicle.get_timestep(id), + self.k.vehicle.get_timedelta(id), + id]) + human_orientations.append( + self.k.vehicle.get_orientation(id)) + human_dynamics.append( + self.k.vehicle.get_speed(id)/max_speed) + for id in machine_idlist: + machine_logs.append( + [self.k.vehicle.get_timestep(id), + self.k.vehicle.get_timedelta(id), + id]) + machine_orientations.append( + self.k.vehicle.get_orientation(id)) + machine_dynamics.append( + self.k.vehicle.get_speed(id)/max_speed) + + # step the renderer + self.frame = self.renderer.render(human_orientations, + machine_orientations, + human_dynamics, + machine_dynamics, + human_logs, + machine_logs) + + # get local observation of RL vehicles + self.sights = [] + for id in human_idlist: + # Force tracking human vehicles by adding "track" in vehicle id. + # The tracked human vehicles will be treated as machine vehicles. + if "track" in id: + orientation = self.k.vehicle.get_orientation(id) + sight = self.renderer.get_sight( + orientation, id) + self.sights.append(sight) + for id in machine_idlist: + orientation = self.k.vehicle.get_orientation(id) + sight = self.renderer.get_sight( + orientation, id) + self.sights.append(sight) diff --git a/flow/envs/base_env.py b/flow/envs/base_env.py old mode 100755 new mode 100644 index 70feaaaa44..e92093b96c --- a/flow/envs/base_env.py +++ b/flow/envs/base_env.py @@ -1,760 +1,13 @@ -"""Base environment class. This is the parent of all other environments.""" +"""Pending deprecation file. -from copy import deepcopy -import os -import atexit -import time -import traceback -import numpy as np -import random -from flow.renderer.pyglet_renderer import PygletRenderer as Renderer +To view the actual content, go to: flow/envs/base.py +""" +from flow.utils.flow_warnings import deprecated +from flow.envs.base import Env as BaseEnv -import gym -from gym.spaces import Box -from gym.spaces import Tuple -from traci.exceptions import FatalTraCIError -from traci.exceptions import TraCIException -import sumolib +@deprecated('flow.envs.base_env', 'flow.envs.base.Env') +class Env(BaseEnv): + """See parent class.""" - -from flow.core.util import ensure_dir -from flow.core.kernel import Kernel -from flow.utils.exceptions import FatalFlowError - - -class Env(gym.Env): - """Base environment class. - - Provides the interface for interacting with various aspects of a traffic - simulation. Using this class, you can start a simulation instance, provide - a scenario to specify a configuration and controllers, perform simulation - steps, and reset the simulation to an initial configuration. - - Env is Serializable to allow for pickling and replaying of the policy. - - This class cannot be used as is: you must extend it to implement an - action applicator method, and properties to define the MDP if you - choose to use it with an rl library (e.g. RLlib). This can be done by - overloading the following functions in a child class: - - * action_space - * observation_space - * apply_rl_action - * get_state - * compute_reward - - Attributes - ---------- - env_params : flow.core.params.EnvParams - see flow/core/params.py - sim_params : flow.core.params.SimParams - see flow/core/params.py - net_params : flow.core.params.NetParams - see flow/core/params.py - initial_config : flow.core.params.InitialConfig - see flow/core/params.py - scenario : flow.scenarios.Scenario - see flow/scenarios/base_scenario.py - simulator : str - the simulator used, one of {'traci', 'aimsun'} - k : flow.core.kernel.Kernel - Flow kernel object, using for state acquisition and issuing commands to - the certain components of the simulator. For more information, see: - flow/core/kernel/kernel.py - state : to be defined in observation space - state of the simulation - obs_var_labels : list - optional labels for each entries in observed state - sim_step : float optional - seconds per simulation step; 0.1 by default - time_counter : int - number of steps taken since the start of a rollout - step_counter : int - number of steps taken since the environment was initialized, or since - `restart_simulation` was called - initial_state : dict - initial state information for all vehicles. The network is always - initialized with the number of vehicles originally specified in - VehicleParams - - * Key = Vehicle ID, - * Entry = (vehicle type, starting edge, starting lane index, starting - position on edge, starting speed) - - initial_ids : list of str - name of the vehicles that will originally available in the network at - the start of a rollout (i.e. after `env.reset()` is called). This also - corresponds to `self.initial_state.keys()`. - available_routes : dict - the available_routes variable contains a dictionary of routes vehicles - can traverse; to be used when routes need to be chosen dynamically. - Equivalent to `scenario.rts`. - renderer : flow.renderer.pyglet_renderer.PygletRenderer or None - renderer class, used to collect image-based representations of the - traffic network. This attribute is set to None if `sim_params.render` - is set to True or False. - """ - - def __init__(self, env_params, sim_params, scenario, simulator='traci'): - """Initialize the environment class. - - Parameters - ---------- - env_params : flow.core.params.EnvParams - see flow/core/params.py - sim_params : flow.core.params.SimParams - see flow/core/params.py - scenario : flow.scenarios.Scenario - see flow/scenarios/base_scenario.py - simulator : str - the simulator used, one of {'traci', 'aimsun'}. Defaults to 'traci' - - Raises - ------ - flow.utils.exceptions.FatalFlowError - if the render mode is not set to a valid value - """ - self.env_params = env_params - self.scenario = scenario - self.net_params = scenario.net_params - self.initial_config = scenario.initial_config - self.sim_params = sim_params - time_stamp = ''.join(str(time.time()).split('.')) - if os.environ.get("TEST_FLAG", 0): - # 1.0 works with stress_test_start 10k times - time.sleep(1.0 * int(time_stamp[-6:]) / 1e6) - # FIXME: this is sumo-specific - self.sim_params.port = sumolib.miscutils.getFreeSocketPort() - # time_counter: number of steps taken since the start of a rollout - self.time_counter = 0 - # step_counter: number of total steps taken - self.step_counter = 0 - # initial_state: - self.initial_state = {} - self.state = None - self.obs_var_labels = [] - - # simulation step size - self.sim_step = sim_params.sim_step - - # the simulator used by this environment - self.simulator = simulator - - # create the Flow kernel - self.k = Kernel(simulator=self.simulator, - sim_params=sim_params) - - # use the scenario class's network parameters to generate the necessary - # scenario components within the scenario kernel - self.k.scenario.generate_network(scenario) - - # initial the vehicles kernel using the VehicleParams object - self.k.vehicle.initialize(deepcopy(scenario.vehicles)) - - # initialize the simulation using the simulation kernel. This will use - # the scenario kernel as an input in order to determine what network - # needs to be simulated. - kernel_api = self.k.simulation.start_simulation( - scenario=self.k.scenario, sim_params=sim_params) - - # pass the kernel api to the kernel and it's subclasses - self.k.pass_api(kernel_api) - - # the available_routes variable contains a dictionary of routes - # vehicles can traverse; to be used when routes need to be chosen - # dynamically - self.available_routes = self.k.scenario.rts - - # store the initial vehicle ids - self.initial_ids = deepcopy(scenario.vehicles.ids) - - # store the initial state of the vehicles kernel (needed for restarting - # the simulation) - self.k.vehicle.kernel_api = None - self.k.vehicle.master_kernel = None - self.initial_vehicles = deepcopy(self.k.vehicle) - self.k.vehicle.kernel_api = self.k.kernel_api - self.k.vehicle.master_kernel = self.k - - self.setup_initial_state() - - # use pyglet to render the simulation - if self.sim_params.render in ['gray', 'dgray', 'rgb', 'drgb']: - save_render = self.sim_params.save_render - sight_radius = self.sim_params.sight_radius - pxpm = self.sim_params.pxpm - show_radius = self.sim_params.show_radius - - # get network polygons - network = [] - # FIXME: add to scenario kernel instead of hack - for lane_id in self.k.kernel_api.lane.getIDList(): - _lane_poly = self.k.kernel_api.lane.getShape(lane_id) - lane_poly = [i for pt in _lane_poly for i in pt] - network.append(lane_poly) - - # instantiate a pyglet renderer - self.renderer = Renderer( - network, - self.sim_params.render, - save_render, - sight_radius=sight_radius, - pxpm=pxpm, - show_radius=show_radius) - - # render a frame - self.render(reset=True) - elif self.sim_params.render in [True, False]: - pass # default to sumo-gui (if True) or sumo (if False) - else: - raise FatalFlowError( - 'Mode %s is not supported!' % self.sim_params.render) - atexit.register(self.terminate) - - def restart_simulation(self, sim_params, render=None): - """Restart an already initialized simulation instance. - - This is used when visualizing a rollout, in order to update the - rendering with potentially a gui and export emission data from sumo. - - This is also used to handle cases when the runtime of an experiment is - too long, causing the sumo instance - - Parameters - ---------- - sim_params : flow.core.params.SimParams - simulation-specific parameters - render : bool, optional - specifies whether to use the gui - """ - self.k.close() - - # killed the sumo process if using sumo/TraCI - if self.simulator == 'traci': - self.k.simulation.sumo_proc.kill() - - if render is not None: - self.sim_params.render = render - - if sim_params.emission_path is not None: - ensure_dir(sim_params.emission_path) - self.sim_params.emission_path = sim_params.emission_path - - self.k.scenario.generate_network(self.scenario) - self.k.vehicle.initialize(deepcopy(self.scenario.vehicles)) - kernel_api = self.k.simulation.start_simulation( - scenario=self.k.scenario, sim_params=self.sim_params) - self.k.pass_api(kernel_api) - - self.setup_initial_state() - - def setup_initial_state(self): - """Store information on the initial state of vehicles in the network. - - This information is to be used upon reset. This method also adds this - information to the self.vehicles class and starts a subscription with - sumo to collect state information each step. - """ - # determine whether to shuffle the vehicles - if self.initial_config.shuffle: - random.shuffle(self.initial_ids) - - # generate starting position for vehicles in the network - start_pos, start_lanes = self.k.scenario.generate_starting_positions( - initial_config=self.initial_config, - num_vehicles=len(self.initial_ids)) - - # save the initial state. This is used in the _reset function - for i, veh_id in enumerate(self.initial_ids): - type_id = self.k.vehicle.get_type(veh_id) - pos = start_pos[i][1] - lane = start_lanes[i] - speed = self.k.vehicle.get_initial_speed(veh_id) - edge = start_pos[i][0] - - self.initial_state[veh_id] = (type_id, edge, lane, pos, speed) - - def step(self, rl_actions): - """Advance the environment by one step. - - Assigns actions to autonomous and human-driven agents (i.e. vehicles, - traffic lights, etc...). Actions that are not assigned are left to the - control of the simulator. The actions are then used to advance the - simulator by the number of time steps requested per environment step. - - Results from the simulations are processed through various classes, - such as the Vehicle and TrafficLight kernels, to produce standardized - methods for identifying specific network state features. Finally, - results from the simulator are used to generate appropriate - observations. - - Parameters - ---------- - rl_actions : array_like - an list of actions provided by the rl algorithm - - Returns - ------- - observation : array_like - agent's observation of the current environment - reward : float - amount of reward associated with the previous state/action pair - done : bool - indicates whether the episode has ended - info : dict - contains other diagnostic information from the previous action - """ - for _ in range(self.env_params.sims_per_step): - self.time_counter += 1 - self.step_counter += 1 - - # perform acceleration actions for controlled human-driven vehicles - if len(self.k.vehicle.get_controlled_ids()) > 0: - accel = [] - for veh_id in self.k.vehicle.get_controlled_ids(): - action = self.k.vehicle.get_acc_controller( - veh_id).get_action(self) - accel.append(action) - self.k.vehicle.apply_acceleration( - self.k.vehicle.get_controlled_ids(), accel) - - # perform lane change actions for controlled human-driven vehicles - if len(self.k.vehicle.get_controlled_lc_ids()) > 0: - direction = [] - for veh_id in self.k.vehicle.get_controlled_lc_ids(): - target_lane = self.k.vehicle.get_lane_changing_controller( - veh_id).get_action(self) - direction.append(target_lane) - self.k.vehicle.apply_lane_change( - self.k.vehicle.get_controlled_lc_ids(), - direction=direction) - - # perform (optionally) routing actions for all vehicles in the - # network, including RL and SUMO-controlled vehicles - routing_ids = [] - routing_actions = [] - for veh_id in self.k.vehicle.get_ids(): - if self.k.vehicle.get_routing_controller(veh_id) \ - is not None: - routing_ids.append(veh_id) - route_contr = self.k.vehicle.get_routing_controller( - veh_id) - routing_actions.append(route_contr.choose_route(self)) - - self.k.vehicle.choose_routes(routing_ids, routing_actions) - - self.apply_rl_actions(rl_actions) - - self.additional_command() - - # advance the simulation in the simulator by one step - self.k.simulation.simulation_step() - - # store new observations in the vehicles and traffic lights class - self.k.update(reset=False) - - # update the colors of vehicles - if self.sim_params.render: - self.k.vehicle.update_vehicle_colors() - - # crash encodes whether the simulator experienced a collision - crash = self.k.simulation.check_collision() - - # stop collecting new simulation steps if there is a collision - if crash: - break - - # render a frame - self.render() - - states = self.get_state() - - # collect information of the state of the network based on the - # environment class used - self.state = np.asarray(states).T - - # collect observation new state associated with action - next_observation = np.copy(states) - - # test if the environment should terminate due to a collision or the - # time horizon being met - done = (self.time_counter >= self.env_params.warmup_steps + - self.env_params.horizon) # or crash - - # compute the info for each agent - infos = {} - - # compute the reward - if self.env_params.clip_actions: - rl_clipped = self.clip_actions(rl_actions) - reward = self.compute_reward(rl_clipped, fail=crash) - else: - reward = self.compute_reward(rl_actions, fail=crash) - - return next_observation, reward, done, infos - - def reset(self): - """Reset the environment. - - This method is performed in between rollouts. It resets the state of - the environment, and re-initializes the vehicles in their starting - positions. - - If "shuffle" is set to True in InitialConfig, the initial positions of - vehicles is recalculated and the vehicles are shuffled. - - Returns - ------- - observation : array_like - the initial observation of the space. The initial reward is assumed - to be zero. - """ - # reset the time counter - self.time_counter = 0 - - # warn about not using restart_instance when using inflows - if len(self.net_params.inflows.get()) > 0 and \ - not self.sim_params.restart_instance: - print( - "**********************************************************\n" - "**********************************************************\n" - "**********************************************************\n" - "WARNING: Inflows will cause computational performance to\n" - "significantly decrease after large number of rollouts. In \n" - "order to avoid this, set SumoParams(restart_instance=True).\n" - "**********************************************************\n" - "**********************************************************\n" - "**********************************************************" - ) - - if self.sim_params.restart_instance or \ - (self.step_counter > 2e6 and self.simulator != 'aimsun'): - self.step_counter = 0 - # issue a random seed to induce randomness into the next rollout - self.sim_params.seed = random.randint(0, 1e5) - - self.k.vehicle = deepcopy(self.initial_vehicles) - self.k.vehicle.master_kernel = self.k - # restart the sumo instance - self.restart_simulation(self.sim_params) - - # perform shuffling (if requested) - elif self.initial_config.shuffle: - self.setup_initial_state() - - # clear all vehicles from the network and the vehicles class - if self.simulator == 'traci': - for veh_id in self.k.kernel_api.vehicle.getIDList(): # FIXME: hack - try: - self.k.vehicle.remove(veh_id) - except (FatalTraCIError, TraCIException): - print(traceback.format_exc()) - - # clear all vehicles from the network and the vehicles class - # FIXME (ev, ak) this is weird and shouldn't be necessary - for veh_id in list(self.k.vehicle.get_ids()): - # do not try to remove the vehicles from the network in the first - # step after initializing the network, as there will be no vehicles - if self.step_counter == 0: - continue - try: - self.k.vehicle.remove(veh_id) - except (FatalTraCIError, TraCIException): - print("Error during start: {}".format(traceback.format_exc())) - - # reintroduce the initial vehicles to the network - for veh_id in self.initial_ids: - type_id, edge, lane_index, pos, speed = \ - self.initial_state[veh_id] - - try: - self.k.vehicle.add( - veh_id=veh_id, - type_id=type_id, - edge=edge, - lane=lane_index, - pos=pos, - speed=speed) - except (FatalTraCIError, TraCIException): - # if a vehicle was not removed in the first attempt, remove it - # now and then reintroduce it - self.k.vehicle.remove(veh_id) - if self.simulator == 'traci': - self.k.kernel_api.vehicle.remove(veh_id) # FIXME: hack - self.k.vehicle.add( - veh_id=veh_id, - type_id=type_id, - edge=edge, - lane=lane_index, - pos=pos, - speed=speed) - - # advance the simulation in the simulator by one step - self.k.simulation.simulation_step() - - # update the information in each kernel to match the current state - self.k.update(reset=True) - - # update the colors of vehicles - if self.sim_params.render: - self.k.vehicle.update_vehicle_colors() - - if self.simulator == 'traci': - initial_ids = self.k.kernel_api.vehicle.getIDList() - else: - initial_ids = self.initial_ids - - # check to make sure all vehicles have been spawned - if len(self.initial_ids) > len(initial_ids): - missing_vehicles = list(set(self.initial_ids) - set(initial_ids)) - msg = '\nNot enough vehicles have spawned! Bad start?\n' \ - 'Missing vehicles / initial state:\n' - for veh_id in missing_vehicles: - msg += '- {}: {}\n'.format(veh_id, self.initial_state[veh_id]) - raise FatalFlowError(msg=msg) - - states = self.get_state() - - # collect information of the state of the network based on the - # environment class used - self.state = np.asarray(states).T - - # observation associated with the reset (no warm-up steps) - observation = np.copy(states) - - # perform (optional) warm-up steps before training - for _ in range(self.env_params.warmup_steps): - observation, _, _, _ = self.step(rl_actions=None) - - # render a frame - self.render(reset=True) - - return observation - - def additional_command(self): - """Additional commands that may be performed by the step method.""" - pass - - def clip_actions(self, rl_actions=None): - """Clip the actions passed from the RL agent. - - Parameters - ---------- - rl_actions : array_like - list of actions provided by the RL algorithm - - Returns - ------- - array_like - The rl_actions clipped according to the box or boxes - """ - # ignore if no actions are issued - if rl_actions is None: - return - - # clip according to the action space requirements - if isinstance(self.action_space, Box): - rl_actions = np.clip( - rl_actions, - a_min=self.action_space.low, - a_max=self.action_space.high) - elif isinstance(self.action_space, Tuple): - for idx, action in enumerate(rl_actions): - subspace = self.action_space[idx] - if isinstance(subspace, Box): - rl_actions[idx] = np.clip( - action, - a_min=subspace.low, - a_max=subspace.high) - return rl_actions - - def apply_rl_actions(self, rl_actions=None): - """Specify the actions to be performed by the rl agent(s). - - If no actions are provided at any given step, the rl agents default to - performing actions specified by SUMO. - - Parameters - ---------- - rl_actions : array_like - list of actions provided by the RL algorithm - """ - # ignore if no actions are issued - if rl_actions is None: - return - - rl_clipped = self.clip_actions(rl_actions) - self._apply_rl_actions(rl_clipped) - - def _apply_rl_actions(self, rl_actions): - raise NotImplementedError - - def get_state(self): - """Return the state of the simulation as perceived by the RL agent. - - MUST BE implemented in new environments. - - Returns - ------- - state : array_like - information on the state of the vehicles, which is provided to the - agent - """ - raise NotImplementedError - - @property - def action_space(self): - """Identify the dimensions and bounds of the action space. - - MUST BE implemented in new environments. - - Returns - ------- - gym Box or Tuple type - a bounded box depicting the shape and bounds of the action space - """ - raise NotImplementedError - - @property - def observation_space(self): - """Identify the dimensions and bounds of the observation space. - - MUST BE implemented in new environments. - - Returns - ------- - gym Box or Tuple type - a bounded box depicting the shape and bounds of the observation - space - """ - raise NotImplementedError - - def compute_reward(self, rl_actions, **kwargs): - """Reward function for the RL agent(s). - - MUST BE implemented in new environments. - Defaults to 0 for non-implemented environments. - - Parameters - ---------- - rl_actions : array_like - actions performed by rl vehicles - kwargs : dict - other parameters of interest. Contains a "fail" element, which - is True if a vehicle crashed, and False otherwise - - Returns - ------- - reward : float or list of float - """ - return 0 - - def terminate(self): - """Close the TraCI I/O connection. - - Should be done at end of every experiment. Must be in Env because the - environment opens the TraCI connection. - """ - try: - # close everything within the kernel - self.k.close() - # close pyglet renderer - if self.sim_params.render in ['gray', 'dgray', 'rgb', 'drgb']: - self.renderer.close() - except FileNotFoundError: - # Skip automatic termination. Connection is probably already closed - print(traceback.format_exc()) - - def render(self, reset=False, buffer_length=5): - """Render a frame. - - Parameters - ---------- - reset : bool - set to True to reset the buffer - buffer_length : int - length of the buffer - """ - if self.sim_params.render in ['gray', 'dgray', 'rgb', 'drgb']: - # render a frame - self.pyglet_render() - - # cache rendering - if reset: - self.frame_buffer = [self.frame.copy() for _ in range(5)] - self.sights_buffer = [self.sights.copy() for _ in range(5)] - else: - if self.step_counter % int(1/self.sim_step) == 0: - self.frame_buffer.append(self.frame.copy()) - self.sights_buffer.append(self.sights.copy()) - if len(self.frame_buffer) > buffer_length: - self.frame_buffer.pop(0) - self.sights_buffer.pop(0) - - def pyglet_render(self): - """Render a frame using pyglet.""" - # get human and RL simulation status - human_idlist = self.k.vehicle.get_human_ids() - machine_idlist = self.k.vehicle.get_rl_ids() - human_logs = [] - human_orientations = [] - human_dynamics = [] - machine_logs = [] - machine_orientations = [] - machine_dynamics = [] - max_speed = self.k.scenario.max_speed() - for id in human_idlist: - # Force tracking human vehicles by adding "track" in vehicle id. - # The tracked human vehicles will be treated as machine vehicles. - if 'track' in id: - machine_logs.append( - [self.k.vehicle.get_timestep(id), - self.k.vehicle.get_timedelta(id), - id]) - machine_orientations.append( - self.k.vehicle.get_orientation(id)) - machine_dynamics.append( - self.k.vehicle.get_speed(id)/max_speed) - else: - human_logs.append( - [self.k.vehicle.get_timestep(id), - self.k.vehicle.get_timedelta(id), - id]) - human_orientations.append( - self.k.vehicle.get_orientation(id)) - human_dynamics.append( - self.k.vehicle.get_speed(id)/max_speed) - for id in machine_idlist: - machine_logs.append( - [self.k.vehicle.get_timestep(id), - self.k.vehicle.get_timedelta(id), - id]) - machine_orientations.append( - self.k.vehicle.get_orientation(id)) - machine_dynamics.append( - self.k.vehicle.get_speed(id)/max_speed) - - # step the renderer - self.frame = self.renderer.render(human_orientations, - machine_orientations, - human_dynamics, - machine_dynamics, - human_logs, - machine_logs) - - # get local observation of RL vehicles - self.sights = [] - for id in human_idlist: - # Force tracking human vehicles by adding "track" in vehicle id. - # The tracked human vehicles will be treated as machine vehicles. - if "track" in id: - orientation = self.k.vehicle.get_orientation(id) - sight = self.renderer.get_sight( - orientation, id) - self.sights.append(sight) - for id in machine_idlist: - orientation = self.k.vehicle.get_orientation(id) - sight = self.renderer.get_sight( - orientation, id) - self.sights.append(sight) + pass diff --git a/flow/envs/bay_bridge/base.py b/flow/envs/bay_bridge.py similarity index 100% rename from flow/envs/bay_bridge/base.py rename to flow/envs/bay_bridge.py diff --git a/flow/envs/bay_bridge/__init__.py b/flow/envs/bay_bridge/__init__.py deleted file mode 100644 index 7a671f6daa..0000000000 --- a/flow/envs/bay_bridge/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Empty init file to ensure documentation for Bay Bridge is created.""" diff --git a/flow/envs/bottleneck.py b/flow/envs/bottleneck.py new file mode 100644 index 0000000000..67410b84fe --- /dev/null +++ b/flow/envs/bottleneck.py @@ -0,0 +1,1085 @@ +""" +Environments for training vehicles to reduce capacity drops in a bottleneck. + +This environment was used in: + +E. Vinitsky, K. Parvate, A. Kreidieh, C. Wu, Z. Hu, A. Bayen, "Lagrangian +Control through Deep-RL: Applications to Bottleneck Decongestion," IEEE +Intelligent Transportation Systems Conference (ITSC), 2018. +""" + +from flow.controllers.rlcontroller import RLController +from flow.controllers.lane_change_controllers import SimLaneChangeController +from flow.controllers.routing_controllers import ContinuousRouter +from flow.core.params import InFlows, NetParams +from flow.core.params import SumoCarFollowingParams, SumoLaneChangeParams +from flow.core.params import VehicleParams + +from copy import deepcopy + +import numpy as np +from gym.spaces.box import Box + +from flow.core import rewards +from flow.envs.base import Env + +MAX_LANES = 4 # base number of largest number of lanes in the network +EDGE_LIST = ["1", "2", "3", "4", "5"] # Edge 1 is before the toll booth +EDGE_BEFORE_TOLL = "1" # Specifies which edge number is before toll booth +TB_TL_ID = "2" +EDGE_AFTER_TOLL = "2" # Specifies which edge number is after toll booth +NUM_TOLL_LANES = MAX_LANES + +TOLL_BOOTH_AREA = 10 # how far into the edge lane changing is disabled +RED_LIGHT_DIST = 50 # how close for the ramp meter to start going off + +EDGE_BEFORE_RAMP_METER = "2" # Specifies which edge is before ramp meter +EDGE_AFTER_RAMP_METER = "3" # Specifies which edge is after ramp meter +NUM_RAMP_METERS = MAX_LANES + +RAMP_METER_AREA = 80 # Area occupied by ramp meter + +MEAN_NUM_SECONDS_WAIT_AT_FAST_TRACK = 3 # Average waiting time at fast track +MEAN_NUM_SECONDS_WAIT_AT_TOLL = 15 # Average waiting time at toll + +BOTTLE_NECK_LEN = 280 # Length of bottleneck +NUM_VEHICLE_NORM = 20 + +ADDITIONAL_ENV_PARAMS = { + # maximum acceleration for autonomous vehicles, in m/s^2 + "max_accel": 3, + # maximum deceleration for autonomous vehicles, in m/s^2 + "max_decel": 3, + # lane change duration for autonomous vehicles, in s. Autonomous vehicles + # reject new lane changing commands for this duration after successfully + # changing lanes. + "lane_change_duration": 5, + # whether the toll booth should be active + "disable_tb": True, + # whether the ramp meter is active + "disable_ramp_metering": True, +} + +# Keys for RL experiments +ADDITIONAL_RL_ENV_PARAMS = { + # velocity to use in reward functions + "target_velocity": 30, + # if an RL vehicle exits, place it back at the front + "add_rl_if_exit": True, +} + +# Keys for VSL style experiments +ADDITIONAL_VSL_ENV_PARAMS = { + # number of controlled regions for velocity bottleneck controller + "controlled_segments": [("1", 1, True), ("2", 1, True), ("3", 1, True), + ("4", 1, True), ("5", 1, True)], + # whether lanes in a segment have the same action or not + "symmetric": + False, + # which edges are observed + "observed_segments": [("1", 1), ("2", 1), ("3", 1), ("4", 1), ("5", 1)], + # whether the inflow should be reset on each rollout + "reset_inflow": + False, + # the range of inflows to reset on + "inflow_range": [1000, 2000] +} + +START_RECORD_TIME = 0.0 # Time to start recording +PERIOD = 10.0 + + +class BottleneckEnv(Env): + """Abstract bottleneck environment. + + This environment is used as a simplified representation of the toll booth + portion of the bay bridge. Contains ramp meters, and a toll both. + + Additional + ---------- + Vehicles are rerouted to the start of their original routes once + they reach the end of the network in order to ensure a constant + number of vehicles. + + Attributes + ---------- + scaling : int + A factor describing how many lanes are in the system. Scaling=1 implies + 4 lanes going to 2 going to 1, scaling=2 implies 8 lanes going to 4 + going to 2, etc. + edge_dict : dict of dicts + A dict mapping edges to a dict of lanes where each entry in the lane + dict tracks the vehicles that are in that lane. Used to save on + unnecessary lookups. + cars_waiting_for_toll : {veh_id: {lane_change_mode: int, color: (int)}} + A dict mapping vehicle ids to a dict tracking the color and lane change + mode of vehicles before they entered the toll area. When vehicles exit + the tollbooth area, these values are used to set the lane change mode + and color of the vehicle back to how they were before they entered the + toll area. + cars_before_ramp : {veh_id: {lane_change_mode: int, color: (int)}} + Identical to cars_waiting_for_toll, but used to track cars approaching + the ramp meter versus approaching the tollbooth. + toll_wait_time : np.ndarray(float) + Random value, sampled from a gaussian indicating how much a vehicle in + each lane should wait to pass through the toll area. This value is + re-sampled for each approaching vehicle. That is, whenever a vehicle + approaches the toll area, we re-sample from the Gaussian to determine + its weight time. + fast_track_lanes : np.ndarray(int) + Middle lanes of the tollbooth are declared fast-track lanes, this numpy + array keeps track of which lanes these are. At a fast track lane, the + mean of the Gaussian from which we sample wait times is given by + MEAN_NUM_SECONDS_WAIT_AT_FAST_TRACK. + tl_state : str + String tracking the color of the traffic lights at the tollbooth. These + traffic lights are used imitate the effect of a tollbooth. If lane 1-4 + are respectively green, red, red, green, then this string would be + "GrrG" + n_crit : int + The ALINEA algorithm adjusts the ratio of red to green time for the + ramp-metering traffic light based on feedback on how congested the + system is. As the measure of congestion, we use the number of vehicles + stuck in the bottleneck (edge 4). The critical operating value it tries + to stabilize the number of vehicles in edge 4 is n_crit. If there are + more than n_crit vehicles on edge 4, we increase the fraction of red + time to decrease the inflow to edge 4. + q_max : float + The ALINEA algorithm tries to control the flow rate through the ramp + meter. q_max is the maximum possible estimated flow we allow through + the bottleneck and can be converted into a maximum value for the ratio + of green to red time that we allow. + q_min : float + Similar to q_max, this is used to set the minimum value of green to red + ratio that we allow. + q : float + This value tracks the flow we intend to allow through the bottleneck. + For details on how it is computed, please read the alinea method or the + paper linked in that method. + feedback_update_time : float + The parameters of the ALINEA algorithm are only updated every + feedback_update_time seconds. + feedback_timer : float + This keeps track of how many seconds have passed since the ALINEA + parameters were last updated. If it exceeds feedback_update_time, the + parameters are updated + cycle_time : int + This value tracks how long a green-red cycle of the ramp meter is. The + first self.green_time seconds will be green and the remainder of the + cycle will be red. + ramp_state : np.ndarray + Array of floats of length equal to the number of lanes. For each lane, + this value tracks how many seconds of a given cycle have passed in that + lane. Each lane is offset from its adjacent lanes by + cycle_offset/(self.scaling * MAX_LANES) seconds. This offsetting means + that lights are offset in when they releasse vehicles into the + bottleneck. This helps maximize the throughput of the ramp meter. + green_time : float + How many seconds of a given cycle the light should remain green 4. + Defaults to 4 as this is just enough time for two vehicles to enter the + bottleneck from a given traffic light. + feedback_coeff : float + This is the gain on the feedback in the ALINEA algorithm + smoothed_num : np.ndarray + Numpy array keeping track of how many vehicles were in edge 4 over the + last 10 time seconds. This provides a more stable estimate of the + number of vehicles in edge 4. + outflow_index : int + Keeps track of which index of smoothed_num we should update with the + latest number of vehicles in the bottleneck. Should eventually be + deprecated as smoothed_num should be switched to a queue instead of an + array. + """ + + def __init__(self, env_params, sim_params, scenario, simulator='traci'): + """Initialize the BottleneckEnv class.""" + for p in ADDITIONAL_ENV_PARAMS.keys(): + if p not in env_params.additional_params: + raise KeyError( + 'Environment parameter "{}" not supplied'.format(p)) + + super().__init__(env_params, sim_params, scenario, simulator) + env_add_params = self.env_params.additional_params + # tells how scaled the number of lanes are + self.scaling = scenario.net_params.additional_params.get("scaling", 1) + self.edge_dict = dict() + self.cars_waiting_for_toll = dict() + self.cars_before_ramp = dict() + self.toll_wait_time = np.abs( + np.random.normal(MEAN_NUM_SECONDS_WAIT_AT_TOLL / self.sim_step, + 4 / self.sim_step, NUM_TOLL_LANES * self.scaling)) + self.fast_track_lanes = range( + int(np.ceil(1.5 * self.scaling)), int(np.ceil(2.6 * self.scaling))) + + self.tl_state = "" + + # values for the ALINEA ramp meter algorithm + self.n_crit = env_add_params.get("n_crit", 8) + self.q_max = env_add_params.get("q_max", 1100) + self.q_min = env_add_params.get("q_min", .25 * 1100) + self.q = self.q_min # ramp meter feedback controller + self.feedback_update_time = env_add_params.get("feedback_update", 15) + self.feedback_timer = 0.0 + self.cycle_time = 6 + cycle_offset = 8 + self.ramp_state = np.linspace(0, + cycle_offset * self.scaling * MAX_LANES, + self.scaling * MAX_LANES) + self.green_time = 4 + self.feedback_coeff = env_add_params.get("feedback_coeff", 20) + + self.smoothed_num = np.zeros(10) # averaged number of vehs in '4' + self.outflow_index = 0 + + def additional_command(self): + """Build a dict with vehicle information. + + The dict contains the list of vehicles and their position for each edge + and for each edge within the edge. + """ + super().additional_command() + + # build a dict containing the list of vehicles and their position for + # each edge and for each lane within the edge + empty_edge = [[] for _ in range(MAX_LANES * self.scaling)] + + self.edge_dict = {k: deepcopy(empty_edge) for k in EDGE_LIST} + for veh_id in self.k.vehicle.get_ids(): + try: + edge = self.k.vehicle.get_edge(veh_id) + if edge not in self.edge_dict: + self.edge_dict[edge] = deepcopy(empty_edge) + lane = self.k.vehicle.get_lane(veh_id) # integer + pos = self.k.vehicle.get_position(veh_id) + self.edge_dict[edge][lane].append((veh_id, pos)) + except Exception: + pass + + if not self.env_params.additional_params['disable_tb']: + self.apply_toll_bridge_control() + if not self.env_params.additional_params['disable_ramp_metering']: + self.ramp_meter_lane_change_control() + self.alinea() + + # compute the outflow + veh_ids = self.k.vehicle.get_ids_by_edge('4') + self.smoothed_num[self.outflow_index] = len(veh_ids) + self.outflow_index = \ + (self.outflow_index + 1) % self.smoothed_num.shape[0] + + def ramp_meter_lane_change_control(self): + """Control lane change behavior of vehicles near the ramp meters. + + If the traffic lights after the toll booth are enabled + ('disable_ramp_metering' is False), we want to change the lane changing + behavior of vehicles approaching the lights so that they stop changing + lanes. This method disables their lane changing before the light and + re-enables it after they have passed the light. + + Additionally, to visually make it clearer that the lane changing + behavior of the vehicles has been adjusted, we temporary set the color + of the affected vehicles to light blue. + """ + cars_that_have_left = [] + for veh_id in self.cars_before_ramp: + if self.k.vehicle.get_edge(veh_id) == EDGE_AFTER_RAMP_METER: + color = self.cars_before_ramp[veh_id]['color'] + self.k.vehicle.set_color(veh_id, color) + if self.simulator == 'traci': + lane_change_mode = self.cars_before_ramp[veh_id][ + 'lane_change_mode'] + self.k.kernel_api.vehicle.setLaneChangeMode( + veh_id, lane_change_mode) + cars_that_have_left.append(veh_id) + + for veh_id in cars_that_have_left: + del self.cars_before_ramp[veh_id] + + for lane in range(NUM_RAMP_METERS * self.scaling): + cars_in_lane = self.edge_dict[EDGE_BEFORE_RAMP_METER][lane] + + for veh_id, pos in cars_in_lane: + if pos > RAMP_METER_AREA: + if veh_id not in self.cars_waiting_for_toll: + if self.simulator == 'traci': + # Disable lane changes inside Toll Area + lane_change_mode = \ + self.k.kernel_api.vehicle.getLaneChangeMode( + veh_id) + self.k.kernel_api.vehicle.setLaneChangeMode( + veh_id, 512) + else: + lane_change_mode = None + color = self.k.vehicle.get_color(veh_id) + self.k.vehicle.set_color(veh_id, (0, 255, 255)) + self.cars_before_ramp[veh_id] = { + 'lane_change_mode': lane_change_mode, + 'color': color + } + + def alinea(self): + """Utilize the ALINEA algorithm for toll booth metering control. + + This acts as an implementation of the ramp metering control algorithm + from the article: + + Spiliopoulou, Anastasia D., Ioannis Papamichail, and Markos + Papageorgiou. "Toll plaza merging traffic control for throughput + maximization." Journal of Transportation Engineering 136.1 (2009): + 67-76. + + Essentially, we apply feedback control around the value self.n_crit. + We keep track of the number of vehicles in edge 4, average them across + time ot get a smoothed value and then compute + q_{t+1} = clip(q_t + K * (n_crit - n_avg), q_min, q_max). We then + convert this into a cycle_time value via cycle_time = 7200 / q. + Cycle_time = self.green_time + red_time i.e. the first self.green_time + seconds of a cycle will be green, and the remainder will be all red. + """ + self.feedback_timer += self.sim_step + self.ramp_state += self.sim_step + if self.feedback_timer > self.feedback_update_time: + self.feedback_timer = 0 + # now implement the integral controller update + # find all the vehicles in an edge + q_update = self.feedback_coeff * ( + self.n_crit - np.average(self.smoothed_num)) + self.q = np.clip( + self.q + q_update, a_min=self.q_min, a_max=self.q_max) + # convert q to cycle time + self.cycle_time = 7200 / self.q + + # now apply the ramp meter + self.ramp_state %= self.cycle_time + # step through, if the value of tl_state is below self.green_time + # we should be green, otherwise we should be red + tl_mask = (self.ramp_state <= self.green_time) + colors = ['G' if val else 'r' for val in tl_mask] + self.k.traffic_light.set_state('3', ''.join(colors)) + + def apply_toll_bridge_control(self): + """Apply control to the toll bridge. + + Vehicles approaching the toll region slow down and stop lane changing. + + If 'disable_tb' is set to False, vehicles within TOLL_BOOTH_AREA of the + end of edge EDGE_BEFORE_TOLL are labelled as approaching the toll + booth. Their color changes and their lane changing is disabled. To + force them to slow down/mimic the effect of the toll booth, we sample + from a random normal distribution with mean + MEAN_NUM_SECONDS_WAIT_AT_TOLL and std-dev 1/self.sim_step to get how + long a vehicle should wait. We then turn on a red light for that many + seconds. + """ + cars_that_have_left = [] + for veh_id in self.cars_waiting_for_toll: + if self.k.vehicle.get_edge(veh_id) == EDGE_AFTER_TOLL: + lane = self.k.vehicle.get_lane(veh_id) + color = self.cars_waiting_for_toll[veh_id]["color"] + self.k.vehicle.set_color(veh_id, color) + if self.simulator == 'traci': + lane_change_mode = \ + self.cars_waiting_for_toll[veh_id]["lane_change_mode"] + self.k.kernel_api.vehicle.setLaneChangeMode( + veh_id, lane_change_mode) + if lane not in self.fast_track_lanes: + self.toll_wait_time[lane] = max( + 0, + np.random.normal( + MEAN_NUM_SECONDS_WAIT_AT_TOLL / self.sim_step, + 1 / self.sim_step)) + else: + self.toll_wait_time[lane] = max( + 0, + np.random.normal( + MEAN_NUM_SECONDS_WAIT_AT_FAST_TRACK / + self.sim_step, 1 / self.sim_step)) + + cars_that_have_left.append(veh_id) + + for veh_id in cars_that_have_left: + del self.cars_waiting_for_toll[veh_id] + + traffic_light_states = ["G"] * NUM_TOLL_LANES * self.scaling + + for lane in range(NUM_TOLL_LANES * self.scaling): + cars_in_lane = self.edge_dict[EDGE_BEFORE_TOLL][lane] + + for veh_id, pos in cars_in_lane: + if pos > TOLL_BOOTH_AREA: + if veh_id not in self.cars_waiting_for_toll: + # Disable lane changes inside Toll Area + if self.simulator == 'traci': + lane_change_mode = self.k.kernel_api.vehicle.\ + getLaneChangeMode(veh_id) + self.k.kernel_api.vehicle.setLaneChangeMode( + veh_id, 512) + else: + lane_change_mode = None + color = self.k.vehicle.get_color(veh_id) + self.k.vehicle.set_color(veh_id, (255, 0, 255)) + self.cars_waiting_for_toll[veh_id] = \ + {'lane_change_mode': lane_change_mode, + 'color': color} + else: + if pos > 50: + if self.toll_wait_time[lane] < 0: + traffic_light_states[lane] = "G" + else: + traffic_light_states[lane] = "r" + self.toll_wait_time[lane] -= 1 + + new_tl_state = "".join(traffic_light_states) + + if new_tl_state != self.tl_state: + self.tl_state = new_tl_state + self.k.traffic_light.set_state( + node_id=TB_TL_ID, state=new_tl_state) + + def get_bottleneck_density(self, lanes=None): + """Return the density of specified lanes. + + If no lanes are specified, this function calculates the + density of all vehicles on all lanes of the bottleneck edges. + """ + bottleneck_ids = self.k.vehicle.get_ids_by_edge(['3', '4']) + if lanes: + veh_ids = [ + veh_id for veh_id in bottleneck_ids + if str(self.k.vehicle.get_edge(veh_id)) + "_" + + str(self.k.vehicle.get_lane(veh_id)) in lanes + ] + else: + veh_ids = self.k.vehicle.get_ids_by_edge(['3', '4']) + return len(veh_ids) / BOTTLE_NECK_LEN + + # Dummy action and observation spaces + @property + def action_space(self): + """See class definition.""" + return Box( + low=-float("inf"), + high=float("inf"), + shape=(1, ), + dtype=np.float32) + + @property + def observation_space(self): + """See class definition.""" + return Box( + low=-float("inf"), + high=float("inf"), + shape=(1, ), + dtype=np.float32) + + def compute_reward(self, rl_actions, **kwargs): + """Outflow rate over last ten seconds normalized to max of 1.""" + reward = self.k.vehicle.get_outflow_rate(10 * self.sim_step) / \ + (2000.0 * self.scaling) + return reward + + def get_state(self): + """See class definition.""" + return np.asarray([1]) + + +class BottleneckAccelEnv(BottleneckEnv): + """BottleneckAccelEnv. + + Environment used to train vehicles to effectively pass through a + bottleneck. + + States + An observation is the edge position, speed, lane, and edge number of + the AV, the distance to and velocity of the vehicles + in front and behind the AV for all lanes. Additionally, we pass the + density and average velocity of all edges. Finally, we pad with + zeros in case an AV has exited the system. + Note: the vehicles are arranged in an initial order, so we pad + the missing vehicle at its normal position in the order + + Actions + The action space consist of a list in which the first half + is accelerations and the second half is a direction for lane + changing that we round + + Rewards + The reward is the two-norm of the difference between the speed of + all vehicles in the network and some desired speed. To this we add + a positive reward for moving the vehicles forward, and a penalty to + vehicles that lane changing too frequently. + + Termination + A rollout is terminated once the time horizon is reached. + """ + + def __init__(self, env_params, sim_params, scenario, simulator='traci'): + """Initialize BottleneckAccelEnv.""" + for p in ADDITIONAL_RL_ENV_PARAMS.keys(): + if p not in env_params.additional_params: + raise KeyError( + 'Environment parameter "{}" not supplied'.format(p)) + + super().__init__(env_params, sim_params, scenario, simulator) + self.add_rl_if_exit = env_params.get_additional_param("add_rl_if_exit") + self.num_rl = deepcopy(self.initial_vehicles.num_rl_vehicles) + self.rl_id_list = deepcopy(self.initial_vehicles.get_rl_ids()) + self.max_speed = self.k.scenario.max_speed() + + @property + def observation_space(self): + """See class definition.""" + num_edges = len(self.k.scenario.get_edge_list()) + num_rl_veh = self.num_rl + num_obs = 2 * num_edges + 4 * MAX_LANES * self.scaling \ + * num_rl_veh + 4 * num_rl_veh + + return Box(low=0, high=1, shape=(num_obs, ), dtype=np.float32) + + def get_state(self): + """See class definition.""" + headway_scale = 1000 + + rl_ids = self.k.vehicle.get_rl_ids() + + # rl vehicle data (absolute position, speed, and lane index) + rl_obs = np.empty(0) + id_counter = 0 + for veh_id in rl_ids: + # check if we have skipped a vehicle, if not, pad + rl_id_num = self.rl_id_list.index(veh_id) + if rl_id_num != id_counter: + rl_obs = np.concatenate( + (rl_obs, np.zeros(4 * (rl_id_num - id_counter)))) + id_counter = rl_id_num + 1 + else: + id_counter += 1 + + # get the edge and convert it to a number + edge_num = self.k.vehicle.get_edge(veh_id) + if edge_num is None or edge_num == '' or edge_num[0] == ':': + edge_num = -1 + else: + edge_num = int(edge_num) / 6 + rl_obs = np.concatenate((rl_obs, [ + self.k.vehicle.get_x_by_id(veh_id) / 1000, + (self.k.vehicle.get_speed(veh_id) / self.max_speed), + (self.k.vehicle.get_lane(veh_id) / MAX_LANES), edge_num + ])) + + # if all the missing vehicles are at the end, pad + diff = self.num_rl - int(rl_obs.shape[0] / 4) + if diff > 0: + rl_obs = np.concatenate((rl_obs, np.zeros(4 * diff))) + + # relative vehicles data (lane headways, tailways, vel_ahead, and + # vel_behind) + relative_obs = np.empty(0) + id_counter = 0 + for veh_id in rl_ids: + # check if we have skipped a vehicle, if not, pad + rl_id_num = self.rl_id_list.index(veh_id) + if rl_id_num != id_counter: + pad_mat = np.zeros( + 4 * MAX_LANES * self.scaling * (rl_id_num - id_counter)) + relative_obs = np.concatenate((relative_obs, pad_mat)) + id_counter = rl_id_num + 1 + else: + id_counter += 1 + num_lanes = MAX_LANES * self.scaling + headway = np.asarray([1000] * num_lanes) / headway_scale + tailway = np.asarray([1000] * num_lanes) / headway_scale + vel_in_front = np.asarray([0] * num_lanes) / self.max_speed + vel_behind = np.asarray([0] * num_lanes) / self.max_speed + + lane_leaders = self.k.vehicle.get_lane_leaders(veh_id) + lane_followers = self.k.vehicle.get_lane_followers(veh_id) + lane_headways = self.k.vehicle.get_lane_headways(veh_id) + lane_tailways = self.k.vehicle.get_lane_tailways(veh_id) + headway[0:len(lane_headways)] = ( + np.asarray(lane_headways) / headway_scale) + tailway[0:len(lane_tailways)] = ( + np.asarray(lane_tailways) / headway_scale) + for i, lane_leader in enumerate(lane_leaders): + if lane_leader != '': + vel_in_front[i] = ( + self.k.vehicle.get_speed(lane_leader) / self.max_speed) + for i, lane_follower in enumerate(lane_followers): + if lane_followers != '': + vel_behind[i] = (self.k.vehicle.get_speed(lane_follower) / + self.max_speed) + + relative_obs = np.concatenate((relative_obs, headway, tailway, + vel_in_front, vel_behind)) + + # if all the missing vehicles are at the end, pad + diff = self.num_rl - int(relative_obs.shape[0] / (4 * MAX_LANES)) + if diff > 0: + relative_obs = np.concatenate((relative_obs, + np.zeros(4 * MAX_LANES * diff))) + + # per edge data (average speed, density + edge_obs = [] + for edge in self.k.scenario.get_edge_list(): + veh_ids = self.k.vehicle.get_ids_by_edge(edge) + if len(veh_ids) > 0: + avg_speed = (sum(self.k.vehicle.get_speed(veh_ids)) / + len(veh_ids)) / self.max_speed + density = len(veh_ids) / self.k.scenario.edge_length(edge) + edge_obs += [avg_speed, density] + else: + edge_obs += [0, 0] + + return np.concatenate((rl_obs, relative_obs, edge_obs)) + + def compute_reward(self, rl_actions, **kwargs): + """See class definition.""" + num_rl = self.k.vehicle.num_rl_vehicles + lane_change_acts = np.abs(np.round(rl_actions[1::2])[:num_rl]) + return (rewards.desired_velocity(self) + rewards.rl_forward_progress( + self, gain=0.1) - rewards.boolean_action_penalty( + lane_change_acts, gain=1.0)) + + @property + def action_space(self): + """See class definition.""" + max_decel = self.env_params.additional_params["max_decel"] + max_accel = self.env_params.additional_params["max_accel"] + + lb = [-abs(max_decel), -1] * self.num_rl + ub = [max_accel, 1] * self.num_rl + + return Box(np.array(lb), np.array(ub), dtype=np.float32) + + def _apply_rl_actions(self, actions): + """ + See parent class. + + Takes a tuple and applies a lane change or acceleration. if a lane + change is applied, don't issue any commands + for the duration of the lane change and return negative rewards + for actions during that lane change. if a lane change isn't applied, + and sufficient time has passed, issue an acceleration like normal. + """ + num_rl = self.k.vehicle.num_rl_vehicles + acceleration = actions[::2][:num_rl] + direction = np.round(actions[1::2])[:num_rl] + + # re-arrange actions according to mapping in observation space + sorted_rl_ids = sorted(self.k.vehicle.get_rl_ids(), + key=self.k.vehicle.get_x_by_id) + + # represents vehicles that are allowed to change lanes + non_lane_changing_veh = [ + self.time_counter <= self.env_params.additional_params[ + 'lane_change_duration'] + self.k.vehicle.get_last_lc(veh_id) + for veh_id in sorted_rl_ids] + + # vehicle that are not allowed to change have their directions set to 0 + direction[non_lane_changing_veh] = \ + np.array([0] * sum(non_lane_changing_veh)) + + self.k.vehicle.apply_acceleration(sorted_rl_ids, acc=acceleration) + self.k.vehicle.apply_lane_change(sorted_rl_ids, direction=direction) + + def additional_command(self): + """Reintroduce any RL vehicle that may have exited in the last step. + + This is used to maintain a constant number of RL vehicle in the system + at all times, in order to comply with a fixed size observation and + action space. + """ + super().additional_command() + # if the number of rl vehicles has decreased introduce it back in + num_rl = self.k.vehicle.num_rl_vehicles + if num_rl != len(self.rl_id_list) and self.add_rl_if_exit: + # find the vehicles that have exited + diff_list = list( + set(self.rl_id_list).difference(self.k.vehicle.get_rl_ids())) + for rl_id in diff_list: + # distribute rl cars evenly over lanes + lane_num = self.rl_id_list.index(rl_id) % \ + MAX_LANES * self.scaling + # reintroduce it at the start of the network + try: + self.k.vehicle.add( + veh_id=rl_id, + edge='1', + type_id=str('rl'), + lane=str(lane_num), + pos="0", + speed="max") + except Exception: + pass + + +class BottleneckDesiredVelocityEnv(BottleneckEnv): + """BottleneckDesiredVelocityEnv. + + Environment used to train vehicles to effectively pass through a + bottleneck by specifying the velocity that RL vehicles should attempt to + travel in certain regions of space. + + States + An observation is the number of vehicles in each lane in each + segment + + Actions + The action space consist of a list in which each element + corresponds to the desired speed that RL vehicles should travel in + that region of space + + Rewards + The reward is the outflow of the bottleneck plus a reward + for RL vehicles making forward progress + """ + + def __init__(self, env_params, sim_params, scenario, simulator='traci'): + """Initialize BottleneckDesiredVelocityEnv.""" + super().__init__(env_params, sim_params, scenario, simulator) + for p in ADDITIONAL_VSL_ENV_PARAMS.keys(): + if p not in env_params.additional_params: + raise KeyError( + 'Environment parameter "{}" not supplied'.format(p)) + + # default (edge, segment, controlled) status + add_env_params = self.env_params.additional_params + default = [(str(i), 1, True) for i in range(1, 6)] + super(BottleneckDesiredVelocityEnv, self).__init__(env_params, + sim_params, + scenario) + self.segments = add_env_params.get("controlled_segments", default) + + # number of segments for each edge + self.num_segments = [segment[1] for segment in self.segments] + + # whether an edge is controlled + self.is_controlled = [segment[2] for segment in self.segments] + + self.num_controlled_segments = [ + segment[1] for segment in self.segments if segment[2] + ] + + # sum of segments + self.total_segments = int( + np.sum([segment[1] for segment in self.segments])) + # sum of controlled segments + segment_list = [segment[1] for segment in self.segments if segment[2]] + self.total_controlled_segments = int(np.sum(segment_list)) + + # list of controlled edges for comparison + self.controlled_edges = [ + segment[0] for segment in self.segments if segment[2] + ] + + additional_params = env_params.additional_params + + # for convenience, construct the relevant positions defining + # segments within edges + # self.slices is a dictionary mapping + # edge (str) -> segment start location (list of int) + self.slices = {} + for edge, num_segments, _ in self.segments: + edge_length = self.k.scenario.edge_length(edge) + self.slices[edge] = np.linspace(0, edge_length, num_segments + 1) + + # get info for observed segments + self.obs_segments = additional_params.get("observed_segments", []) + + # number of segments for each edge + self.num_obs_segments = [segment[1] for segment in self.obs_segments] + + # for convenience, construct the relevant positions defining + # segments within edges + # self.slices is a dictionary mapping + # edge (str) -> segment start location (list of int) + self.obs_slices = {} + for edge, num_segments in self.obs_segments: + edge_length = self.k.scenario.edge_length(edge) + self.obs_slices[edge] = np.linspace(0, edge_length, + num_segments + 1) + + # self.symmetric is True if all lanes in a segment + # have same action, else False + self.symmetric = additional_params.get("symmetric") + + # action index tells us, given an edge and a lane,the offset into + # rl_actions that we should take. + self.action_index = [0] + for i, (edge, segment, controlled) in enumerate(self.segments[:-1]): + if self.symmetric: + self.action_index += [ + self.action_index[i] + segment * controlled + ] + else: + num_lanes = self.k.scenario.num_lanes(edge) + self.action_index += [ + self.action_index[i] + segment * controlled * num_lanes + ] + + self.action_index = {} + action_list = [0] + index = 0 + for (edge, num_segments, controlled) in self.segments: + if controlled: + if self.symmetric: + self.action_index[edge] = [action_list[index]] + action_list += [action_list[index] + controlled] + else: + num_lanes = self.k.scenario.num_lanes(edge) + self.action_index[edge] = [action_list[index]] + action_list += [ + action_list[index] + + num_segments * controlled * num_lanes + ] + index += 1 + + @property + def observation_space(self): + """See class definition.""" + num_obs = 0 + # density and velocity for rl and non-rl vehicles per segment + # Last element is the outflow + for segment in self.obs_segments: + num_obs += 4 * segment[1] * self.k.scenario.num_lanes(segment[0]) + num_obs += 1 + return Box(low=0.0, high=1.0, shape=(num_obs, ), dtype=np.float32) + + @property + def action_space(self): + """See class definition.""" + if self.symmetric: + action_size = self.total_controlled_segments + else: + action_size = 0.0 + for segment in self.segments: # iterate over segments + if segment[2]: # if controlled + num_lanes = self.k.scenario.num_lanes(segment[0]) + action_size += num_lanes * segment[1] + add_params = self.env_params.additional_params + max_accel = add_params.get("max_accel") + max_decel = add_params.get("max_decel") + return Box( + low=-max_decel*self.sim_step, high=max_accel*self.sim_step, + shape=(int(action_size), ), dtype=np.float32) + + def get_state(self): + """Return aggregate statistics of different segments of the bottleneck. + + The state space of the system is defined by splitting the bottleneck up + into edges and then segments in each edge. The class variable + self.num_obs_segments specifies how many segments each edge is cut up + into. Each lane defines a unique segment: we refer to this as a + lane-segment. For example, if edge 1 has four lanes and three segments, + then we have a total of 12 lane-segments. We will track the aggregate + statistics of the vehicles in each lane segment. + + For each lane-segment we return the: + + * Number of vehicles on that segment. + * Number of AVs (referred to here as rl_vehicles) in the segment. + * The average speed of the vehicles in that segment. + * The average speed of the rl vehicles in that segment. + + Finally, we also append the total outflow of the bottleneck over the + last 20 * self.sim_step seconds. + """ + num_vehicles_list = [] + num_rl_vehicles_list = [] + vehicle_speeds_list = [] + rl_speeds_list = [] + for i, edge in enumerate(EDGE_LIST): + num_lanes = self.k.scenario.num_lanes(edge) + num_vehicles = np.zeros((self.num_obs_segments[i], num_lanes)) + num_rl_vehicles = np.zeros((self.num_obs_segments[i], num_lanes)) + vehicle_speeds = np.zeros((self.num_obs_segments[i], num_lanes)) + rl_vehicle_speeds = np.zeros((self.num_obs_segments[i], num_lanes)) + ids = self.k.vehicle.get_ids_by_edge(edge) + lane_list = self.k.vehicle.get_lane(ids) + pos_list = self.k.vehicle.get_position(ids) + for i, id in enumerate(ids): + segment = np.searchsorted(self.obs_slices[edge], + pos_list[i]) - 1 + if id in self.k.vehicle.get_rl_ids(): + rl_vehicle_speeds[segment, lane_list[i]] \ + += self.k.vehicle.get_speed(id) + num_rl_vehicles[segment, lane_list[i]] += 1 + else: + vehicle_speeds[segment, lane_list[i]] \ + += self.k.vehicle.get_speed(id) + num_vehicles[segment, lane_list[i]] += 1 + + # normalize + + num_vehicles /= NUM_VEHICLE_NORM + num_rl_vehicles /= NUM_VEHICLE_NORM + num_vehicles_list += num_vehicles.flatten().tolist() + num_rl_vehicles_list += num_rl_vehicles.flatten().tolist() + vehicle_speeds_list += vehicle_speeds.flatten().tolist() + rl_speeds_list += rl_vehicle_speeds.flatten().tolist() + + unnorm_veh_list = np.asarray(num_vehicles_list) * NUM_VEHICLE_NORM + unnorm_rl_list = np.asarray(num_rl_vehicles_list) * NUM_VEHICLE_NORM + + # compute the mean speed if the speed isn't zero + num_rl = len(num_rl_vehicles_list) + num_veh = len(num_vehicles_list) + mean_speed = np.nan_to_num([ + vehicle_speeds_list[i] / unnorm_veh_list[i] + if int(unnorm_veh_list[i]) else 0 for i in range(num_veh) + ]) + mean_speed_norm = mean_speed / 50 + mean_rl_speed = np.nan_to_num([ + rl_speeds_list[i] / unnorm_rl_list[i] + if int(unnorm_rl_list[i]) else 0 for i in range(num_rl) + ]) / 50 + outflow = np.asarray( + self.k.vehicle.get_outflow_rate(20 * self.sim_step) / 2000.0) + return np.concatenate((num_vehicles_list, num_rl_vehicles_list, + mean_speed_norm, mean_rl_speed, [outflow])) + + def _apply_rl_actions(self, rl_actions): + """ + RL actions are split up into 3 levels. + + * First, they're split into edge actions. + * Then they're split into segment actions. + * Then they're split into lane actions. + """ + for rl_id in self.k.vehicle.get_rl_ids(): + edge = self.k.vehicle.get_edge(rl_id) + lane = self.k.vehicle.get_lane(rl_id) + if edge: + # If in outer lanes, on a controlled edge, in a controlled lane + if edge[0] != ':' and edge in self.controlled_edges: + pos = self.k.vehicle.get_position(rl_id) + + if not self.symmetric: + num_lanes = self.k.scenario.num_lanes(edge) + # find what segment we fall into + bucket = np.searchsorted(self.slices[edge], pos) - 1 + action = rl_actions[int(lane) + bucket * num_lanes + + self.action_index[edge]] + else: + # find what segment we fall into + bucket = np.searchsorted(self.slices[edge], pos) - 1 + action = rl_actions[bucket + self.action_index[edge]] + + max_speed_curr = self.k.vehicle.get_max_speed(rl_id) + next_max = np.clip(max_speed_curr + action, 0.01, 23.0) + self.k.vehicle.set_max_speed(rl_id, next_max) + + else: + # set the desired velocity of the controller to the default + self.k.vehicle.set_max_speed(rl_id, 23.0) + + def compute_reward(self, rl_actions, **kwargs): + """Outflow rate over last ten seconds normalized to max of 1.""" + if self.env_params.evaluate: + if self.time_counter == self.env_params.horizon: + reward = self.k.vehicle.get_outflow_rate(500) + else: + return 0 + else: + reward = self.k.vehicle.get_outflow_rate(10 * self.sim_step) / \ + (2000.0 * self.scaling) + return reward + + def reset(self): + """Reset the environment with a new inflow rate. + + The diverse set of inflows are used to generate a policy that is more + robust with respect to the inflow rate. The inflow rate is update by + creating a new scenario similar to the previous one, but with a new + Inflow object with a rate within the additional environment parameter + "inflow_range", which is a list consisting of the smallest and largest + allowable inflow rates. + + **WARNING**: The inflows assume there are vehicles of type + "followerstopper" and "human" within the VehicleParams object. + """ + add_params = self.env_params.additional_params + if add_params.get("reset_inflow"): + inflow_range = add_params.get("inflow_range") + flow_rate = np.random.uniform( + min(inflow_range), max(inflow_range)) * self.scaling + + # We try this for 100 trials in case unexpected errors during + # instantiation. + for _ in range(100): + try: + # introduce new inflows within the pre-defined inflow range + inflow = InFlows() + inflow.add( + veh_type="followerstopper", # FIXME: make generic + edge="1", + vehs_per_hour=flow_rate * .1, + departLane="random", + departSpeed=10) + inflow.add( + veh_type="human", + edge="1", + vehs_per_hour=flow_rate * .9, + departLane="random", + departSpeed=10) + + # all other network parameters should match the previous + # environment (we only want to change the inflow) + additional_net_params = { + "scaling": self.scaling, + "speed_limit": self.net_params. + additional_params['speed_limit'] + } + net_params = NetParams( + inflows=inflow, + additional_params=additional_net_params) + + vehicles = VehicleParams() + vehicles.add( + veh_id="human", # FIXME: make generic + car_following_params=SumoCarFollowingParams( + speed_mode=9, + ), + lane_change_controller=(SimLaneChangeController, {}), + routing_controller=(ContinuousRouter, {}), + lane_change_params=SumoLaneChangeParams( + lane_change_mode=0, # 1621,#0b100000101, + ), + num_vehicles=1 * self.scaling) + vehicles.add( + veh_id="followerstopper", + acceleration_controller=(RLController, {}), + lane_change_controller=(SimLaneChangeController, {}), + routing_controller=(ContinuousRouter, {}), + car_following_params=SumoCarFollowingParams( + speed_mode=9, + ), + lane_change_params=SumoLaneChangeParams( + lane_change_mode=0, + ), + num_vehicles=1 * self.scaling) + + # recreate the scenario object + self.scenario = self.scenario.__class__( + name=self.scenario.orig_name, + vehicles=vehicles, + net_params=net_params, + initial_config=self.initial_config, + traffic_lights=self.scenario.traffic_lights) + observation = super().reset() + + # reset the timer to zero + self.time_counter = 0 + + return observation + + except Exception as e: + print('error on reset ', e) + + # perform the generic reset function + observation = super().reset() + + # reset the timer to zero + self.time_counter = 0 + + return observation diff --git a/flow/envs/bottleneck_env.py b/flow/envs/bottleneck_env.py index 97f677759c..4106fd9ec0 100644 --- a/flow/envs/bottleneck_env.py +++ b/flow/envs/bottleneck_env.py @@ -1,1084 +1,32 @@ -""" -Environments for training vehicles to reduce capacity drops in a bottleneck. - -This environment was used in: +"""Pending deprecation file. -E. Vinitsky, K. Parvate, A. Kreidieh, C. Wu, Z. Hu, A. Bayen, "Lagrangian -Control through Deep-RL: Applications to Bottleneck Decongestion," IEEE -Intelligent Transportation Systems Conference (ITSC), 2018. +To view the actual content, go to: flow/envs/bottleneck.py """ +from flow.utils.flow_warnings import deprecated +from flow.envs.bottleneck import BottleneckEnv as BEnv +from flow.envs.bottleneck import BottleneckAccelEnv as BAEnv +from flow.envs.bottleneck import BottleneckDesiredVelocityEnv as BDVEnv -from flow.controllers.rlcontroller import RLController -from flow.controllers.lane_change_controllers import SimLaneChangeController -from flow.controllers.routing_controllers import ContinuousRouter -from flow.core.params import InFlows, NetParams -from flow.core.params import SumoCarFollowingParams, SumoLaneChangeParams -from flow.core.params import VehicleParams - -from copy import deepcopy - -import numpy as np -from gym.spaces.box import Box - -from flow.core import rewards -from flow.envs.base_env import Env - -MAX_LANES = 4 # base number of largest number of lanes in the network -EDGE_LIST = ["1", "2", "3", "4", "5"] # Edge 1 is before the toll booth -EDGE_BEFORE_TOLL = "1" # Specifies which edge number is before toll booth -TB_TL_ID = "2" -EDGE_AFTER_TOLL = "2" # Specifies which edge number is after toll booth -NUM_TOLL_LANES = MAX_LANES - -TOLL_BOOTH_AREA = 10 # how far into the edge lane changing is disabled -RED_LIGHT_DIST = 50 # how close for the ramp meter to start going off - -EDGE_BEFORE_RAMP_METER = "2" # Specifies which edge is before ramp meter -EDGE_AFTER_RAMP_METER = "3" # Specifies which edge is after ramp meter -NUM_RAMP_METERS = MAX_LANES - -RAMP_METER_AREA = 80 # Area occupied by ramp meter - -MEAN_NUM_SECONDS_WAIT_AT_FAST_TRACK = 3 # Average waiting time at fast track -MEAN_NUM_SECONDS_WAIT_AT_TOLL = 15 # Average waiting time at toll - -BOTTLE_NECK_LEN = 280 # Length of bottleneck -NUM_VEHICLE_NORM = 20 - -ADDITIONAL_ENV_PARAMS = { - # maximum acceleration for autonomous vehicles, in m/s^2 - "max_accel": 3, - # maximum deceleration for autonomous vehicles, in m/s^2 - "max_decel": 3, - # lane change duration for autonomous vehicles, in s. Autonomous vehicles - # reject new lane changing commands for this duration after successfully - # changing lanes. - "lane_change_duration": 5, - # whether the toll booth should be active - "disable_tb": True, - # whether the ramp meter is active - "disable_ramp_metering": True, -} - -# Keys for RL experiments -ADDITIONAL_RL_ENV_PARAMS = { - # velocity to use in reward functions - "target_velocity": 30, - # if an RL vehicle exits, place it back at the front - "add_rl_if_exit": True, -} - -# Keys for VSL style experiments -ADDITIONAL_VSL_ENV_PARAMS = { - # number of controlled regions for velocity bottleneck controller - "controlled_segments": [("1", 1, True), ("2", 1, True), ("3", 1, True), - ("4", 1, True), ("5", 1, True)], - # whether lanes in a segment have the same action or not - "symmetric": - False, - # which edges are observed - "observed_segments": [("1", 1), ("2", 1), ("3", 1), ("4", 1), ("5", 1)], - # whether the inflow should be reset on each rollout - "reset_inflow": - False, - # the range of inflows to reset on - "inflow_range": [1000, 2000] -} - -START_RECORD_TIME = 0.0 # Time to start recording -PERIOD = 10.0 - - -class BottleneckEnv(Env): - """Abstract bottleneck environment. - - This environment is used as a simplified representation of the toll booth - portion of the bay bridge. Contains ramp meters, and a toll both. - - Additional - ---------- - Vehicles are rerouted to the start of their original routes once - they reach the end of the network in order to ensure a constant - number of vehicles. - - Attributes - ---------- - scaling : int - A factor describing how many lanes are in the system. Scaling=1 implies - 4 lanes going to 2 going to 1, scaling=2 implies 8 lanes going to 4 - going to 2, etc. - edge_dict : dict of dicts - A dict mapping edges to a dict of lanes where each entry in the lane - dict tracks the vehicles that are in that lane. Used to save on - unnecessary lookups. - cars_waiting_for_toll : {veh_id: {lane_change_mode: int, color: (int)}} - A dict mapping vehicle ids to a dict tracking the color and lane change - mode of vehicles before they entered the toll area. When vehicles exit - the tollbooth area, these values are used to set the lane change mode - and color of the vehicle back to how they were before they entered the - toll area. - cars_before_ramp : {veh_id: {lane_change_mode: int, color: (int)}} - Identical to cars_waiting_for_toll, but used to track cars approaching - the ramp meter versus approaching the tollbooth. - toll_wait_time : np.ndarray(float) - Random value, sampled from a gaussian indicating how much a vehicle in - each lane should wait to pass through the toll area. This value is - re-sampled for each approaching vehicle. That is, whenever a vehicle - approaches the toll area, we re-sample from the Gaussian to determine - its weight time. - fast_track_lanes : np.ndarray(int) - Middle lanes of the tollbooth are declared fast-track lanes, this numpy - array keeps track of which lanes these are. At a fast track lane, the - mean of the Gaussian from which we sample wait times is given by - MEAN_NUM_SECONDS_WAIT_AT_FAST_TRACK. - tl_state : str - String tracking the color of the traffic lights at the tollbooth. These - traffic lights are used imitate the effect of a tollbooth. If lane 1-4 - are respectively green, red, red, green, then this string would be - "GrrG" - n_crit : int - The ALINEA algorithm adjusts the ratio of red to green time for the - ramp-metering traffic light based on feedback on how congested the - system is. As the measure of congestion, we use the number of vehicles - stuck in the bottleneck (edge 4). The critical operating value it tries - to stabilize the number of vehicles in edge 4 is n_crit. If there are - more than n_crit vehicles on edge 4, we increase the fraction of red - time to decrease the inflow to edge 4. - q_max : float - The ALINEA algorithm tries to control the flow rate through the ramp - meter. q_max is the maximum possible estimated flow we allow through - the bottleneck and can be converted into a maximum value for the ratio - of green to red time that we allow. - q_min : float - Similar to q_max, this is used to set the minimum value of green to red - ratio that we allow. - q : float - This value tracks the flow we intend to allow through the bottleneck. - For details on how it is computed, please read the alinea method or the - paper linked in that method. - feedback_update_time : float - The parameters of the ALINEA algorithm are only updated every - feedback_update_time seconds. - feedback_timer : float - This keeps track of how many seconds have passed since the ALINEA - parameters were last updated. If it exceeds feedback_update_time, the - parameters are updated - cycle_time : int - This value tracks how long a green-red cycle of the ramp meter is. The - first self.green_time seconds will be green and the remainder of the - cycle will be red. - ramp_state : np.ndarray - Array of floats of length equal to the number of lanes. For each lane, - this value tracks how many seconds of a given cycle have passed in that - lane. Each lane is offset from its adjacent lanes by - cycle_offset/(self.scaling * MAX_LANES) seconds. This offsetting means - that lights are offset in when they releasse vehicles into the - bottleneck. This helps maximize the throughput of the ramp meter. - green_time : float - How many seconds of a given cycle the light should remain green 4. - Defaults to 4 as this is just enough time for two vehicles to enter the - bottleneck from a given traffic light. - feedback_coeff : float - This is the gain on the feedback in the ALINEA algorithm - smoothed_num : np.ndarray - Numpy array keeping track of how many vehicles were in edge 4 over the - last 10 time seconds. This provides a more stable estimate of the - number of vehicles in edge 4. - outflow_index : int - Keeps track of which index of smoothed_num we should update with the - latest number of vehicles in the bottleneck. Should eventually be - deprecated as smoothed_num should be switched to a queue instead of an - array. - """ - - def __init__(self, env_params, sim_params, scenario, simulator='traci'): - """Initialize the BottleneckEnv class.""" - for p in ADDITIONAL_ENV_PARAMS.keys(): - if p not in env_params.additional_params: - raise KeyError( - 'Environment parameter "{}" not supplied'.format(p)) - - super().__init__(env_params, sim_params, scenario, simulator) - env_add_params = self.env_params.additional_params - # tells how scaled the number of lanes are - self.scaling = scenario.net_params.additional_params.get("scaling", 1) - self.edge_dict = dict() - self.cars_waiting_for_toll = dict() - self.cars_before_ramp = dict() - self.toll_wait_time = np.abs( - np.random.normal(MEAN_NUM_SECONDS_WAIT_AT_TOLL / self.sim_step, - 4 / self.sim_step, NUM_TOLL_LANES * self.scaling)) - self.fast_track_lanes = range( - int(np.ceil(1.5 * self.scaling)), int(np.ceil(2.6 * self.scaling))) - - self.tl_state = "" - - # values for the ALINEA ramp meter algorithm - self.n_crit = env_add_params.get("n_crit", 8) - self.q_max = env_add_params.get("q_max", 1100) - self.q_min = env_add_params.get("q_min", .25 * 1100) - self.q = self.q_min # ramp meter feedback controller - self.feedback_update_time = env_add_params.get("feedback_update", 15) - self.feedback_timer = 0.0 - self.cycle_time = 6 - cycle_offset = 8 - self.ramp_state = np.linspace(0, - cycle_offset * self.scaling * MAX_LANES, - self.scaling * MAX_LANES) - self.green_time = 4 - self.feedback_coeff = env_add_params.get("feedback_coeff", 20) - - self.smoothed_num = np.zeros(10) # averaged number of vehs in '4' - self.outflow_index = 0 - - def additional_command(self): - """Build a dict with vehicle information. - - The dict contains the list of vehicles and their position for each edge - and for each edge within the edge. - """ - super().additional_command() - - # build a dict containing the list of vehicles and their position for - # each edge and for each lane within the edge - empty_edge = [[] for _ in range(MAX_LANES * self.scaling)] - - self.edge_dict = {k: deepcopy(empty_edge) for k in EDGE_LIST} - for veh_id in self.k.vehicle.get_ids(): - try: - edge = self.k.vehicle.get_edge(veh_id) - if edge not in self.edge_dict: - self.edge_dict[edge] = deepcopy(empty_edge) - lane = self.k.vehicle.get_lane(veh_id) # integer - pos = self.k.vehicle.get_position(veh_id) - self.edge_dict[edge][lane].append((veh_id, pos)) - except Exception: - pass - - if not self.env_params.additional_params['disable_tb']: - self.apply_toll_bridge_control() - if not self.env_params.additional_params['disable_ramp_metering']: - self.ramp_meter_lane_change_control() - self.alinea() - - # compute the outflow - veh_ids = self.k.vehicle.get_ids_by_edge('4') - self.smoothed_num[self.outflow_index] = len(veh_ids) - self.outflow_index = \ - (self.outflow_index + 1) % self.smoothed_num.shape[0] - - def ramp_meter_lane_change_control(self): - """Control lane change behavior of vehicles near the ramp meters. - - If the traffic lights after the toll booth are enabled - ('disable_ramp_metering' is False), we want to change the lane changing - behavior of vehicles approaching the lights so that they stop changing - lanes. This method disables their lane changing before the light and - re-enables it after they have passed the light. - - Additionally, to visually make it clearer that the lane changing - behavior of the vehicles has been adjusted, we temporary set the color - of the affected vehicles to light blue. - """ - cars_that_have_left = [] - for veh_id in self.cars_before_ramp: - if self.k.vehicle.get_edge(veh_id) == EDGE_AFTER_RAMP_METER: - color = self.cars_before_ramp[veh_id]['color'] - self.k.vehicle.set_color(veh_id, color) - if self.simulator == 'traci': - lane_change_mode = self.cars_before_ramp[veh_id][ - 'lane_change_mode'] - self.k.kernel_api.vehicle.setLaneChangeMode( - veh_id, lane_change_mode) - cars_that_have_left.append(veh_id) - - for veh_id in cars_that_have_left: - del self.cars_before_ramp[veh_id] - - for lane in range(NUM_RAMP_METERS * self.scaling): - cars_in_lane = self.edge_dict[EDGE_BEFORE_RAMP_METER][lane] - - for veh_id, pos in cars_in_lane: - if pos > RAMP_METER_AREA: - if veh_id not in self.cars_waiting_for_toll: - if self.simulator == 'traci': - # Disable lane changes inside Toll Area - lane_change_mode = \ - self.k.kernel_api.vehicle.getLaneChangeMode( - veh_id) - self.k.kernel_api.vehicle.setLaneChangeMode( - veh_id, 512) - else: - lane_change_mode = None - color = self.k.vehicle.get_color(veh_id) - self.k.vehicle.set_color(veh_id, (0, 255, 255)) - self.cars_before_ramp[veh_id] = { - 'lane_change_mode': lane_change_mode, - 'color': color - } - - def alinea(self): - """Utilize the ALINEA algorithm for toll booth metering control. - - This acts as an implementation of the ramp metering control algorithm - from the article: - - Spiliopoulou, Anastasia D., Ioannis Papamichail, and Markos - Papageorgiou. "Toll plaza merging traffic control for throughput - maximization." Journal of Transportation Engineering 136.1 (2009): - 67-76. - - Essentially, we apply feedback control around the value self.n_crit. - We keep track of the number of vehicles in edge 4, average them across - time ot get a smoothed value and then compute - q_{t+1} = clip(q_t + K * (n_crit - n_avg), q_min, q_max). We then - convert this into a cycle_time value via cycle_time = 7200 / q. - Cycle_time = self.green_time + red_time i.e. the first self.green_time - seconds of a cycle will be green, and the remainder will be all red. - """ - self.feedback_timer += self.sim_step - self.ramp_state += self.sim_step - if self.feedback_timer > self.feedback_update_time: - self.feedback_timer = 0 - # now implement the integral controller update - # find all the vehicles in an edge - q_update = self.feedback_coeff * ( - self.n_crit - np.average(self.smoothed_num)) - self.q = np.clip( - self.q + q_update, a_min=self.q_min, a_max=self.q_max) - # convert q to cycle time - self.cycle_time = 7200 / self.q - - # now apply the ramp meter - self.ramp_state %= self.cycle_time - # step through, if the value of tl_state is below self.green_time - # we should be green, otherwise we should be red - tl_mask = (self.ramp_state <= self.green_time) - colors = ['G' if val else 'r' for val in tl_mask] - self.k.traffic_light.set_state('3', ''.join(colors)) - - def apply_toll_bridge_control(self): - """Apply control to the toll bridge. - - Vehicles approaching the toll region slow down and stop lane changing. - - If 'disable_tb' is set to False, vehicles within TOLL_BOOTH_AREA of the - end of edge EDGE_BEFORE_TOLL are labelled as approaching the toll - booth. Their color changes and their lane changing is disabled. To - force them to slow down/mimic the effect of the toll booth, we sample - from a random normal distribution with mean - MEAN_NUM_SECONDS_WAIT_AT_TOLL and std-dev 1/self.sim_step to get how - long a vehicle should wait. We then turn on a red light for that many - seconds. - """ - cars_that_have_left = [] - for veh_id in self.cars_waiting_for_toll: - if self.k.vehicle.get_edge(veh_id) == EDGE_AFTER_TOLL: - lane = self.k.vehicle.get_lane(veh_id) - color = self.cars_waiting_for_toll[veh_id]["color"] - self.k.vehicle.set_color(veh_id, color) - if self.simulator == 'traci': - lane_change_mode = \ - self.cars_waiting_for_toll[veh_id]["lane_change_mode"] - self.k.kernel_api.vehicle.setLaneChangeMode( - veh_id, lane_change_mode) - if lane not in self.fast_track_lanes: - self.toll_wait_time[lane] = max( - 0, - np.random.normal( - MEAN_NUM_SECONDS_WAIT_AT_TOLL / self.sim_step, - 1 / self.sim_step)) - else: - self.toll_wait_time[lane] = max( - 0, - np.random.normal( - MEAN_NUM_SECONDS_WAIT_AT_FAST_TRACK / - self.sim_step, 1 / self.sim_step)) - - cars_that_have_left.append(veh_id) - - for veh_id in cars_that_have_left: - del self.cars_waiting_for_toll[veh_id] - - traffic_light_states = ["G"] * NUM_TOLL_LANES * self.scaling - - for lane in range(NUM_TOLL_LANES * self.scaling): - cars_in_lane = self.edge_dict[EDGE_BEFORE_TOLL][lane] - - for veh_id, pos in cars_in_lane: - if pos > TOLL_BOOTH_AREA: - if veh_id not in self.cars_waiting_for_toll: - # Disable lane changes inside Toll Area - if self.simulator == 'traci': - lane_change_mode = self.k.kernel_api.vehicle.\ - getLaneChangeMode(veh_id) - self.k.kernel_api.vehicle.setLaneChangeMode( - veh_id, 512) - else: - lane_change_mode = None - color = self.k.vehicle.get_color(veh_id) - self.k.vehicle.set_color(veh_id, (255, 0, 255)) - self.cars_waiting_for_toll[veh_id] = \ - {'lane_change_mode': lane_change_mode, - 'color': color} - else: - if pos > 50: - if self.toll_wait_time[lane] < 0: - traffic_light_states[lane] = "G" - else: - traffic_light_states[lane] = "r" - self.toll_wait_time[lane] -= 1 - - new_tl_state = "".join(traffic_light_states) - - if new_tl_state != self.tl_state: - self.tl_state = new_tl_state - self.k.traffic_light.set_state( - node_id=TB_TL_ID, state=new_tl_state) - - def get_bottleneck_density(self, lanes=None): - """Return the density of specified lanes. - - If no lanes are specified, this function calculates the - density of all vehicles on all lanes of the bottleneck edges. - """ - bottleneck_ids = self.k.vehicle.get_ids_by_edge(['3', '4']) - if lanes: - veh_ids = [ - veh_id for veh_id in bottleneck_ids - if str(self.k.vehicle.get_edge(veh_id)) + "_" + - str(self.k.vehicle.get_lane(veh_id)) in lanes - ] - else: - veh_ids = self.k.vehicle.get_ids_by_edge(['3', '4']) - return len(veh_ids) / BOTTLE_NECK_LEN - - # Dummy action and observation spaces - @property - def action_space(self): - """See class definition.""" - return Box( - low=-float("inf"), - high=float("inf"), - shape=(1, ), - dtype=np.float32) - - @property - def observation_space(self): - """See class definition.""" - return Box( - low=-float("inf"), - high=float("inf"), - shape=(1, ), - dtype=np.float32) - - def compute_reward(self, rl_actions, **kwargs): - """Outflow rate over last ten seconds normalized to max of 1.""" - reward = self.k.vehicle.get_outflow_rate(10 * self.sim_step) / \ - (2000.0 * self.scaling) - return reward - - def get_state(self): - """See class definition.""" - return np.asarray([1]) - - -class BottleNeckAccelEnv(BottleneckEnv): - """BottleNeckAccelEnv. - - Environment used to train vehicles to effectively pass through a - bottleneck. - - States - An observation is the edge position, speed, lane, and edge number of - the AV, the distance to and velocity of the vehicles - in front and behind the AV for all lanes. Additionally, we pass the - density and average velocity of all edges. Finally, we pad with - zeros in case an AV has exited the system. - Note: the vehicles are arranged in an initial order, so we pad - the missing vehicle at its normal position in the order - - Actions - The action space consist of a list in which the first half - is accelerations and the second half is a direction for lane - changing that we round - - Rewards - The reward is the two-norm of the difference between the speed of - all vehicles in the network and some desired speed. To this we add - a positive reward for moving the vehicles forward, and a penalty to - vehicles that lane changing too frequently. - - Termination - A rollout is terminated once the time horizon is reached. - """ - - def __init__(self, env_params, sim_params, scenario, simulator='traci'): - """Initialize BottleNeckAccelEnv.""" - for p in ADDITIONAL_RL_ENV_PARAMS.keys(): - if p not in env_params.additional_params: - raise KeyError( - 'Environment parameter "{}" not supplied'.format(p)) - - super().__init__(env_params, sim_params, scenario, simulator) - self.add_rl_if_exit = env_params.get_additional_param("add_rl_if_exit") - self.num_rl = deepcopy(self.initial_vehicles.num_rl_vehicles) - self.rl_id_list = deepcopy(self.initial_vehicles.get_rl_ids()) - self.max_speed = self.k.scenario.max_speed() - - @property - def observation_space(self): - """See class definition.""" - num_edges = len(self.k.scenario.get_edge_list()) - num_rl_veh = self.num_rl - num_obs = 2 * num_edges + 4 * MAX_LANES * self.scaling \ - * num_rl_veh + 4 * num_rl_veh - - return Box(low=0, high=1, shape=(num_obs, ), dtype=np.float32) - - def get_state(self): - """See class definition.""" - headway_scale = 1000 - - rl_ids = self.k.vehicle.get_rl_ids() - - # rl vehicle data (absolute position, speed, and lane index) - rl_obs = np.empty(0) - id_counter = 0 - for veh_id in rl_ids: - # check if we have skipped a vehicle, if not, pad - rl_id_num = self.rl_id_list.index(veh_id) - if rl_id_num != id_counter: - rl_obs = np.concatenate( - (rl_obs, np.zeros(4 * (rl_id_num - id_counter)))) - id_counter = rl_id_num + 1 - else: - id_counter += 1 - - # get the edge and convert it to a number - edge_num = self.k.vehicle.get_edge(veh_id) - if edge_num is None or edge_num == '' or edge_num[0] == ':': - edge_num = -1 - else: - edge_num = int(edge_num) / 6 - rl_obs = np.concatenate((rl_obs, [ - self.k.vehicle.get_x_by_id(veh_id) / 1000, - (self.k.vehicle.get_speed(veh_id) / self.max_speed), - (self.k.vehicle.get_lane(veh_id) / MAX_LANES), edge_num - ])) - - # if all the missing vehicles are at the end, pad - diff = self.num_rl - int(rl_obs.shape[0] / 4) - if diff > 0: - rl_obs = np.concatenate((rl_obs, np.zeros(4 * diff))) - - # relative vehicles data (lane headways, tailways, vel_ahead, and - # vel_behind) - relative_obs = np.empty(0) - id_counter = 0 - for veh_id in rl_ids: - # check if we have skipped a vehicle, if not, pad - rl_id_num = self.rl_id_list.index(veh_id) - if rl_id_num != id_counter: - pad_mat = np.zeros( - 4 * MAX_LANES * self.scaling * (rl_id_num - id_counter)) - relative_obs = np.concatenate((relative_obs, pad_mat)) - id_counter = rl_id_num + 1 - else: - id_counter += 1 - num_lanes = MAX_LANES * self.scaling - headway = np.asarray([1000] * num_lanes) / headway_scale - tailway = np.asarray([1000] * num_lanes) / headway_scale - vel_in_front = np.asarray([0] * num_lanes) / self.max_speed - vel_behind = np.asarray([0] * num_lanes) / self.max_speed - - lane_leaders = self.k.vehicle.get_lane_leaders(veh_id) - lane_followers = self.k.vehicle.get_lane_followers(veh_id) - lane_headways = self.k.vehicle.get_lane_headways(veh_id) - lane_tailways = self.k.vehicle.get_lane_tailways(veh_id) - headway[0:len(lane_headways)] = ( - np.asarray(lane_headways) / headway_scale) - tailway[0:len(lane_tailways)] = ( - np.asarray(lane_tailways) / headway_scale) - for i, lane_leader in enumerate(lane_leaders): - if lane_leader != '': - vel_in_front[i] = ( - self.k.vehicle.get_speed(lane_leader) / self.max_speed) - for i, lane_follower in enumerate(lane_followers): - if lane_followers != '': - vel_behind[i] = (self.k.vehicle.get_speed(lane_follower) / - self.max_speed) - - relative_obs = np.concatenate((relative_obs, headway, tailway, - vel_in_front, vel_behind)) - - # if all the missing vehicles are at the end, pad - diff = self.num_rl - int(relative_obs.shape[0] / (4 * MAX_LANES)) - if diff > 0: - relative_obs = np.concatenate((relative_obs, - np.zeros(4 * MAX_LANES * diff))) - - # per edge data (average speed, density - edge_obs = [] - for edge in self.k.scenario.get_edge_list(): - veh_ids = self.k.vehicle.get_ids_by_edge(edge) - if len(veh_ids) > 0: - avg_speed = (sum(self.k.vehicle.get_speed(veh_ids)) / - len(veh_ids)) / self.max_speed - density = len(veh_ids) / self.k.scenario.edge_length(edge) - edge_obs += [avg_speed, density] - else: - edge_obs += [0, 0] - - return np.concatenate((rl_obs, relative_obs, edge_obs)) - - def compute_reward(self, rl_actions, **kwargs): - """See class definition.""" - num_rl = self.k.vehicle.num_rl_vehicles - lane_change_acts = np.abs(np.round(rl_actions[1::2])[:num_rl]) - return (rewards.desired_velocity(self) + rewards.rl_forward_progress( - self, gain=0.1) - rewards.boolean_action_penalty( - lane_change_acts, gain=1.0)) - - @property - def action_space(self): - """See class definition.""" - max_decel = self.env_params.additional_params["max_decel"] - max_accel = self.env_params.additional_params["max_accel"] - - lb = [-abs(max_decel), -1] * self.num_rl - ub = [max_accel, 1] * self.num_rl - - return Box(np.array(lb), np.array(ub), dtype=np.float32) - - def _apply_rl_actions(self, actions): - """ - See parent class. - - Takes a tuple and applies a lane change or acceleration. if a lane - change is applied, don't issue any commands - for the duration of the lane change and return negative rewards - for actions during that lane change. if a lane change isn't applied, - and sufficient time has passed, issue an acceleration like normal. - """ - num_rl = self.k.vehicle.num_rl_vehicles - acceleration = actions[::2][:num_rl] - direction = np.round(actions[1::2])[:num_rl] - - # re-arrange actions according to mapping in observation space - sorted_rl_ids = sorted(self.k.vehicle.get_rl_ids(), - key=self.k.vehicle.get_x_by_id) - - # represents vehicles that are allowed to change lanes - non_lane_changing_veh = [ - self.time_counter <= self.env_params.additional_params[ - 'lane_change_duration'] + self.k.vehicle.get_last_lc(veh_id) - for veh_id in sorted_rl_ids] - - # vehicle that are not allowed to change have their directions set to 0 - direction[non_lane_changing_veh] = \ - np.array([0] * sum(non_lane_changing_veh)) - - self.k.vehicle.apply_acceleration(sorted_rl_ids, acc=acceleration) - self.k.vehicle.apply_lane_change(sorted_rl_ids, direction=direction) - - def additional_command(self): - """Reintroduce any RL vehicle that may have exited in the last step. - - This is used to maintain a constant number of RL vehicle in the system - at all times, in order to comply with a fixed size observation and - action space. - """ - super().additional_command() - # if the number of rl vehicles has decreased introduce it back in - num_rl = self.k.vehicle.num_rl_vehicles - if num_rl != len(self.rl_id_list) and self.add_rl_if_exit: - # find the vehicles that have exited - diff_list = list( - set(self.rl_id_list).difference(self.k.vehicle.get_rl_ids())) - for rl_id in diff_list: - # distribute rl cars evenly over lanes - lane_num = self.rl_id_list.index(rl_id) % \ - MAX_LANES * self.scaling - # reintroduce it at the start of the network - try: - self.k.vehicle.add( - veh_id=rl_id, - edge='1', - type_id=str('rl'), - lane=str(lane_num), - pos="0", - speed="max") - except Exception: - pass - - -class DesiredVelocityEnv(BottleneckEnv): - """DesiredVelocityEnv. - - Environment used to train vehicles to effectively pass through a - bottleneck by specifying the velocity that RL vehicles should attempt to - travel in certain regions of space. - - States - An observation is the number of vehicles in each lane in each - segment - - Actions - The action space consist of a list in which each element - corresponds to the desired speed that RL vehicles should travel in - that region of space - - Rewards - The reward is the outflow of the bottleneck plus a reward - for RL vehicles making forward progress - """ - - def __init__(self, env_params, sim_params, scenario, simulator='traci'): - """Initialize DesiredVelocityEnv.""" - super().__init__(env_params, sim_params, scenario, simulator) - for p in ADDITIONAL_VSL_ENV_PARAMS.keys(): - if p not in env_params.additional_params: - raise KeyError( - 'Environment parameter "{}" not supplied'.format(p)) - - # default (edge, segment, controlled) status - add_env_params = self.env_params.additional_params - default = [(str(i), 1, True) for i in range(1, 6)] - super(DesiredVelocityEnv, self).__init__(env_params, sim_params, - scenario) - self.segments = add_env_params.get("controlled_segments", default) - - # number of segments for each edge - self.num_segments = [segment[1] for segment in self.segments] - - # whether an edge is controlled - self.is_controlled = [segment[2] for segment in self.segments] - - self.num_controlled_segments = [ - segment[1] for segment in self.segments if segment[2] - ] - - # sum of segments - self.total_segments = int( - np.sum([segment[1] for segment in self.segments])) - # sum of controlled segments - segment_list = [segment[1] for segment in self.segments if segment[2]] - self.total_controlled_segments = int(np.sum(segment_list)) - - # list of controlled edges for comparison - self.controlled_edges = [ - segment[0] for segment in self.segments if segment[2] - ] - - additional_params = env_params.additional_params - - # for convenience, construct the relevant positions defining - # segments within edges - # self.slices is a dictionary mapping - # edge (str) -> segment start location (list of int) - self.slices = {} - for edge, num_segments, _ in self.segments: - edge_length = self.k.scenario.edge_length(edge) - self.slices[edge] = np.linspace(0, edge_length, num_segments + 1) - - # get info for observed segments - self.obs_segments = additional_params.get("observed_segments", []) - - # number of segments for each edge - self.num_obs_segments = [segment[1] for segment in self.obs_segments] - - # for convenience, construct the relevant positions defining - # segments within edges - # self.slices is a dictionary mapping - # edge (str) -> segment start location (list of int) - self.obs_slices = {} - for edge, num_segments in self.obs_segments: - edge_length = self.k.scenario.edge_length(edge) - self.obs_slices[edge] = np.linspace(0, edge_length, - num_segments + 1) - - # self.symmetric is True if all lanes in a segment - # have same action, else False - self.symmetric = additional_params.get("symmetric") - - # action index tells us, given an edge and a lane,the offset into - # rl_actions that we should take. - self.action_index = [0] - for i, (edge, segment, controlled) in enumerate(self.segments[:-1]): - if self.symmetric: - self.action_index += [ - self.action_index[i] + segment * controlled - ] - else: - num_lanes = self.k.scenario.num_lanes(edge) - self.action_index += [ - self.action_index[i] + segment * controlled * num_lanes - ] - - self.action_index = {} - action_list = [0] - index = 0 - for (edge, num_segments, controlled) in self.segments: - if controlled: - if self.symmetric: - self.action_index[edge] = [action_list[index]] - action_list += [action_list[index] + controlled] - else: - num_lanes = self.k.scenario.num_lanes(edge) - self.action_index[edge] = [action_list[index]] - action_list += [ - action_list[index] + - num_segments * controlled * num_lanes - ] - index += 1 - - @property - def observation_space(self): - """See class definition.""" - num_obs = 0 - # density and velocity for rl and non-rl vehicles per segment - # Last element is the outflow - for segment in self.obs_segments: - num_obs += 4 * segment[1] * self.k.scenario.num_lanes(segment[0]) - num_obs += 1 - return Box(low=0.0, high=1.0, shape=(num_obs, ), dtype=np.float32) - - @property - def action_space(self): - """See class definition.""" - if self.symmetric: - action_size = self.total_controlled_segments - else: - action_size = 0.0 - for segment in self.segments: # iterate over segments - if segment[2]: # if controlled - num_lanes = self.k.scenario.num_lanes(segment[0]) - action_size += num_lanes * segment[1] - add_params = self.env_params.additional_params - max_accel = add_params.get("max_accel") - max_decel = add_params.get("max_decel") - return Box( - low=-max_decel*self.sim_step, high=max_accel*self.sim_step, - shape=(int(action_size), ), dtype=np.float32) - - def get_state(self): - """Return aggregate statistics of different segments of the bottleneck. - - The state space of the system is defined by splitting the bottleneck up - into edges and then segments in each edge. The class variable - self.num_obs_segments specifies how many segments each edge is cut up - into. Each lane defines a unique segment: we refer to this as a - lane-segment. For example, if edge 1 has four lanes and three segments, - then we have a total of 12 lane-segments. We will track the aggregate - statistics of the vehicles in each lane segment. - - For each lane-segment we return the: - - * Number of vehicles on that segment. - * Number of AVs (referred to here as rl_vehicles) in the segment. - * The average speed of the vehicles in that segment. - * The average speed of the rl vehicles in that segment. - - Finally, we also append the total outflow of the bottleneck over the - last 20 * self.sim_step seconds. - """ - num_vehicles_list = [] - num_rl_vehicles_list = [] - vehicle_speeds_list = [] - rl_speeds_list = [] - for i, edge in enumerate(EDGE_LIST): - num_lanes = self.k.scenario.num_lanes(edge) - num_vehicles = np.zeros((self.num_obs_segments[i], num_lanes)) - num_rl_vehicles = np.zeros((self.num_obs_segments[i], num_lanes)) - vehicle_speeds = np.zeros((self.num_obs_segments[i], num_lanes)) - rl_vehicle_speeds = np.zeros((self.num_obs_segments[i], num_lanes)) - ids = self.k.vehicle.get_ids_by_edge(edge) - lane_list = self.k.vehicle.get_lane(ids) - pos_list = self.k.vehicle.get_position(ids) - for i, id in enumerate(ids): - segment = np.searchsorted(self.obs_slices[edge], - pos_list[i]) - 1 - if id in self.k.vehicle.get_rl_ids(): - rl_vehicle_speeds[segment, lane_list[i]] \ - += self.k.vehicle.get_speed(id) - num_rl_vehicles[segment, lane_list[i]] += 1 - else: - vehicle_speeds[segment, lane_list[i]] \ - += self.k.vehicle.get_speed(id) - num_vehicles[segment, lane_list[i]] += 1 - - # normalize - - num_vehicles /= NUM_VEHICLE_NORM - num_rl_vehicles /= NUM_VEHICLE_NORM - num_vehicles_list += num_vehicles.flatten().tolist() - num_rl_vehicles_list += num_rl_vehicles.flatten().tolist() - vehicle_speeds_list += vehicle_speeds.flatten().tolist() - rl_speeds_list += rl_vehicle_speeds.flatten().tolist() - - unnorm_veh_list = np.asarray(num_vehicles_list) * NUM_VEHICLE_NORM - unnorm_rl_list = np.asarray(num_rl_vehicles_list) * NUM_VEHICLE_NORM - - # compute the mean speed if the speed isn't zero - num_rl = len(num_rl_vehicles_list) - num_veh = len(num_vehicles_list) - mean_speed = np.nan_to_num([ - vehicle_speeds_list[i] / unnorm_veh_list[i] - if int(unnorm_veh_list[i]) else 0 for i in range(num_veh) - ]) - mean_speed_norm = mean_speed / 50 - mean_rl_speed = np.nan_to_num([ - rl_speeds_list[i] / unnorm_rl_list[i] - if int(unnorm_rl_list[i]) else 0 for i in range(num_rl) - ]) / 50 - outflow = np.asarray( - self.k.vehicle.get_outflow_rate(20 * self.sim_step) / 2000.0) - return np.concatenate((num_vehicles_list, num_rl_vehicles_list, - mean_speed_norm, mean_rl_speed, [outflow])) - - def _apply_rl_actions(self, rl_actions): - """ - RL actions are split up into 3 levels. - - * First, they're split into edge actions. - * Then they're split into segment actions. - * Then they're split into lane actions. - """ - for rl_id in self.k.vehicle.get_rl_ids(): - edge = self.k.vehicle.get_edge(rl_id) - lane = self.k.vehicle.get_lane(rl_id) - if edge: - # If in outer lanes, on a controlled edge, in a controlled lane - if edge[0] != ':' and edge in self.controlled_edges: - pos = self.k.vehicle.get_position(rl_id) - - if not self.symmetric: - num_lanes = self.k.scenario.num_lanes(edge) - # find what segment we fall into - bucket = np.searchsorted(self.slices[edge], pos) - 1 - action = rl_actions[int(lane) + bucket * num_lanes + - self.action_index[edge]] - else: - # find what segment we fall into - bucket = np.searchsorted(self.slices[edge], pos) - 1 - action = rl_actions[bucket + self.action_index[edge]] - - max_speed_curr = self.k.vehicle.get_max_speed(rl_id) - next_max = np.clip(max_speed_curr + action, 0.01, 23.0) - self.k.vehicle.set_max_speed(rl_id, next_max) - - else: - # set the desired velocity of the controller to the default - self.k.vehicle.set_max_speed(rl_id, 23.0) - - def compute_reward(self, rl_actions, **kwargs): - """Outflow rate over last ten seconds normalized to max of 1.""" - if self.env_params.evaluate: - if self.time_counter == self.env_params.horizon: - reward = self.k.vehicle.get_outflow_rate(500) - else: - return 0 - else: - reward = self.k.vehicle.get_outflow_rate(10 * self.sim_step) / \ - (2000.0 * self.scaling) - return reward - - def reset(self): - """Reset the environment with a new inflow rate. - - The diverse set of inflows are used to generate a policy that is more - robust with respect to the inflow rate. The inflow rate is update by - creating a new scenario similar to the previous one, but with a new - Inflow object with a rate within the additional environment parameter - "inflow_range", which is a list consisting of the smallest and largest - allowable inflow rates. - - **WARNING**: The inflows assume there are vehicles of type - "followerstopper" and "human" within the VehicleParams object. - """ - add_params = self.env_params.additional_params - if add_params.get("reset_inflow"): - inflow_range = add_params.get("inflow_range") - flow_rate = np.random.uniform( - min(inflow_range), max(inflow_range)) * self.scaling - - # We try this for 100 trials in case unexpected errors during - # instantiation. - for _ in range(100): - try: - # introduce new inflows within the pre-defined inflow range - inflow = InFlows() - inflow.add( - veh_type="followerstopper", # FIXME: make generic - edge="1", - vehs_per_hour=flow_rate * .1, - departLane="random", - departSpeed=10) - inflow.add( - veh_type="human", - edge="1", - vehs_per_hour=flow_rate * .9, - departLane="random", - departSpeed=10) - - # all other network parameters should match the previous - # environment (we only want to change the inflow) - additional_net_params = { - "scaling": self.scaling, - "speed_limit": self.net_params. - additional_params['speed_limit'] - } - net_params = NetParams( - inflows=inflow, - additional_params=additional_net_params) - vehicles = VehicleParams() - vehicles.add( - veh_id="human", # FIXME: make generic - car_following_params=SumoCarFollowingParams( - speed_mode=9, - ), - lane_change_controller=(SimLaneChangeController, {}), - routing_controller=(ContinuousRouter, {}), - lane_change_params=SumoLaneChangeParams( - lane_change_mode=0, # 1621,#0b100000101, - ), - num_vehicles=1 * self.scaling) - vehicles.add( - veh_id="followerstopper", - acceleration_controller=(RLController, {}), - lane_change_controller=(SimLaneChangeController, {}), - routing_controller=(ContinuousRouter, {}), - car_following_params=SumoCarFollowingParams( - speed_mode=9, - ), - lane_change_params=SumoLaneChangeParams( - lane_change_mode=0, - ), - num_vehicles=1 * self.scaling) +@deprecated('flow.envs.bottleneck_env', + 'flow.envs.bottleneck.BottleneckEnv') +class BottleneckEnv(BEnv): + """See parent class.""" - # recreate the scenario object - self.scenario = self.scenario.__class__( - name=self.scenario.orig_name, - vehicles=vehicles, - net_params=net_params, - initial_config=self.initial_config, - traffic_lights=self.scenario.traffic_lights) - observation = super().reset() + pass - # reset the timer to zero - self.time_counter = 0 - return observation +@deprecated('flow.envs.bottleneck_env', + 'flow.envs.bottleneck.BottleneckAccelEnv') +class BottleNeckAccelEnv(BAEnv): + """See parent class.""" - except Exception as e: - print('error on reset ', e) + pass - # perform the generic reset function - observation = super().reset() - # reset the timer to zero - self.time_counter = 0 +@deprecated('flow.envs.bottleneck_env', + 'flow.envs.bottleneck.BottleneckDesiredVelocityEnv') +class DesiredVelocityEnv(BDVEnv): + """See parent class.""" - return observation + pass diff --git a/flow/envs/green_wave_env.py b/flow/envs/green_wave_env.py index 0c0aee97c1..6b34e562e6 100644 --- a/flow/envs/green_wave_env.py +++ b/flow/envs/green_wave_env.py @@ -1,740 +1,32 @@ -"""Environments for scenarios with traffic lights. +"""Pending deprecation file. -These environments are used to train traffic lights to regulate traffic flow -through an n x m grid. +To view the actual content, go to: flow/envs/traffic_light_grid.py """ +from flow.utils.flow_warnings import deprecated +from flow.envs.traffic_light_grid import TrafficLightGridEnv as TLGEnv +from flow.envs.traffic_light_grid import TrafficLightGridPOEnv as TLGPOEnv +from flow.envs.traffic_light_grid import TrafficLightGridTestEnv as TLGTEnv -import numpy as np -import re -from gym.spaces.box import Box -from gym.spaces.discrete import Discrete -from gym.spaces import Tuple +@deprecated('flow.envs.green_wave_env', + 'flow.envs.traffic_light_grid.TrafficLightGridEnv') +class TrafficLightGridEnv(TLGEnv): + """See parent class.""" -from flow.core import rewards -from flow.envs.base_env import Env + pass -ADDITIONAL_ENV_PARAMS = { - # minimum switch time for each traffic light (in seconds) - "switch_time": 2.0, - # whether the traffic lights should be actuated by sumo or RL - # options are "controlled" and "actuated" - "tl_type": "controlled", - # determines whether the action space is meant to be discrete or continuous - "discrete": False, -} -ADDITIONAL_PO_ENV_PARAMS = { - # num of vehicles the agent can observe on each incoming edge - "num_observed": 2, - # velocity to use in reward functions - "target_velocity": 30, -} +@deprecated('flow.envs.green_wave_env', + 'flow.envs.traffic_light_grid.TrafficLightGridPOEnv') +class PO_TrafficLightGridEnv(TLGPOEnv): + """See parent class.""" + pass -class TrafficLightGridEnv(Env): - """Environment used to train traffic lights. - Required from env_params: +@deprecated('flow.envs.green_wave_env', + 'flow.envs.traffic_light_grid.TrafficLightGridTestEnv') +class GreenWaveTestEnv(TLGTEnv): + """See parent class.""" - * switch_time: minimum time a light must be constant before - it switches (in seconds). - Earlier RL commands are ignored. - * tl_type: whether the traffic lights should be actuated by sumo or RL, - options are respectively "actuated" and "controlled" - * discrete: determines whether the action space is meant to be discrete or - continuous - - States - An observation is the distance of each vehicle to its intersection, a - number uniquely identifying which edge the vehicle is on, and the speed - of the vehicle. - - Actions - The action space consist of a list of float variables ranging from 0-1 - specifying whether a traffic light is supposed to switch or not. The - actions are sent to the traffic light in the grid from left to right - and then top to bottom. - - Rewards - The reward is the negative per vehicle delay minus a penalty for - switching traffic lights - - Termination - A rollout is terminated once the time horizon is reached. - - Additional - Vehicles are rerouted to the start of their original routes once they - reach the end of the network in order to ensure a constant number of - vehicles. - - Attributes - ---------- - grid_array : dict - Array containing information on the grid, such as the length of roads, - row_num, col_num, number of initial cars - rows : int - Number of rows in this grid scenario - cols : int - Number of columns in this grid scenario - num_traffic_lights : int - Number of intersection in this grid scenario - tl_type : str - Type of traffic lights, either 'actuated' or 'static' - steps : int - Horizon of this experiment, see EnvParams.horion - obs_var_labels : dict - Referenced in the visualizer. Tells the visualizer which - metrics to track - node_mapping : dict - Dictionary mapping intersections / nodes (nomenclature is used - interchangeably here) to the edges that are leading to said - intersection / node - last_change : np array [num_traffic_lights]x1 np array - Multi-dimensional array keeping track, in timesteps, of how much time - has passed since the last change to yellow for each traffic light - direction : np array [num_traffic_lights]x1 np array - Multi-dimensional array keeping track of which direction in traffic - light is flowing. 0 indicates flow from top to bottom, and - 1 indicates flow from left to right - currently_yellow : np array [num_traffic_lights]x1 np array - Multi-dimensional array keeping track of whether or not each traffic - light is currently yellow. 1 if yellow, 0 if not - min_switch_time : np array [num_traffic_lights]x1 np array - The minimum time in timesteps that a light can be yellow. Serves - as a lower bound - discrete : bool - Indicates whether or not the action space is discrete. See below for - more information: - https://github.com/openai/gym/blob/master/gym/spaces/discrete.py - """ - - def __init__(self, env_params, sim_params, scenario, simulator='traci'): - - for p in ADDITIONAL_ENV_PARAMS.keys(): - if p not in env_params.additional_params: - raise KeyError( - 'Environment parameter "{}" not supplied'.format(p)) - - self.grid_array = scenario.net_params.additional_params["grid_array"] - self.rows = self.grid_array["row_num"] - self.cols = self.grid_array["col_num"] - # self.num_observed = self.grid_array.get("num_observed", 3) - self.num_traffic_lights = self.rows * self.cols - self.tl_type = env_params.additional_params.get('tl_type') - - super().__init__(env_params, sim_params, scenario, simulator) - - # Saving env variables for plotting - self.steps = env_params.horizon - self.obs_var_labels = { - 'edges': np.zeros((self.steps, self.k.vehicle.num_vehicles)), - 'velocities': np.zeros((self.steps, self.k.vehicle.num_vehicles)), - 'positions': np.zeros((self.steps, self.k.vehicle.num_vehicles)) - } - - # Keeps track of the last time the traffic lights in an intersection - # were allowed to change (the last time the lights were allowed to - # change from a red-green state to a red-yellow state.) - self.last_change = np.zeros((self.rows * self.cols, 1)) - # Keeps track of the direction of the intersection (the direction that - # is currently being allowed to flow. 0 indicates flow from top to - # bottom, and 1 indicates flow from left to right.) - self.direction = np.zeros((self.rows * self.cols, 1)) - # Value of 1 indicates that the intersection is in a red-yellow state. - # value 0 indicates that the intersection is in a red-green state. - self.currently_yellow = np.zeros((self.rows * self.cols, 1)) - - # when this hits min_switch_time we change from yellow to red - # the second column indicates the direction that is currently being - # allowed to flow. 0 is flowing top to bottom, 1 is left to right - # For third column, 0 signifies yellow and 1 green or red - self.min_switch_time = env_params.additional_params["switch_time"] - - if self.tl_type != "actuated": - for i in range(self.rows * self.cols): - self.k.traffic_light.set_state( - node_id='center' + str(i), state="GrGr") - self.currently_yellow[i] = 0 - - # # Additional Information for Plotting - # self.edge_mapping = {"top": [], "bot": [], "right": [], "left": []} - # for i, veh_id in enumerate(self.k.vehicle.get_ids()): - # edge = self.k.vehicle.get_edge(veh_id) - # for key in self.edge_mapping: - # if key in edge: - # self.edge_mapping[key].append(i) - # break - - # check whether the action space is meant to be discrete or continuous - self.discrete = env_params.additional_params.get("discrete", False) - - @property - def action_space(self): - """See class definition.""" - if self.discrete: - return Discrete(2 ** self.num_traffic_lights) - else: - return Box( - low=-1, - high=1, - shape=(self.num_traffic_lights,), - dtype=np.float32) - - @property - def observation_space(self): - """See class definition.""" - speed = Box( - low=0, - high=1, - shape=(self.initial_vehicles.num_vehicles,), - dtype=np.float32) - dist_to_intersec = Box( - low=0., - high=np.inf, - shape=(self.initial_vehicles.num_vehicles,), - dtype=np.float32) - edge_num = Box( - low=0., - high=1, - shape=(self.initial_vehicles.num_vehicles,), - dtype=np.float32) - traffic_lights = Box( - low=0., - high=1, - shape=(3 * self.rows * self.cols,), - dtype=np.float32) - return Tuple((speed, dist_to_intersec, edge_num, traffic_lights)) - - def get_state(self): - """See class definition.""" - # compute the normalizers - grid_array = self.net_params.additional_params["grid_array"] - max_dist = max(grid_array["short_length"], - grid_array["long_length"], - grid_array["inner_length"]) - - # get the state arrays - speeds = [ - self.k.vehicle.get_speed(veh_id) / self.k.scenario.max_speed() - for veh_id in self.k.vehicle.get_ids() - ] - dist_to_intersec = [ - self.get_distance_to_intersection(veh_id) / max_dist - for veh_id in self.k.vehicle.get_ids() - ] - edges = [ - self._convert_edge(self.k.vehicle.get_edge(veh_id)) / - (self.k.scenario.network.num_edges - 1) - for veh_id in self.k.vehicle.get_ids() - ] - - state = [ - speeds, dist_to_intersec, edges, - self.last_change.flatten().tolist(), - self.direction.flatten().tolist(), - self.currently_yellow.flatten().tolist() - ] - return np.array(state) - - def _apply_rl_actions(self, rl_actions): - """See class definition.""" - # check if the action space is discrete - if self.discrete: - # convert single value to list of 0's and 1's - rl_mask = [int(x) for x in list('{0:0b}'.format(rl_actions))] - rl_mask = [0] * (self.num_traffic_lights - len(rl_mask)) + rl_mask - else: - # convert values less than 0 to zero and above 0 to 1. 0 indicates - # that should not switch the direction, and 1 indicates that switch - # should happen - rl_mask = rl_actions > 0.0 - - for i, action in enumerate(rl_mask): - if self.currently_yellow[i] == 1: # currently yellow - self.last_change[i] += self.sim_step - # Check if our timer has exceeded the yellow phase, meaning it - # should switch to red - if self.last_change[i] >= self.min_switch_time: - if self.direction[i] == 0: - self.k.traffic_light.set_state( - node_id='center{}'.format(i), - state="GrGr") - else: - self.k.traffic_light.set_state( - node_id='center{}'.format(i), - state='rGrG') - self.currently_yellow[i] = 0 - else: - if action: - if self.direction[i] == 0: - self.k.traffic_light.set_state( - node_id='center{}'.format(i), - state='yryr') - else: - self.k.traffic_light.set_state( - node_id='center{}'.format(i), - state='ryry') - self.last_change[i] = 0.0 - self.direction[i] = not self.direction[i] - self.currently_yellow[i] = 1 - - def compute_reward(self, rl_actions, **kwargs): - """See class definition.""" - return - rewards.min_delay_unscaled(self) \ - - rewards.boolean_action_penalty(rl_actions >= 0.5, gain=1.0) - - # =============================== - # ============ UTILS ============ - # =============================== - - def get_distance_to_intersection(self, veh_ids): - """Determine the distance from a vehicle to its next intersection. - - Parameters - ---------- - veh_ids : str or str list - vehicle(s) identifier(s) - - Returns - ------- - float (or float list) - distance to closest intersection - """ - if isinstance(veh_ids, list): - return [self.get_distance_to_intersection(veh_id) - for veh_id in veh_ids] - return self.find_intersection_dist(veh_ids) - - def find_intersection_dist(self, veh_id): - """Return distance from intersection. - - Return the distance from the vehicle's current position to the position - of the node it is heading toward. - """ - edge_id = self.k.vehicle.get_edge(veh_id) - # FIXME this might not be the best way of handling this - if edge_id == "": - return -10 - if 'center' in edge_id: - return 0 - edge_len = self.k.scenario.edge_length(edge_id) - relative_pos = self.k.vehicle.get_position(veh_id) - dist = edge_len - relative_pos - return dist - - def _convert_edge(self, edges): - """Convert the string edge to a number. - - Start at the bottom left vertical edge and going right and then up, so - the bottom left vertical edge is zero, the right edge beside it is 1. - - The numbers are assigned along the lowest column, then the lowest row, - then the second lowest column, etc. Left goes before right, top goes - before bottom. - - The values are zero indexed. - - Parameters - ---------- - edges : list of str or str - name of the edge(s) - - Returns - ------- - list of int or int - a number uniquely identifying each edge - """ - if isinstance(edges, list): - return [self._split_edge(edge) for edge in edges] - else: - return self._split_edge(edges) - - def _split_edge(self, edge): - """Act as utility function for convert_edge.""" - if edge: - if edge[0] == ":": # center - center_index = int(edge.split("center")[1][0]) - base = ((self.cols + 1) * self.rows * 2) \ - + ((self.rows + 1) * self.cols * 2) - return base + center_index + 1 - else: - pattern = re.compile(r"[a-zA-Z]+") - edge_type = pattern.match(edge).group() - edge = edge.split(edge_type)[1].split('_') - row_index, col_index = [int(x) for x in edge] - if edge_type in ['bot', 'top']: - rows_below = 2 * (self.cols + 1) * row_index - cols_below = 2 * (self.cols * (row_index + 1)) - edge_num = rows_below + cols_below + 2 * col_index + 1 - return edge_num if edge_type == 'bot' else edge_num + 1 - if edge_type in ['left', 'right']: - rows_below = 2 * (self.cols + 1) * row_index - cols_below = 2 * (self.cols * row_index) - edge_num = rows_below + cols_below + 2 * col_index + 1 - return edge_num if edge_type == 'left' else edge_num + 1 - else: - return 0 - - def _get_relative_node(self, agent_id, direction): - """Yield node number of traffic light agent in a given direction. - - For example, the nodes in a grid with 2 rows and 3 columns are - indexed as follows: - - | | | - --- 3 --- 4 --- 5 --- - | | | - --- 0 --- 1 --- 2 --- - | | | - - See flow.scenarios.grid for more information. - - Example of function usage: - - Seeking the "top" direction to ":center0" would return 3. - - Seeking the "bottom" direction to ":center0" would return -1. - - :param agent_id: agent id of the form ":center#" - :param direction: top, bottom, left, right - :return: node number - """ - ID_IDX = 1 - agent_id_num = int(agent_id.split("center")[ID_IDX]) - if direction == "top": - node = agent_id_num + self.cols - if node >= self.cols * self.rows: - node = -1 - elif direction == "bottom": - node = agent_id_num - self.cols - if node < 0: - node = -1 - elif direction == "left": - if agent_id_num % self.cols == 0: - node = -1 - else: - node = agent_id_num - 1 - elif direction == "right": - if agent_id_num % self.cols == self.cols - 1: - node = -1 - else: - node = agent_id_num + 1 - else: - raise NotImplementedError - - return node - - def additional_command(self): - """See parent class. - - Used to insert vehicles that are on the exit edge and place them - back on their entrance edge. - """ - for veh_id in self.k.vehicle.get_ids(): - self._reroute_if_final_edge(veh_id) - - def _reroute_if_final_edge(self, veh_id): - """Reroute vehicle associated with veh_id. - - Checks if an edge is the final edge. If it is return the route it - should start off at. - """ - edge = self.k.vehicle.get_edge(veh_id) - if edge == "": - return - if edge[0] == ":": # center edge - return - pattern = re.compile(r"[a-zA-Z]+") - edge_type = pattern.match(edge).group() - edge = edge.split(edge_type)[1].split('_') - row_index, col_index = [int(x) for x in edge] - - # find the route that we're going to place the vehicle on if we are - # going to remove it - route_id = None - if edge_type == 'bot' and col_index == self.cols: - route_id = "bot{}_0".format(row_index) - elif edge_type == 'top' and col_index == 0: - route_id = "top{}_{}".format(row_index, self.cols) - elif edge_type == 'left' and row_index == 0: - route_id = "left{}_{}".format(self.rows, col_index) - elif edge_type == 'right' and row_index == self.rows: - route_id = "right0_{}".format(col_index) - - if route_id is not None: - type_id = self.k.vehicle.get_type(veh_id) - lane_index = self.k.vehicle.get_lane(veh_id) - # remove the vehicle - self.k.vehicle.remove(veh_id) - # reintroduce it at the start of the network - self.k.vehicle.add( - veh_id=veh_id, - edge=route_id, - type_id=str(type_id), - lane=str(lane_index), - pos="0", - speed="max") - - def get_closest_to_intersection(self, edges, num_closest, padding=False): - """Return the IDs of the vehicles that are closest to an intersection. - - For each edge in edges, return the IDs (veh_id) of the num_closest - vehicles in edge that are closest to an intersection (the intersection - they are heading towards). - - This function performs no check on whether or not edges are going - towards an intersection or not, it just gets the vehicles that are - closest to the end of their edges. - - If there are less than num_closest vehicles on an edge, the function - performs padding by adding empty strings "" instead of vehicle ids if - the padding parameter is set to True. - - Parameters - ---------- - edges : str | str list - ID of an edge or list of edge IDs. - num_closest : int (> 0) - Number of vehicles to consider on each edge. - padding : bool (default False) - If there are less than num_closest vehicles on an edge, perform - padding by adding empty strings "" instead of vehicle ids if the - padding parameter is set to True (note: leaving padding to False - while passing a list of several edges as parameter can lead to - information loss since you will not know which edge, if any, - contains less than num_closest vehicles). - - Usage - ----- - For example, consider the following network, composed of 4 edges - whose ids are "edge0", "edge1", "edge2" and "edge3", the numbers - being vehicles all headed towards intersection x. The ID of the vehicle - with number n is "veh{n}" (edge "veh0", "veh1"...). - - edge1 - | | - | 7 | - | 8 | - -------------| |------------- - edge0 1 2 3 4 5 6 x edge2 - -------------| |------------- - | 9 | - | 10| - | 11| - edge3 - - And consider the following example calls on the previous network: - - >>> get_closest_to_intersection("edge0", 4) - ["veh6", "veh5", "veh4", "veh3"] - - >>> get_closest_to_intersection("edge0", 8) - ["veh6", "veh5", "veh4", "veh3", "veh2", "veh1"] - - >>> get_closest_to_intersection("edge0", 8, padding=True) - ["veh6", "veh5", "veh4", "veh3", "veh2", "veh1", "", ""] - - >>> get_closest_to_intersection(["edge0", "edge1", "edge2", "edge3"], - 3, padding=True) - ["veh6", "veh5", "veh4", "veh8", "veh7", "", "", "", "", "veh9", - "veh10", "veh11"] - - Returns - ------- - str list - If n is the number of edges given as parameters, then the returned - list contains n * num_closest vehicle IDs. - - Raises - ------ - ValueError - if num_closest <= 0 - """ - if num_closest <= 0: - raise ValueError("Function get_closest_to_intersection called with" - "parameter num_closest={}, but num_closest should" - "be positive".format(num_closest)) - - if isinstance(edges, list): - ids = [self.get_closest_to_intersection(edge, num_closest) - for edge in edges] - # flatten the list and return it - return [veh_id for sublist in ids for veh_id in sublist] - - # get the ids of all the vehicles on the edge 'edges' ordered by - # increasing distance to end of edge (intersection) - veh_ids_ordered = sorted(self.k.vehicle.get_ids_by_edge(edges), - key=self.get_distance_to_intersection) - - # return the ids of the num_closest vehicles closest to the - # intersection, potentially with ""-padding. - pad_lst = [""] * (num_closest - len(veh_ids_ordered)) - return veh_ids_ordered[:num_closest] + (pad_lst if padding else []) - - -class PO_TrafficLightGridEnv(TrafficLightGridEnv): - """Environment used to train traffic lights. - - Required from env_params: - - * switch_time: minimum switch time for each traffic light (in seconds). - Earlier RL commands are ignored. - * num_observed: number of vehicles nearest each intersection that is - observed in the state space; defaults to 2 - - States - An observation is the number of observed vehicles in each intersection - closest to the traffic lights, a number uniquely identifying which - edge the vehicle is on, and the speed of the vehicle. - - Actions - The action space consist of a list of float variables ranging from 0-1 - specifying whether a traffic light is supposed to switch or not. The - actions are sent to the traffic light in the grid from left to right - and then top to bottom. - - Rewards - The reward is the delay of each vehicle minus a penalty for switching - traffic lights - - Termination - A rollout is terminated once the time horizon is reached. - - Additional - Vehicles are rerouted to the start of their original routes once they - reach the end of the network in order to ensure a constant number of - vehicles. - - """ - - def __init__(self, env_params, sim_params, scenario, simulator='traci'): - super().__init__(env_params, sim_params, scenario, simulator) - - for p in ADDITIONAL_PO_ENV_PARAMS.keys(): - if p not in env_params.additional_params: - raise KeyError( - 'Environment parameter "{}" not supplied'.format(p)) - - # number of vehicles nearest each intersection that is observed in the - # state space; defaults to 2 - self.num_observed = env_params.additional_params.get("num_observed", 2) - - # used during visualization - self.observed_ids = [] - - @property - def observation_space(self): - """State space that is partially observed. - - Velocities, distance to intersections, edge number (for nearby - vehicles) from each direction, edge information, and traffic light - state. - """ - tl_box = Box( - low=0., - high=1, - shape=(3 * 4 * self.num_observed * self.num_traffic_lights + - 2 * len(self.k.scenario.get_edge_list()) + - 3 * self.num_traffic_lights,), - dtype=np.float32) - return tl_box - - def get_state(self): - """See parent class. - - Returns self.num_observed number of vehicles closest to each traffic - light and for each vehicle its velocity, distance to intersection, - edge_number traffic light state. This is partially observed - """ - speeds = [] - dist_to_intersec = [] - edge_number = [] - max_speed = max( - self.k.scenario.speed_limit(edge) - for edge in self.k.scenario.get_edge_list()) - grid_array = self.net_params.additional_params["grid_array"] - max_dist = max(grid_array["short_length"], grid_array["long_length"], - grid_array["inner_length"]) - all_observed_ids = [] - - for _, edges in self.scenario.node_mapping: - for edge in edges: - observed_ids = \ - self.get_closest_to_intersection(edge, self.num_observed) - all_observed_ids += observed_ids - - # check which edges we have so we can always pad in the right - # positions - speeds += [ - self.k.vehicle.get_speed(veh_id) / max_speed - for veh_id in observed_ids - ] - dist_to_intersec += [ - (self.k.scenario.edge_length( - self.k.vehicle.get_edge(veh_id)) - - self.k.vehicle.get_position(veh_id)) / max_dist - for veh_id in observed_ids - ] - edge_number += \ - [self._convert_edge(self.k.vehicle.get_edge(veh_id)) / - (self.k.scenario.network.num_edges - 1) - for veh_id in observed_ids] - - if len(observed_ids) < self.num_observed: - diff = self.num_observed - len(observed_ids) - speeds += [0] * diff - dist_to_intersec += [0] * diff - edge_number += [0] * diff - - # now add in the density and average velocity on the edges - density = [] - velocity_avg = [] - for edge in self.k.scenario.get_edge_list(): - ids = self.k.vehicle.get_ids_by_edge(edge) - if len(ids) > 0: - vehicle_length = 5 - density += [vehicle_length * len(ids) / - self.k.scenario.edge_length(edge)] - velocity_avg += [np.mean( - [self.k.vehicle.get_speed(veh_id) for veh_id in - ids]) / max_speed] - else: - density += [0] - velocity_avg += [0] - self.observed_ids = all_observed_ids - return np.array( - np.concatenate([ - speeds, dist_to_intersec, edge_number, density, velocity_avg, - self.last_change.flatten().tolist(), - self.direction.flatten().tolist(), - self.currently_yellow.flatten().tolist() - ])) - - def compute_reward(self, rl_actions, **kwargs): - """See class definition.""" - if self.env_params.evaluate: - return - rewards.min_delay_unscaled(self) - else: - return (- rewards.min_delay_unscaled(self) + - rewards.penalize_standstill(self, gain=0.2)) - - def additional_command(self): - """See class definition.""" - # specify observed vehicles - [self.k.vehicle.set_observed(veh_id) for veh_id in self.observed_ids] - - -class GreenWaveTestEnv(TrafficLightGridEnv): - """ - Class for use in testing. - - This class overrides RL methods of green wave so we can test - construction without needing to specify RL methods - """ - - def _apply_rl_actions(self, rl_actions): - """See class definition.""" - pass - - def compute_reward(self, rl_actions, **kwargs): - """No return, for testing purposes.""" - return 0 + pass diff --git a/flow/envs/loop/__init__.py b/flow/envs/loop/__init__.py deleted file mode 100644 index 0aa374f9b8..0000000000 --- a/flow/envs/loop/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Empty init file to ensure documentation for the loop env is created.""" diff --git a/flow/envs/loop/lane_changing.py b/flow/envs/loop/lane_changing.py old mode 100755 new mode 100644 index ae463937c2..76f05e7bce --- a/flow/envs/loop/lane_changing.py +++ b/flow/envs/loop/lane_changing.py @@ -1,269 +1,23 @@ -"""Environments that can train both lane change and acceleration behaviors.""" +"""Pending deprecation file. -from flow.envs.loop.loop_accel import AccelEnv -from flow.core import rewards +To view the actual content, go to: flow/envs/ring/lane_change_accel.py +""" +from flow.utils.flow_warnings import deprecated +from flow.envs.ring.lane_change_accel import LaneChangeAccelEnv as LCEnv +from flow.envs.ring.lane_change_accel import LaneChangeAccelPOEnv as LCPOEnv -from gym.spaces.box import Box -import numpy as np -ADDITIONAL_ENV_PARAMS = { - # maximum acceleration for autonomous vehicles, in m/s^2 - "max_accel": 3, - # maximum deceleration for autonomous vehicles, in m/s^2 - "max_decel": 3, - # lane change duration for autonomous vehicles, in s. Autonomous vehicles - # reject new lane changing commands for this duration after successfully - # changing lanes. - "lane_change_duration": 5, - # desired velocity for all vehicles in the network, in m/s - "target_velocity": 10, - # specifies whether vehicles are to be sorted by position during a - # simulation step. If set to True, the environment parameter - # self.sorted_ids will return a list of all vehicles sorted in accordance - # with the environment - 'sort_vehicles': False -} +@deprecated('flow.envs.loop.lane_changing', + 'flow.envs.ring.lane_change_accel.LaneChangeAccelEnv') +class LaneChangeAccelEnv(LCEnv): + """See parent class.""" + pass -class LaneChangeAccelEnv(AccelEnv): - """Fully observable lane change and acceleration environment. - This environment is used to train autonomous vehicles to improve traffic - flows when lane-change and acceleration actions are permitted by the rl - agent. +@deprecated('flow.envs.loop.lane_changing', + 'flow.envs.ring.lane_change_accel.LaneChangeAccelPOEnv') +class LaneChangeAccelPOEnv(LCPOEnv): + """See parent class.""" - Required from env_params: - - * max_accel: maximum acceleration for autonomous vehicles, in m/s^2 - * max_decel: maximum deceleration for autonomous vehicles, in m/s^2 - * lane_change_duration: lane change duration for autonomous vehicles, in s - * target_velocity: desired velocity for all vehicles in the network, in m/s - * sort_vehicles: specifies whether vehicles are to be sorted by position - during a simulation step. If set to True, the environment parameter - self.sorted_ids will return a list of all vehicles sorted in accordance - with the environment - - States - The state consists of the velocities, absolute position, and lane index - of all vehicles in the network. This assumes a constant number of - vehicles. - - Actions - Actions consist of: - - * a (continuous) acceleration from -abs(max_decel) to max_accel, - specified in env_params - * a (continuous) lane-change action from -1 to 1, used to determine the - lateral direction the vehicle will take. - - Lane change actions are performed only if the vehicle has not changed - lanes for the lane change duration specified in env_params. - - Rewards - The reward function is the two-norm of the distance of the speed of the - vehicles in the network from a desired speed, combined with a penalty - to discourage excess lane changes by the rl vehicle. - - Termination - A rollout is terminated if the time horizon is reached or if two - vehicles collide into one another. - """ - - def __init__(self, env_params, sim_params, scenario, simulator='traci'): - for p in ADDITIONAL_ENV_PARAMS.keys(): - if p not in env_params.additional_params: - raise KeyError( - 'Environment parameter "{}" not supplied'.format(p)) - - super().__init__(env_params, sim_params, scenario, simulator) - - @property - def action_space(self): - """See class definition.""" - max_decel = self.env_params.additional_params["max_decel"] - max_accel = self.env_params.additional_params["max_accel"] - - lb = [-abs(max_decel), -1] * self.initial_vehicles.num_rl_vehicles - ub = [max_accel, 1] * self.initial_vehicles.num_rl_vehicles - - return Box(np.array(lb), np.array(ub), dtype=np.float32) - - @property - def observation_space(self): - """See class definition.""" - return Box( - low=0, - high=1, - shape=(3 * self.initial_vehicles.num_vehicles, ), - dtype=np.float32) - - def compute_reward(self, rl_actions, **kwargs): - """See class definition.""" - # compute the system-level performance of vehicles from a velocity - # perspective - reward = rewards.desired_velocity(self, fail=kwargs["fail"]) - - # punish excessive lane changes by reducing the reward by a set value - # every time an rl car changes lanes (10% of max reward) - for veh_id in self.k.vehicle.get_rl_ids(): - if self.k.vehicle.get_last_lc(veh_id) == self.time_counter: - reward -= 0.1 - - return reward - - def get_state(self): - """See class definition.""" - # normalizers - max_speed = self.k.scenario.max_speed() - length = self.k.scenario.length() - max_lanes = max( - self.k.scenario.num_lanes(edge) - for edge in self.k.scenario.get_edge_list()) - - speed = [self.k.vehicle.get_speed(veh_id) / max_speed - for veh_id in self.sorted_ids] - pos = [self.k.vehicle.get_x_by_id(veh_id) / length - for veh_id in self.sorted_ids] - lane = [self.k.vehicle.get_lane(veh_id) / max_lanes - for veh_id in self.sorted_ids] - - return np.array(speed + pos + lane) - - def _apply_rl_actions(self, actions): - """See class definition.""" - acceleration = actions[::2] - direction = actions[1::2] - - # re-arrange actions according to mapping in observation space - sorted_rl_ids = [ - veh_id for veh_id in self.sorted_ids - if veh_id in self.k.vehicle.get_rl_ids() - ] - - # represents vehicles that are allowed to change lanes - non_lane_changing_veh = \ - [self.time_counter <= - self.env_params.additional_params["lane_change_duration"] - + self.k.vehicle.get_last_lc(veh_id) - for veh_id in sorted_rl_ids] - # vehicle that are not allowed to change have their directions set to 0 - direction[non_lane_changing_veh] = \ - np.array([0] * sum(non_lane_changing_veh)) - - self.k.vehicle.apply_acceleration(sorted_rl_ids, acc=acceleration) - self.k.vehicle.apply_lane_change(sorted_rl_ids, direction=direction) - - def additional_command(self): - """Define which vehicles are observed for visualization purposes.""" - # specify observed vehicles - if self.k.vehicle.num_rl_vehicles > 0: - for veh_id in self.k.vehicle.get_human_ids(): - self.k.vehicle.set_observed(veh_id) - - -class LaneChangeAccelPOEnv(LaneChangeAccelEnv): - """POMDP version of LaneChangeAccelEnv. - - Required from env_params: - - * max_accel: maximum acceleration for autonomous vehicles, in m/s^2 - * max_decel: maximum deceleration for autonomous vehicles, in m/s^2 - * lane_change_duration: lane change duration for autonomous vehicles, in s - * target_velocity: desired velocity for all vehicles in the network, in m/s - - States - States are a list of rl vehicles speeds, as well as the speeds and - bumper-to-bumper headways between the rl vehicles and their - leaders/followers in all lanes. There is no assumption on the number of - vehicles in the network, so long as the number of rl vehicles is - static. - - Actions - See parent class. - - Rewards - See parent class. - - Termination - See parent class. - - Attributes - ---------- - num_lanes : int - maximum number of lanes on any edge in the network - visible : list of str - lists of visible vehicles, used for visualization purposes - """ - - def __init__(self, env_params, sim_params, scenario, simulator='traci'): - super().__init__(env_params, sim_params, scenario, simulator) - - self.num_lanes = max(self.k.scenario.num_lanes(edge) - for edge in self.k.scenario.get_edge_list()) - self.visible = [] - - @property - def observation_space(self): - """See class definition.""" - return Box( - low=0, - high=1, - shape=(4 * self.initial_vehicles.num_rl_vehicles * - self.num_lanes + self.initial_vehicles.num_rl_vehicles, ), - dtype=np.float32) - - def get_state(self): - """See class definition.""" - obs = [ - 0 - for _ in range(4 * self.k.vehicle.num_rl_vehicles * self.num_lanes) - ] - - self.visible = [] - for i, rl_id in enumerate(self.k.vehicle.get_rl_ids()): - # normalizers - max_length = self.k.scenario.length() - max_speed = self.k.scenario.max_speed() - - # set to 1000 since the absence of a vehicle implies a large - # headway - headway = [1] * self.num_lanes - tailway = [1] * self.num_lanes - vel_in_front = [0] * self.num_lanes - vel_behind = [0] * self.num_lanes - - lane_leaders = self.k.vehicle.get_lane_leaders(rl_id) - lane_followers = self.k.vehicle.get_lane_followers(rl_id) - lane_headways = self.k.vehicle.get_lane_headways(rl_id) - lane_tailways = self.k.vehicle.get_lane_tailways(rl_id) - headway[0:len(lane_headways)] = lane_headways - tailway[0:len(lane_tailways)] = lane_tailways - - for j, lane_leader in enumerate(lane_leaders): - if lane_leader != '': - lane_headways[j] /= max_length - vel_in_front[j] = self.k.vehicle.get_speed(lane_leader) \ - / max_speed - self.visible.extend([lane_leader]) - for j, lane_follower in enumerate(lane_followers): - if lane_follower != '': - lane_headways[j] /= max_length - vel_behind[j] = self.k.vehicle.get_speed(lane_follower) \ - / max_speed - self.visible.extend([lane_follower]) - - # add the headways, tailways, and speed for all lane leaders - # and followers - obs[4*self.num_lanes*i:4*self.num_lanes*(i+1)] = \ - np.concatenate((headway, tailway, vel_in_front, vel_behind)) - - # add the speed for the ego rl vehicle - obs.append(self.k.vehicle.get_speed(rl_id)) - - return np.array(obs) - - def additional_command(self): - """Define which vehicles are observed for visualization purposes.""" - # specify observed vehicles - for veh_id in self.visible: - self.k.vehicle.set_observed(veh_id) + pass diff --git a/flow/envs/loop/loop_accel.py b/flow/envs/loop/loop_accel.py old mode 100755 new mode 100644 index 2e9e3e5cd9..a8378a6ac4 --- a/flow/envs/loop/loop_accel.py +++ b/flow/envs/loop/loop_accel.py @@ -1,183 +1,14 @@ -"""Environment for training the acceleration behavior of vehicles in a loop.""" +"""Pending deprecation file. -from flow.core import rewards -from flow.envs.base_env import Env +To view the actual content, go to: flow/envs/ring/accel.py +""" +from flow.utils.flow_warnings import deprecated +from flow.envs.ring.accel import AccelEnv as AEnv -from gym.spaces.box import Box -import numpy as np +@deprecated('flow.envs.loop.accel', + 'flow.envs.ring.accel.AccelEnv') +class AccelEnv(AEnv): + """See parent class.""" -ADDITIONAL_ENV_PARAMS = { - # maximum acceleration for autonomous vehicles, in m/s^2 - 'max_accel': 3, - # maximum deceleration for autonomous vehicles, in m/s^2 - 'max_decel': 3, - # desired velocity for all vehicles in the network, in m/s - 'target_velocity': 10, - # specifies whether vehicles are to be sorted by position during a - # simulation step. If set to True, the environment parameter - # self.sorted_ids will return a list of all vehicles sorted in accordance - # with the environment - 'sort_vehicles': False -} - - -class AccelEnv(Env): - """Fully observed acceleration environment. - - This environment used to train autonomous vehicles to improve traffic flows - when acceleration actions are permitted by the rl agent. - - Required from env_params: - - * max_accel: maximum acceleration for autonomous vehicles, in m/s^2 - * max_decel: maximum deceleration for autonomous vehicles, in m/s^2 - * target_velocity: desired velocity for all vehicles in the network, in m/s - * sort_vehicles: specifies whether vehicles are to be sorted by position - during a simulation step. If set to True, the environment parameter - self.sorted_ids will return a list of all vehicles sorted in accordance - with the environment - - States - The state consists of the velocities and absolute position of all - vehicles in the network. This assumes a constant number of vehicles. - - Actions - Actions are a list of acceleration for each rl vehicles, bounded by the - maximum accelerations and decelerations specified in EnvParams. - - Rewards - The reward function is the two-norm of the distance of the speed of the - vehicles in the network from the "target_velocity" term. For a - description of the reward, see: flow.core.rewards.desired_speed - - Termination - A rollout is terminated if the time horizon is reached or if two - vehicles collide into one another. - - Attributes - ---------- - prev_pos : dict - dictionary keeping track of each veh_id's previous position - absolute_position : dict - dictionary keeping track of each veh_id's absolute position - obs_var_labels : list of str - referenced in the visualizer. Tells the visualizer which - metrics to track - """ - - def __init__(self, env_params, sim_params, scenario, simulator='traci'): - for p in ADDITIONAL_ENV_PARAMS.keys(): - if p not in env_params.additional_params: - raise KeyError( - 'Environment parameter \'{}\' not supplied'.format(p)) - - # variables used to sort vehicles by their initial position plus - # distance traveled - self.prev_pos = dict() - self.absolute_position = dict() - - super().__init__(env_params, sim_params, scenario, simulator) - - @property - def action_space(self): - """See class definition.""" - return Box( - low=-abs(self.env_params.additional_params['max_decel']), - high=self.env_params.additional_params['max_accel'], - shape=(self.initial_vehicles.num_rl_vehicles, ), - dtype=np.float32) - - @property - def observation_space(self): - """See class definition.""" - self.obs_var_labels = ['Velocity', 'Absolute_pos'] - return Box( - low=0, - high=1, - shape=(2 * self.initial_vehicles.num_vehicles, ), - dtype=np.float32) - - def _apply_rl_actions(self, rl_actions): - """See class definition.""" - sorted_rl_ids = [ - veh_id for veh_id in self.sorted_ids - if veh_id in self.k.vehicle.get_rl_ids() - ] - self.k.vehicle.apply_acceleration(sorted_rl_ids, rl_actions) - - def compute_reward(self, rl_actions, **kwargs): - """See class definition.""" - if self.env_params.evaluate: - return np.mean(self.k.vehicle.get_speed(self.k.vehicle.get_ids())) - else: - return rewards.desired_velocity(self, fail=kwargs['fail']) - - def get_state(self): - """See class definition.""" - speed = [self.k.vehicle.get_speed(veh_id) / self.k.scenario.max_speed() - for veh_id in self.sorted_ids] - pos = [self.k.vehicle.get_x_by_id(veh_id) / self.k.scenario.length() - for veh_id in self.sorted_ids] - - return np.array(speed + pos) - - def additional_command(self): - """See parent class. - - Define which vehicles are observed for visualization purposes, and - update the sorting of vehicles using the self.sorted_ids variable. - """ - # specify observed vehicles - if self.k.vehicle.num_rl_vehicles > 0: - for veh_id in self.k.vehicle.get_human_ids(): - self.k.vehicle.set_observed(veh_id) - - # update the "absolute_position" variable - for veh_id in self.k.vehicle.get_ids(): - this_pos = self.k.vehicle.get_x_by_id(veh_id) - - if this_pos == -1001: - # in case the vehicle isn't in the network - self.absolute_position[veh_id] = -1001 - else: - change = this_pos - self.prev_pos.get(veh_id, this_pos) - self.absolute_position[veh_id] = \ - (self.absolute_position.get(veh_id, this_pos) + change) \ - % self.k.scenario.length() - self.prev_pos[veh_id] = this_pos - - @property - def sorted_ids(self): - """Sort the vehicle ids of vehicles in the network by position. - - This environment does this by sorting vehicles by their absolute - position, defined as their initial position plus distance traveled. - - Returns - ------- - list of str - a list of all vehicle IDs sorted by position - """ - if self.env_params.additional_params['sort_vehicles']: - return sorted(self.k.vehicle.get_ids(), key=self._get_abs_position) - else: - return self.k.vehicle.get_ids() - - def _get_abs_position(self, veh_id): - """Return the absolute position of a vehicle.""" - return self.absolute_position.get(veh_id, -1001) - - def reset(self): - """See parent class. - - This also includes updating the initial absolute position and previous - position. - """ - obs = super().reset() - - for veh_id in self.k.vehicle.get_ids(): - self.absolute_position[veh_id] = self.k.vehicle.get_x_by_id(veh_id) - self.prev_pos[veh_id] = self.k.vehicle.get_x_by_id(veh_id) - - return obs + pass diff --git a/flow/envs/loop/wave_attenuation.py b/flow/envs/loop/wave_attenuation.py index 6e54cd6af6..1be0bfa45e 100644 --- a/flow/envs/loop/wave_attenuation.py +++ b/flow/envs/loop/wave_attenuation.py @@ -1,281 +1,23 @@ -""" -Environment used to train a stop-and-go dissipating controller. - -This is the environment that was used in: +"""Pending deprecation file. -C. Wu, A. Kreidieh, K. Parvate, E. Vinitsky, A. Bayen, "Flow: Architecture and -Benchmarking for Reinforcement Learning in Traffic Control," CoRR, vol. -abs/1710.05465, 2017. [Online]. Available: https://arxiv.org/abs/1710.05465 +To view the actual content, go to: flow/envs/ring/wave_attenuation.py """ +from flow.utils.flow_warnings import deprecated +from flow.envs.ring.wave_attenuation import WaveAttenuationEnv as WAEnv +from flow.envs.ring.wave_attenuation import WaveAttenuationPOEnv as WAPOEnv -from flow.core.params import InitialConfig -from flow.core.params import NetParams -from flow.envs.base_env import Env - -from gym.spaces.box import Box - -from copy import deepcopy -import numpy as np -import random -from scipy.optimize import fsolve - -ADDITIONAL_ENV_PARAMS = { - # maximum acceleration of autonomous vehicles - 'max_accel': 1, - # maximum deceleration of autonomous vehicles - 'max_decel': 1, - # bounds on the ranges of ring road lengths the autonomous vehicle is - # trained on - 'ring_length': [220, 270], -} - - -def v_eq_max_function(v, *args): - """Return the error between the desired and actual equivalent gap.""" - num_vehicles, length = args - - # maximum gap in the presence of one rl vehicle - s_eq_max = (length - num_vehicles * 5) / (num_vehicles - 1) - - v0 = 30 - s0 = 2 - tau = 1 - gamma = 4 - - error = s_eq_max - (s0 + v * tau) * (1 - (v / v0) ** gamma) ** -0.5 - - return error - - -class WaveAttenuationEnv(Env): - """Fully observable wave attenuation environment. - - This environment is used to train autonomous vehicles to attenuate the - formation and propagation of waves in a variable density ring road. - - Required from env_params: - - * max_accel: maximum acceleration of autonomous vehicles - * max_decel: maximum deceleration of autonomous vehicles - * ring_length: bounds on the ranges of ring road lengths the autonomous - vehicle is trained on. If set to None, the environment sticks to the ring - road specified in the original scenario definition. - - States - The state consists of the velocities and absolute position of all - vehicles in the network. This assumes a constant number of vehicles. - - Actions - Actions are a list of acceleration for each rl vehicles, bounded by the - maximum accelerations and decelerations specified in EnvParams. - - Rewards - The reward function rewards high average speeds from all vehicles in - the network, and penalizes accelerations by the rl vehicle. - - Termination - A rollout is terminated if the time horizon is reached or if two - vehicles collide into one another. - """ - - def __init__(self, env_params, sim_params, scenario, simulator='traci'): - for p in ADDITIONAL_ENV_PARAMS.keys(): - if p not in env_params.additional_params: - raise KeyError( - 'Environment parameter \'{}\' not supplied'.format(p)) - - super().__init__(env_params, sim_params, scenario, simulator) - - @property - def action_space(self): - """See class definition.""" - return Box( - low=-np.abs(self.env_params.additional_params['max_decel']), - high=self.env_params.additional_params['max_accel'], - shape=(self.initial_vehicles.num_rl_vehicles, ), - dtype=np.float32) - - @property - def observation_space(self): - """See class definition.""" - self.obs_var_labels = ["Velocity", "Absolute_pos"] - return Box( - low=0, - high=1, - shape=(2 * self.initial_vehicles.num_vehicles, ), - dtype=np.float32) - - def _apply_rl_actions(self, rl_actions): - """See class definition.""" - self.k.vehicle.apply_acceleration( - self.k.vehicle.get_rl_ids(), rl_actions) - - def compute_reward(self, rl_actions, **kwargs): - """See class definition.""" - # in the warmup steps - if rl_actions is None: - return 0 - - vel = np.array([ - self.k.vehicle.get_speed(veh_id) - for veh_id in self.k.vehicle.get_ids() - ]) - - if any(vel < -100) or kwargs['fail']: - return 0. - - # reward average velocity - eta_2 = 4. - reward = eta_2 * np.mean(vel) / 20 - - # punish accelerations (should lead to reduced stop-and-go waves) - eta = 4 # 0.25 - mean_actions = np.mean(np.abs(np.array(rl_actions))) - accel_threshold = 0 - - if mean_actions > accel_threshold: - reward += eta * (accel_threshold - mean_actions) - - return float(reward) - - def get_state(self): - """See class definition.""" - speed = [self.k.vehicle.get_speed(veh_id) / self.k.scenario.max_speed() - for veh_id in self.k.vehicle.get_ids()] - pos = [self.k.vehicle.get_x_by_id(veh_id) / self.k.scenario.length() - for veh_id in self.k.vehicle.get_ids()] - - return np.array(speed + pos) - - def additional_command(self): - """Define which vehicles are observed for visualization purposes.""" - # specify observed vehicles - if self.k.vehicle.num_rl_vehicles > 0: - for veh_id in self.k.vehicle.get_human_ids(): - self.k.vehicle.set_observed(veh_id) - - def reset(self): - """See parent class. - - The sumo instance is reset with a new ring length, and a number of - steps are performed with the rl vehicle acting as a human vehicle. - """ - # skip if ring length is None - if self.env_params.additional_params['ring_length'] is None: - return super().reset() - - # reset the step counter - self.step_counter = 0 - - # update the scenario - initial_config = InitialConfig(bunching=50, min_gap=0) - length = random.randint( - self.env_params.additional_params['ring_length'][0], - self.env_params.additional_params['ring_length'][1]) - additional_net_params = { - 'length': - length, - 'lanes': - self.net_params.additional_params['lanes'], - 'speed_limit': - self.net_params.additional_params['speed_limit'], - 'resolution': - self.net_params.additional_params['resolution'] - } - net_params = NetParams(additional_params=additional_net_params) - - self.scenario = self.scenario.__class__( - self.scenario.orig_name, self.scenario.vehicles, - net_params, initial_config) - self.k.vehicle = deepcopy(self.initial_vehicles) - self.k.vehicle.kernel_api = self.k.kernel_api - self.k.vehicle.master_kernel = self.k - - # solve for the velocity upper bound of the ring - v_guess = 4 - v_eq_max = fsolve(v_eq_max_function, np.array(v_guess), - args=(len(self.initial_ids), length))[0] - - print('\n-----------------------') - print('ring length:', net_params.additional_params['length']) - print('v_max:', v_eq_max) - print('-----------------------') - - # restart the sumo instance - self.restart_simulation( - sim_params=self.sim_params, - render=self.sim_params.render) - - # perform the generic reset function - observation = super().reset() - - # reset the timer to zero - self.time_counter = 0 - - return observation - - -class WaveAttenuationPOEnv(WaveAttenuationEnv): - """POMDP version of WaveAttenuationEnv. - - Note that this environment only works when there is one autonomous vehicle - on the network. - - Required from env_params: - - * max_accel: maximum acceleration of autonomous vehicles - * max_decel: maximum deceleration of autonomous vehicles - * ring_length: bounds on the ranges of ring road lengths the autonomous - vehicle is trained on - - States - The state consists of the speed and headway of the ego vehicle, as well - as the difference in speed between the ego vehicle and its leader. - There is no assumption on the number of vehicles in the network. - - Actions - See parent class - - Rewards - See parent class - - Termination - See parent class - - """ - - @property - def observation_space(self): - """See class definition.""" - return Box(low=-float('inf'), high=float('inf'), - shape=(3, ), dtype=np.float32) - def get_state(self): - """See class definition.""" - rl_id = self.k.vehicle.get_rl_ids()[0] - lead_id = self.k.vehicle.get_leader(rl_id) or rl_id +@deprecated('flow.envs.loop.wave_attenuation', + 'flow.envs.ring.wave_attenuation.WaveAttenuationEnv') +class WaveAttenuationEnv(WAEnv): + """See parent class.""" - # normalizers - max_speed = 15. - if self.env_params.additional_params['ring_length'] is not None: - max_length = self.env_params.additional_params['ring_length'][1] - else: - max_length = self.k.scenario.length() + pass - observation = np.array([ - self.k.vehicle.get_speed(rl_id) / max_speed, - (self.k.vehicle.get_speed(lead_id) - - self.k.vehicle.get_speed(rl_id)) / max_speed, - (self.k.vehicle.get_x_by_id(lead_id) - - self.k.vehicle.get_x_by_id(rl_id)) % self.k.scenario.length() - / max_length - ]) - return observation +@deprecated('flow.envs.loop.wave_attenuation', + 'flow.envs.ring.wave_attenuation.WaveAttenuationPOEnv') +class WaveAttenuationPOEnv(WAPOEnv): + """See parent class.""" - def additional_command(self): - """Define which vehicles are observed for visualization purposes.""" - # specify observed vehicles - rl_id = self.k.vehicle.get_rl_ids()[0] - lead_id = self.k.vehicle.get_leader(rl_id) or rl_id - self.k.vehicle.set_observed(lead_id) + pass diff --git a/flow/envs/merge.py b/flow/envs/merge.py index dce86f3145..50178a7ec3 100644 --- a/flow/envs/merge.py +++ b/flow/envs/merge.py @@ -5,7 +5,7 @@ TODO(ak): add paper after it has been published. """ -from flow.envs.base_env import Env +from flow.envs.base import Env from flow.core import rewards from gym.spaces.box import Box @@ -25,7 +25,7 @@ } -class WaveAttenuationMergePOEnv(Env): +class MergePOEnv(Env): """Partially observable merge environment. This environment is used to train autonomous vehicles to attenuate the diff --git a/flow/envs/multiagent/__init__.py b/flow/envs/multiagent/__init__.py new file mode 100644 index 0000000000..4c43611aa6 --- /dev/null +++ b/flow/envs/multiagent/__init__.py @@ -0,0 +1,12 @@ +"""Empty init file to ensure documentation for multi-agent envs is created.""" + +from flow.envs.multiagent.base import MultiEnv +from flow.envs.multiagent.ring.wave_attenuation import \ + MultiWaveAttenuationPOEnv + +from flow.envs.multiagent.ring.accel import MultiAgentAccelEnv +from flow.envs.multiagent.traffic_light_grid import MultiTrafficLightGridPOEnv +from flow.envs.multiagent.highway import MultiAgentHighwayPOEnv + +__all__ = ['MultiEnv', 'MultiAgentAccelEnv', 'MultiWaveAttenuationPOEnv', + 'MultiTrafficLightGridPOEnv', 'MultiAgentHighwayPOEnv'] diff --git a/flow/envs/multiagent/base.py b/flow/envs/multiagent/base.py new file mode 100644 index 0000000000..4753c9f459 --- /dev/null +++ b/flow/envs/multiagent/base.py @@ -0,0 +1,297 @@ +"""Environment for training multi-agent experiments.""" + +from copy import deepcopy +import numpy as np +import random +import traceback +from gym.spaces import Box + +from traci.exceptions import FatalTraCIError +from traci.exceptions import TraCIException + +from ray.rllib.env import MultiAgentEnv + +from flow.envs.base import Env +from flow.utils.exceptions import FatalFlowError + + +class MultiEnv(MultiAgentEnv, Env): + """Multi-agent version of base env. See parent class for info.""" + + def step(self, rl_actions): + """Advance the environment by one step. + + Assigns actions to autonomous and human-driven agents (i.e. vehicles, + traffic lights, etc...). Actions that are not assigned are left to the + control of the simulator. The actions are then used to advance the + simulator by the number of time steps requested per environment step. + + Results from the simulations are processed through various classes, + such as the Vehicle and TrafficLight kernels, to produce standardized + methods for identifying specific network state features. Finally, + results from the simulator are used to generate appropriate + observations. + + Parameters + ---------- + rl_actions : array_like + an list of actions provided by the rl algorithm + + Returns + ------- + observation : dict of array_like + agent's observation of the current environment + reward : dict of floats + amount of reward associated with the previous state/action pair + done : dict of bool + indicates whether the episode has ended + info : dict + contains other diagnostic information from the previous action + """ + for _ in range(self.env_params.sims_per_step): + self.time_counter += 1 + self.step_counter += 1 + + # perform acceleration actions for controlled human-driven vehicles + if len(self.k.vehicle.get_controlled_ids()) > 0: + accel = [] + for veh_id in self.k.vehicle.get_controlled_ids(): + accel_contr = self.k.vehicle.get_acc_controller(veh_id) + action = accel_contr.get_action(self) + accel.append(action) + self.k.vehicle.apply_acceleration( + self.k.vehicle.get_controlled_ids(), accel) + + # perform lane change actions for controlled human-driven vehicles + if len(self.k.vehicle.get_controlled_lc_ids()) > 0: + direction = [] + for veh_id in self.k.vehicle.get_controlled_lc_ids(): + target_lane = self.k.vehicle.get_lane_changing_controller( + veh_id).get_action(self) + direction.append(target_lane) + self.k.vehicle.apply_lane_change( + self.k.vehicle.get_controlled_lc_ids(), + direction=direction) + + # perform (optionally) routing actions for all vehicle in the + # network, including rl and sumo-controlled vehicles + routing_ids = [] + routing_actions = [] + for veh_id in self.k.vehicle.get_ids(): + if self.k.vehicle.get_routing_controller(veh_id) is not None: + routing_ids.append(veh_id) + route_contr = self.k.vehicle.get_routing_controller(veh_id) + routing_actions.append(route_contr.choose_route(self)) + self.k.vehicle.choose_routes(routing_ids, routing_actions) + + self.apply_rl_actions(rl_actions) + + self.additional_command() + + # advance the simulation in the simulator by one step + self.k.simulation.simulation_step() + + # store new observations in the vehicles and traffic lights class + self.k.update(reset=False) + + # update the colors of vehicles + if self.sim_params.render: + self.k.vehicle.update_vehicle_colors() + + # crash encodes whether the simulator experienced a collision + crash = self.k.simulation.check_collision() + + # stop collecting new simulation steps if there is a collision + if crash: + break + + states = self.get_state() + done = {key: key in self.k.vehicle.get_arrived_ids() + for key in states.keys()} + if crash: + done['__all__'] = True + else: + done['__all__'] = False + infos = {key: {} for key in states.keys()} + + # compute the reward + if self.env_params.clip_actions: + clipped_actions = self.clip_actions(rl_actions) + reward = self.compute_reward(clipped_actions, fail=crash) + else: + reward = self.compute_reward(rl_actions, fail=crash) + + return states, reward, done, infos + + def reset(self, new_inflow_rate=None): + """Reset the environment. + + This method is performed in between rollouts. It resets the state of + the environment, and re-initializes the vehicles in their starting + positions. + + If "shuffle" is set to True in InitialConfig, the initial positions of + vehicles is recalculated and the vehicles are shuffled. + + Returns + ------- + observation : dict of array_like + the initial observation of the space. The initial reward is assumed + to be zero. + """ + # reset the time counter + self.time_counter = 0 + + # warn about not using restart_instance when using inflows + if len(self.net_params.inflows.get()) > 0 and \ + not self.sim_params.restart_instance: + print( + "**********************************************************\n" + "**********************************************************\n" + "**********************************************************\n" + "WARNING: Inflows will cause computational performance to\n" + "significantly decrease after large number of rollouts. In \n" + "order to avoid this, set SumoParams(restart_instance=True).\n" + "**********************************************************\n" + "**********************************************************\n" + "**********************************************************" + ) + + if self.sim_params.restart_instance or \ + (self.step_counter > 2e6 and self.simulator != 'aimsun'): + self.step_counter = 0 + # issue a random seed to induce randomness into the next rollout + self.sim_params.seed = random.randint(0, 1e5) + + self.k.vehicle = deepcopy(self.initial_vehicles) + self.k.vehicle.master_kernel = self.k + # restart the sumo instance + self.restart_simulation(self.sim_params) + + # perform shuffling (if requested) + elif self.initial_config.shuffle: + self.setup_initial_state() + + # clear all vehicles from the network and the vehicles class + if self.simulator == 'traci': + for veh_id in self.k.kernel_api.vehicle.getIDList(): # FIXME: hack + try: + self.k.vehicle.remove(veh_id) + except (FatalTraCIError, TraCIException): + print(traceback.format_exc()) + + # clear all vehicles from the network and the vehicles class + # FIXME (ev, ak) this is weird and shouldn't be necessary + for veh_id in list(self.k.vehicle.get_ids()): + # do not try to remove the vehicles from the network in the first + # step after initializing the network, as there will be no vehicles + if self.step_counter == 0: + continue + try: + self.k.vehicle.remove(veh_id) + except (FatalTraCIError, TraCIException): + print("Error during start: {}".format(traceback.format_exc())) + + # reintroduce the initial vehicles to the network + for veh_id in self.initial_ids: + type_id, edge, lane_index, pos, speed = \ + self.initial_state[veh_id] + + try: + self.k.vehicle.add( + veh_id=veh_id, + type_id=type_id, + edge=edge, + lane=lane_index, + pos=pos, + speed=speed) + except (FatalTraCIError, TraCIException): + # if a vehicle was not removed in the first attempt, remove it + # now and then reintroduce it + self.k.vehicle.remove(veh_id) + if self.simulator == 'traci': + self.k.kernel_api.vehicle.remove(veh_id) # FIXME: hack + self.k.vehicle.add( + veh_id=veh_id, + type_id=type_id, + edge=edge, + lane=lane_index, + pos=pos, + speed=speed) + + # advance the simulation in the simulator by one step + self.k.simulation.simulation_step() + + # update the information in each kernel to match the current state + self.k.update(reset=True) + + # update the colors of vehicles + if self.sim_params.render: + self.k.vehicle.update_vehicle_colors() + + # check to make sure all vehicles have been spawned + if len(self.initial_ids) > self.k.vehicle.num_vehicles: + missing_vehicles = list( + set(self.initial_ids) - set(self.k.vehicle.get_ids())) + msg = '\nNot enough vehicles have spawned! Bad start?\n' \ + 'Missing vehicles / initial state:\n' + for veh_id in missing_vehicles: + msg += '- {}: {}\n'.format(veh_id, self.initial_state[veh_id]) + raise FatalFlowError(msg=msg) + + # perform (optional) warm-up steps before training + for _ in range(self.env_params.warmup_steps): + observation, _, _, _ = self.step(rl_actions=None) + + # render a frame + self.render(reset=True) + + return self.get_state() + + def clip_actions(self, rl_actions=None): + """Clip the actions passed from the RL agent. + + If no actions are provided at any given step, the rl agents default to + performing actions specified by sumo. + + Parameters + ---------- + rl_actions : array_like + list of actions provided by the RL algorithm + + Returns + ------- + rl_clipped : array_like + The rl_actions clipped according to the box + """ + # ignore if no actions are issued + if rl_actions is None: + return None + + # clip according to the action space requirements + if isinstance(self.action_space, Box): + for key, action in rl_actions.items(): + rl_actions[key] = np.clip( + action, + a_min=self.action_space.low, + a_max=self.action_space.high) + return rl_actions + + def apply_rl_actions(self, rl_actions=None): + """Specify the actions to be performed by the rl agent(s). + + If no actions are provided at any given step, the rl agents default to + performing actions specified by sumo. + + Parameters + ---------- + rl_actions : dict of array_like + dict of list of actions provided by the RL algorithm + """ + # ignore if no actions are issued + if rl_actions is None: + return + + # clip according to the action space requirements + clipped_actions = self.clip_actions(rl_actions) + self._apply_rl_actions(clipped_actions) diff --git a/flow/envs/multiagent/highway.py b/flow/envs/multiagent/highway.py new file mode 100644 index 0000000000..493bc226a9 --- /dev/null +++ b/flow/envs/multiagent/highway.py @@ -0,0 +1,190 @@ +"""Environment used to train vehicles to improve traffic on a highway.""" +import numpy as np +from gym.spaces.box import Box +from flow.core.rewards import desired_velocity +from flow.envs.multiagent.base import MultiEnv + + +ADDITIONAL_ENV_PARAMS = { + # maximum acceleration of autonomous vehicles + 'max_accel': 1, + # maximum deceleration of autonomous vehicles + 'max_decel': 1, + # desired velocity for all vehicles in the network, in m/s + "target_velocity": 25 +} + + +class MultiAgentHighwayPOEnv(MultiEnv): + """Partially observable multi-agent environment for an highway with ramps. + + This environment is used to train autonomous vehicles to attenuate the + formation and propagation of waves in an open highway network. + + The highway can contain an arbitrary number of entrance and exit ramps, and + is intended to be used with the HighwayRampsScenario scenario. + + The policy is shared among the agents, so there can be a non-constant + number of RL vehicles throughout the simulation. + + Required from env_params: + + * max_accel: maximum acceleration for autonomous vehicles, in m/s^2 + * max_decel: maximum deceleration for autonomous vehicles, in m/s^2 + * target_velocity: desired velocity for all vehicles in the network, in m/s + + The following states, actions and rewards are considered for one autonomous + vehicle only, as they will be computed in the same way for each of them. + + States + The observation consists of the speeds and bumper-to-bumper headways of + the vehicles immediately preceding and following autonomous vehicle, as + well as the speed of the autonomous vehicle. + + Actions + The action consists of an acceleration, bound according to the + environment parameters, as well as three values that will be converted + into probabilities via softmax to decide of a lane change (left, none + or right). + + Rewards + The reward function encourages proximity of the system-level velocity + to a desired velocity specified in the environment parameters, while + slightly penalizing small time headways among autonomous vehicles. + + Termination + A rollout is terminated if the time horizon is reached or if two + vehicles collide into one another. + """ + + def __init__(self, env_params, sim_params, scenario, simulator='traci'): + for p in ADDITIONAL_ENV_PARAMS.keys(): + if p not in env_params.additional_params: + raise KeyError( + 'Environment parameter "{}" not supplied'.format(p)) + + super().__init__(env_params, sim_params, scenario, simulator) + + @property + def observation_space(self): + """See class definition.""" + return Box(low=0, high=1, shape=(5, ), dtype=np.float32) + + @property + def action_space(self): + """See class definition.""" + return Box( + low=-np.abs(self.env_params.additional_params['max_decel']), + high=self.env_params.additional_params['max_accel'], + shape=(1,), # (4,), + dtype=np.float32) + + def _apply_rl_actions(self, rl_actions): + """See class definition.""" + # in the warmup steps, rl_actions is None + if rl_actions: + for rl_id, actions in rl_actions.items(): + accel = actions[0] + + # lane_change_softmax = np.exp(actions[1:4]) + # lane_change_softmax /= np.sum(lane_change_softmax) + # lane_change_action = np.random.choice([-1, 0, 1], + # p=lane_change_softmax) + + self.k.vehicle.apply_acceleration(rl_id, accel) + # self.k.vehicle.apply_lane_change(rl_id, lane_change_action) + + def get_state(self): + """See class definition.""" + obs = {} + + # normalizing constants + max_speed = self.k.scenario.max_speed() + max_length = self.k.scenario.length() + + for rl_id in self.k.vehicle.get_rl_ids(): + this_speed = self.k.vehicle.get_speed(rl_id) + lead_id = self.k.vehicle.get_leader(rl_id) + follower = self.k.vehicle.get_follower(rl_id) + + if lead_id in ["", None]: + # in case leader is not visible + lead_speed = max_speed + lead_head = max_length + else: + lead_speed = self.k.vehicle.get_speed(lead_id) + lead_head = self.k.vehicle.get_headway(lead_id) + + if follower in ["", None]: + # in case follower is not visible + follow_speed = 0 + follow_head = max_length + else: + follow_speed = self.k.vehicle.get_speed(follower) + follow_head = self.k.vehicle.get_headway(follower) + + observation = np.array([ + this_speed / max_speed, + (lead_speed - this_speed) / max_speed, + lead_head / max_length, + (this_speed - follow_speed) / max_speed, + follow_head / max_length + ]) + + obs.update({rl_id: observation}) + + return obs + + def compute_reward(self, rl_actions, **kwargs): + """See class definition.""" + # in the warmup steps + if rl_actions is None: + return {} + + rewards = {} + for rl_id in self.k.vehicle.get_rl_ids(): + if self.env_params.evaluate: + # reward is speed of vehicle if we are in evaluation mode + reward = self.k.vehicle.get_speed(rl_id) + elif kwargs['fail']: + # reward is 0 if a collision occurred + reward = 0 + else: + # reward high system-level velocities + cost1 = desired_velocity(self, fail=kwargs['fail']) + + # penalize small time headways + cost2 = 0 + t_min = 1 # smallest acceptable time headway + + lead_id = self.k.vehicle.get_leader(rl_id) + if lead_id not in ["", None] \ + and self.k.vehicle.get_speed(rl_id) > 0: + t_headway = max( + self.k.vehicle.get_headway(rl_id) / + self.k.vehicle.get_speed(rl_id), 0) + cost2 += min((t_headway - t_min) / t_min, 0) + + # weights for cost1, cost2, and cost3, respectively + eta1, eta2 = 1.00, 0.10 + + reward = max(eta1 * cost1 + eta2 * cost2, 0) + + rewards[rl_id] = reward + return rewards + + def additional_command(self): + """See parent class. + + Define which vehicles are observed for visualization purposes. + """ + # specify observed vehicles + for rl_id in self.k.vehicle.get_rl_ids(): + # leader + lead_id = self.k.vehicle.get_leader(rl_id) + if lead_id: + self.k.vehicle.set_observed(lead_id) + # follower + follow_id = self.k.vehicle.get_follower(rl_id) + if follow_id: + self.k.vehicle.set_observed(follow_id) diff --git a/flow/multiagent_envs/loop/__init__.py b/flow/envs/multiagent/ring/__init__.py similarity index 100% rename from flow/multiagent_envs/loop/__init__.py rename to flow/envs/multiagent/ring/__init__.py diff --git a/flow/envs/multiagent/ring/accel.py b/flow/envs/multiagent/ring/accel.py new file mode 100644 index 0000000000..ba2e32fed4 --- /dev/null +++ b/flow/envs/multiagent/ring/accel.py @@ -0,0 +1,52 @@ +"""Environment for training the acceleration behavior of vehicles in a ring.""" + +import numpy as np +from flow.core import rewards +from flow.envs.ring.accel import AccelEnv +from flow.envs.multiagent.base import MultiEnv + + +class MultiAgentAccelEnv(AccelEnv, MultiEnv): + """Adversarial multi-agent env. + + Multi-agent env with an adversarial agent perturbing + the accelerations of the autonomous vehicle + """ + + def _apply_rl_actions(self, rl_actions): + """See class definition.""" + sorted_rl_ids = [ + veh_id for veh_id in self.sorted_ids + if veh_id in self.k.vehicle.get_rl_ids() + ] + av_action = rl_actions['av'] + adv_action = rl_actions['adversary'] + perturb_weight = self.env_params.additional_params['perturb_weight'] + rl_action = av_action + perturb_weight * adv_action + self.k.vehicle.apply_acceleration(sorted_rl_ids, rl_action) + + def compute_reward(self, rl_actions, **kwargs): + """Compute opposing rewards for agents. + + The agent receives the class definition reward, + the adversary receives the negative of the agent reward + """ + if self.env_params.evaluate: + reward = np.mean(self.k.vehicle.get_speed( + self.k.vehicle.get_ids())) + return {'av': reward, 'adversary': -reward} + else: + reward = rewards.desired_velocity(self, fail=kwargs['fail']) + return {'av': reward, 'adversary': -reward} + + def get_state(self, **kwargs): + """See class definition for the state. + + The adversary state and the agent state are identical. + """ + state = np.array([[ + self.k.vehicle.get_speed(veh_id) / self.k.scenario.max_speed(), + self.k.vehicle.get_x_by_id(veh_id) / self.k.scenario.length() + ] for veh_id in self.sorted_ids]) + state = np.ndarray.flatten(state) + return {'av': state, 'adversary': state} diff --git a/flow/envs/multiagent/ring/wave_attenuation.py b/flow/envs/multiagent/ring/wave_attenuation.py new file mode 100644 index 0000000000..89a71ee073 --- /dev/null +++ b/flow/envs/multiagent/ring/wave_attenuation.py @@ -0,0 +1,131 @@ +""" +Environment used to train a stop-and-go dissipating controller. + +This is the environment that was used in: + +C. Wu, A. Kreidieh, K. Parvate, E. Vinitsky, A. Bayen, "Flow: Architecture and +Benchmarking for Reinforcement Learning in Traffic Control," CoRR, vol. +abs/1710.05465, 2017. [Online]. Available: https://arxiv.org/abs/1710.05465 +""" + +import numpy as np +from gym.spaces.box import Box +from flow.envs.multiagent.base import MultiEnv + +ADDITIONAL_ENV_PARAMS = { + # maximum acceleration of autonomous vehicles + 'max_accel': 1, + # maximum deceleration of autonomous vehicles + 'max_decel': 1, + # bounds on the ranges of ring road lengths the autonomous vehicle is + # trained on + 'ring_length': [220, 270], +} + + +class MultiWaveAttenuationPOEnv(MultiEnv): + """Multiagent shared model version of WaveAttenuationPOEnv. + + Intended to work with Lord Of The Rings Scenario. + Note that this environment current + only works when there is one autonomous vehicle + on each ring. + + Required from env_params: See parent class + + States + See parent class + Actions + See parent class + + Rewards + See parent class + + Termination + See parent class + + """ + + @property + def observation_space(self): + """See class definition.""" + return Box(low=0, high=1, shape=(3,), dtype=np.float32) + + @property + def action_space(self): + """See class definition.""" + add_params = self.net_params.additional_params + num_rings = add_params['num_rings'] + return Box( + low=-np.abs(self.env_params.additional_params['max_decel']), + high=self.env_params.additional_params['max_accel'], + shape=(int(self.initial_vehicles.num_rl_vehicles / num_rings), ), + dtype=np.float32) + + def get_state(self): + """See class definition.""" + obs = {} + for rl_id in self.k.vehicle.get_rl_ids(): + lead_id = self.k.vehicle.get_leader(rl_id) or rl_id + + # normalizers + max_speed = 15. + max_length = self.env_params.additional_params['ring_length'][1] + + observation = np.array([ + self.k.vehicle.get_speed(rl_id) / max_speed, + (self.k.vehicle.get_speed(lead_id) - + self.k.vehicle.get_speed(rl_id)) + / max_speed, + self.k.vehicle.get_headway(rl_id) / max_length + ]) + obs.update({rl_id: observation}) + + return obs + + def _apply_rl_actions(self, rl_actions): + """Split the accelerations by ring.""" + if rl_actions: + rl_ids = list(rl_actions.keys()) + accel = list(rl_actions.values()) + self.k.vehicle.apply_acceleration(rl_ids, accel) + + def compute_reward(self, rl_actions, **kwargs): + """See class definition.""" + # in the warmup steps + if rl_actions is None: + return {} + + rew = {} + for rl_id in rl_actions.keys(): + edge_id = rl_id.split('_')[1] + edges = self.gen_edges(edge_id) + vehs_on_edge = self.k.vehicle.get_ids_by_edge(edges) + vel = np.array([ + self.k.vehicle.get_speed(veh_id) + for veh_id in vehs_on_edge + ]) + if any(vel < -100) or kwargs['fail']: + return 0. + + target_vel = self.env_params.additional_params['target_velocity'] + max_cost = np.array([target_vel] * len(vehs_on_edge)) + max_cost = np.linalg.norm(max_cost) + + cost = vel - target_vel + cost = np.linalg.norm(cost) + + rew[rl_id] = max(max_cost - cost, 0) / max_cost + return rew + + def additional_command(self): + """Define which vehicles are observed for visualization purposes.""" + # specify observed vehicles + for rl_id in self.k.vehicle.get_rl_ids(): + lead_id = self.k.vehicle.get_leader(rl_id) or rl_id + self.k.vehicle.set_observed(lead_id) + + def gen_edges(self, i): + """Return the edges corresponding to the rl id.""" + return ['top_{}'.format(i), 'left_{}'.format(i), + 'right_{}'.format(i), 'bottom_{}'.format(i)] diff --git a/flow/envs/multiagent/traffic_light_grid.py b/flow/envs/multiagent/traffic_light_grid.py new file mode 100644 index 0000000000..7701e56d6e --- /dev/null +++ b/flow/envs/multiagent/traffic_light_grid.py @@ -0,0 +1,267 @@ +"""Multi-agent environments for scenarios with traffic lights. + +These environments are used to train traffic lights to regulate traffic flow +through an n x m traffic light grid. +""" + +import numpy as np +from gym.spaces.box import Box +from gym.spaces.discrete import Discrete + +from flow.core import rewards +from flow.envs.traffic_light_grid import TrafficLightGridPOEnv +from flow.envs.multiagent import MultiEnv + +ADDITIONAL_ENV_PARAMS = { + # num of nearby lights the agent can observe {0, ..., num_traffic_lights-1} + "num_local_lights": 4, # FIXME: not implemented yet + # num of nearby edges the agent can observe {0, ..., num_edges} + "num_local_edges": 4, # FIXME: not implemented yet +} + +# Index for retrieving ID when splitting node name, e.g. ":center#" +ID_IDX = 1 + + +class MultiTrafficLightGridPOEnv(TrafficLightGridPOEnv, MultiEnv): + """Multiagent shared model version of TrafficLightGridPOEnv. + + Required from env_params: See parent class + + States + See parent class + + Actions + See parent class + + Rewards + See parent class + + Termination + See parent class + """ + + def __init__(self, env_params, sim_params, scenario, simulator='traci'): + super().__init__(env_params, sim_params, scenario, simulator) + + for p in ADDITIONAL_ENV_PARAMS.keys(): + if p not in env_params.additional_params: + raise KeyError( + 'Environment parameter "{}" not supplied'.format(p)) + + # number of nearest lights to observe, defaults to 4 + self.num_local_lights = env_params.additional_params.get( + "num_local_lights", 4) + + # number of nearest edges to observe, defaults to 4 + self.num_local_edges = env_params.additional_params.get( + "num_local_edges", 4) + + @property + def observation_space(self): + """State space that is partially observed. + + Velocities, distance to intersections, edge number (for nearby + vehicles) from each direction, local edge information, and traffic + light state. + """ + tl_box = Box( + low=0., + high=1, + shape=(3 * 4 * self.num_observed + + 2 * self.num_local_edges + + 3 * (1 + self.num_local_lights), + ), + dtype=np.float32) + return tl_box + + @property + def action_space(self): + """See class definition.""" + if self.discrete: + return Discrete(2) + else: + return Box( + low=-1, + high=1, + shape=(1,), + dtype=np.float32) + + def get_state(self): + """Observations for each traffic light agent. + + :return: dictionary which contains agent-wise observations as follows: + - For the self.num_observed number of vehicles closest and incoming + towards traffic light agent, gives the vehicle velocity, distance to + intersection, edge number. + - For edges in the network, gives the density and average velocity. + - For the self.num_local_lights number of nearest lights (itself + included), gives the traffic light information, including the last + change time, light direction (i.e. phase), and a currently_yellow flag. + """ + # Normalization factors + max_speed = max( + self.k.scenario.speed_limit(edge) + for edge in self.k.scenario.get_edge_list()) + grid_array = self.net_params.additional_params["grid_array"] + max_dist = max(grid_array["short_length"], grid_array["long_length"], + grid_array["inner_length"]) + + # TODO(cathywu) refactor TrafficLightGridPOEnv with convenience + # methods for observations, but remember to flatten for single-agent + + # Observed vehicle information + speeds = [] + dist_to_intersec = [] + edge_number = [] + all_observed_ids = [] + for _, edges in self.scenario.node_mapping: + local_speeds = [] + local_dists_to_intersec = [] + local_edge_numbers = [] + for edge in edges: + observed_ids = \ + self.get_closest_to_intersection(edge, self.num_observed) + all_observed_ids.append(observed_ids) + + # check which edges we have so we can always pad in the right + # positions + local_speeds.extend( + [self.k.vehicle.get_speed(veh_id) / max_speed for veh_id in + observed_ids]) + local_dists_to_intersec.extend([(self.k.scenario.edge_length( + self.k.vehicle.get_edge( + veh_id)) - self.k.vehicle.get_position( + veh_id)) / max_dist for veh_id in observed_ids]) + local_edge_numbers.extend([self._convert_edge( + self.k.vehicle.get_edge(veh_id)) / ( + self.k.scenario.network.num_edges - 1) for veh_id in + observed_ids]) + + if len(observed_ids) < self.num_observed: + diff = self.num_observed - len(observed_ids) + local_speeds.extend([1] * diff) + local_dists_to_intersec.extend([1] * diff) + local_edge_numbers.extend([0] * diff) + + speeds.append(local_speeds) + dist_to_intersec.append(local_dists_to_intersec) + edge_number.append(local_edge_numbers) + + # Edge information + density = [] + velocity_avg = [] + for edge in self.k.scenario.get_edge_list(): + ids = self.k.vehicle.get_ids_by_edge(edge) + if len(ids) > 0: + # TODO(cathywu) Why is there a 5 here? + density += [5 * len(ids) / self.k.scenario.edge_length(edge)] + velocity_avg += [np.mean( + [self.k.vehicle.get_speed(veh_id) for veh_id in + ids]) / max_speed] + else: + density += [0] + velocity_avg += [0] + density = np.array(density) + velocity_avg = np.array(velocity_avg) + self.observed_ids = all_observed_ids + + # Traffic light information + last_change = self.last_change.flatten() + direction = self.direction.flatten() + currently_yellow = self.currently_yellow.flatten() + # This is a catch-all for when the relative_node method returns a -1 + # (when there is no node in the direction sought). We add a last + # item to the lists here, which will serve as a default value. + # TODO(cathywu) are these values reasonable? + last_change = np.append(last_change, [0]) + direction = np.append(direction, [0]) + currently_yellow = np.append(currently_yellow, [1]) + + obs = {} + # TODO(cathywu) allow differentiation between rl and non-rl lights + node_to_edges = self.scenario.node_mapping + for rl_id in self.k.traffic_light.get_ids(): + rl_id_num = int(rl_id.split("center")[ID_IDX]) + local_edges = node_to_edges[rl_id_num][1] + local_edge_numbers = [self.k.scenario.get_edge_list().index(e) + for e in local_edges] + local_id_nums = [rl_id_num, self._get_relative_node(rl_id, "top"), + self._get_relative_node(rl_id, "bottom"), + self._get_relative_node(rl_id, "left"), + self._get_relative_node(rl_id, "right")] + + observation = np.array(np.concatenate( + [speeds[rl_id_num], dist_to_intersec[rl_id_num], + edge_number[rl_id_num], density[local_edge_numbers], + velocity_avg[local_edge_numbers], last_change[local_id_nums], + direction[local_id_nums], currently_yellow[local_id_nums] + ])) + obs.update({rl_id: observation}) + + return obs + + def _apply_rl_actions(self, rl_actions): + """ + See parent class. + + Issues action for each traffic light agent. + """ + for rl_id, rl_action in rl_actions.items(): + i = int(rl_id.split("center")[ID_IDX]) + if self.discrete: + raise NotImplementedError + else: + # convert values less than 0.0 to zero and above to 1. 0's + # indicate that we should not switch the direction + action = rl_action > 0.0 + + if self.currently_yellow[i] == 1: # currently yellow + self.last_change[i] += self.sim_step + # Check if our timer has exceeded the yellow phase, meaning it + # should switch to red + if self.last_change[i] >= self.min_switch_time: + if self.direction[i] == 0: + self.k.traffic_light.set_state( + node_id='center{}'.format(i), state="GrGr") + else: + self.k.traffic_light.set_state( + node_id='center{}'.format(i), state='rGrG') + self.currently_yellow[i] = 0 + else: + if action: + if self.direction[i] == 0: + self.k.traffic_light.set_state( + node_id='center{}'.format(i), state='yryr') + else: + self.k.traffic_light.set_state( + node_id='center{}'.format(i), state='ryry') + self.last_change[i] = 0.0 + self.direction[i] = not self.direction[i] + self.currently_yellow[i] = 1 + + def compute_reward(self, rl_actions, **kwargs): + """See class definition.""" + if rl_actions is None: + return {} + + if self.env_params.evaluate: + rew = -rewards.min_delay_unscaled(self) + else: + rew = -rewards.min_delay_unscaled(self) \ + + rewards.penalize_standstill(self, gain=0.2) + + # each agent receives reward normalized by number of lights + rew /= self.num_traffic_lights + + rews = {} + for rl_id in rl_actions.keys(): + rews[rl_id] = rew + return rews + + def additional_command(self): + """See class definition.""" + # specify observed vehicles + for veh_ids in self.observed_ids: + for veh_id in veh_ids: + self.k.vehicle.set_observed(veh_id) diff --git a/flow/envs/ring/__init__.py b/flow/envs/ring/__init__.py new file mode 100644 index 0000000000..ae058bd162 --- /dev/null +++ b/flow/envs/ring/__init__.py @@ -0,0 +1 @@ +"""Empty init file to ensure documentation for the ring env is created.""" diff --git a/flow/envs/ring/accel.py b/flow/envs/ring/accel.py new file mode 100755 index 0000000000..1718256584 --- /dev/null +++ b/flow/envs/ring/accel.py @@ -0,0 +1,183 @@ +"""Environment for training the acceleration behavior of vehicles in a ring.""" + +from flow.core import rewards +from flow.envs.base import Env + +from gym.spaces.box import Box + +import numpy as np + +ADDITIONAL_ENV_PARAMS = { + # maximum acceleration for autonomous vehicles, in m/s^2 + 'max_accel': 3, + # maximum deceleration for autonomous vehicles, in m/s^2 + 'max_decel': 3, + # desired velocity for all vehicles in the network, in m/s + 'target_velocity': 10, + # specifies whether vehicles are to be sorted by position during a + # simulation step. If set to True, the environment parameter + # self.sorted_ids will return a list of all vehicles sorted in accordance + # with the environment + 'sort_vehicles': False +} + + +class AccelEnv(Env): + """Fully observed acceleration environment. + + This environment used to train autonomous vehicles to improve traffic flows + when acceleration actions are permitted by the rl agent. + + Required from env_params: + + * max_accel: maximum acceleration for autonomous vehicles, in m/s^2 + * max_decel: maximum deceleration for autonomous vehicles, in m/s^2 + * target_velocity: desired velocity for all vehicles in the network, in m/s + * sort_vehicles: specifies whether vehicles are to be sorted by position + during a simulation step. If set to True, the environment parameter + self.sorted_ids will return a list of all vehicles sorted in accordance + with the environment + + States + The state consists of the velocities and absolute position of all + vehicles in the network. This assumes a constant number of vehicles. + + Actions + Actions are a list of acceleration for each rl vehicles, bounded by the + maximum accelerations and decelerations specified in EnvParams. + + Rewards + The reward function is the two-norm of the distance of the speed of the + vehicles in the network from the "target_velocity" term. For a + description of the reward, see: flow.core.rewards.desired_speed + + Termination + A rollout is terminated if the time horizon is reached or if two + vehicles collide into one another. + + Attributes + ---------- + prev_pos : dict + dictionary keeping track of each veh_id's previous position + absolute_position : dict + dictionary keeping track of each veh_id's absolute position + obs_var_labels : list of str + referenced in the visualizer. Tells the visualizer which + metrics to track + """ + + def __init__(self, env_params, sim_params, scenario, simulator='traci'): + for p in ADDITIONAL_ENV_PARAMS.keys(): + if p not in env_params.additional_params: + raise KeyError( + 'Environment parameter \'{}\' not supplied'.format(p)) + + # variables used to sort vehicles by their initial position plus + # distance traveled + self.prev_pos = dict() + self.absolute_position = dict() + + super().__init__(env_params, sim_params, scenario, simulator) + + @property + def action_space(self): + """See class definition.""" + return Box( + low=-abs(self.env_params.additional_params['max_decel']), + high=self.env_params.additional_params['max_accel'], + shape=(self.initial_vehicles.num_rl_vehicles, ), + dtype=np.float32) + + @property + def observation_space(self): + """See class definition.""" + self.obs_var_labels = ['Velocity', 'Absolute_pos'] + return Box( + low=0, + high=1, + shape=(2 * self.initial_vehicles.num_vehicles, ), + dtype=np.float32) + + def _apply_rl_actions(self, rl_actions): + """See class definition.""" + sorted_rl_ids = [ + veh_id for veh_id in self.sorted_ids + if veh_id in self.k.vehicle.get_rl_ids() + ] + self.k.vehicle.apply_acceleration(sorted_rl_ids, rl_actions) + + def compute_reward(self, rl_actions, **kwargs): + """See class definition.""" + if self.env_params.evaluate: + return np.mean(self.k.vehicle.get_speed(self.k.vehicle.get_ids())) + else: + return rewards.desired_velocity(self, fail=kwargs['fail']) + + def get_state(self): + """See class definition.""" + speed = [self.k.vehicle.get_speed(veh_id) / self.k.scenario.max_speed() + for veh_id in self.sorted_ids] + pos = [self.k.vehicle.get_x_by_id(veh_id) / self.k.scenario.length() + for veh_id in self.sorted_ids] + + return np.array(speed + pos) + + def additional_command(self): + """See parent class. + + Define which vehicles are observed for visualization purposes, and + update the sorting of vehicles using the self.sorted_ids variable. + """ + # specify observed vehicles + if self.k.vehicle.num_rl_vehicles > 0: + for veh_id in self.k.vehicle.get_human_ids(): + self.k.vehicle.set_observed(veh_id) + + # update the "absolute_position" variable + for veh_id in self.k.vehicle.get_ids(): + this_pos = self.k.vehicle.get_x_by_id(veh_id) + + if this_pos == -1001: + # in case the vehicle isn't in the network + self.absolute_position[veh_id] = -1001 + else: + change = this_pos - self.prev_pos.get(veh_id, this_pos) + self.absolute_position[veh_id] = \ + (self.absolute_position.get(veh_id, this_pos) + change) \ + % self.k.scenario.length() + self.prev_pos[veh_id] = this_pos + + @property + def sorted_ids(self): + """Sort the vehicle ids of vehicles in the network by position. + + This environment does this by sorting vehicles by their absolute + position, defined as their initial position plus distance traveled. + + Returns + ------- + list of str + a list of all vehicle IDs sorted by position + """ + if self.env_params.additional_params['sort_vehicles']: + return sorted(self.k.vehicle.get_ids(), key=self._get_abs_position) + else: + return self.k.vehicle.get_ids() + + def _get_abs_position(self, veh_id): + """Return the absolute position of a vehicle.""" + return self.absolute_position.get(veh_id, -1001) + + def reset(self): + """See parent class. + + This also includes updating the initial absolute position and previous + position. + """ + obs = super().reset() + + for veh_id in self.k.vehicle.get_ids(): + self.absolute_position[veh_id] = self.k.vehicle.get_x_by_id(veh_id) + self.prev_pos[veh_id] = self.k.vehicle.get_x_by_id(veh_id) + + return obs diff --git a/flow/envs/ring/lane_change_accel.py b/flow/envs/ring/lane_change_accel.py new file mode 100755 index 0000000000..b9e4bed918 --- /dev/null +++ b/flow/envs/ring/lane_change_accel.py @@ -0,0 +1,269 @@ +"""Environments that can train both lane change and acceleration behaviors.""" + +from flow.envs.ring.accel import AccelEnv +from flow.core import rewards + +from gym.spaces.box import Box +import numpy as np + +ADDITIONAL_ENV_PARAMS = { + # maximum acceleration for autonomous vehicles, in m/s^2 + "max_accel": 3, + # maximum deceleration for autonomous vehicles, in m/s^2 + "max_decel": 3, + # lane change duration for autonomous vehicles, in s. Autonomous vehicles + # reject new lane changing commands for this duration after successfully + # changing lanes. + "lane_change_duration": 5, + # desired velocity for all vehicles in the network, in m/s + "target_velocity": 10, + # specifies whether vehicles are to be sorted by position during a + # simulation step. If set to True, the environment parameter + # self.sorted_ids will return a list of all vehicles sorted in accordance + # with the environment + 'sort_vehicles': False +} + + +class LaneChangeAccelEnv(AccelEnv): + """Fully observable lane change and acceleration environment. + + This environment is used to train autonomous vehicles to improve traffic + flows when lane-change and acceleration actions are permitted by the rl + agent. + + Required from env_params: + + * max_accel: maximum acceleration for autonomous vehicles, in m/s^2 + * max_decel: maximum deceleration for autonomous vehicles, in m/s^2 + * lane_change_duration: lane change duration for autonomous vehicles, in s + * target_velocity: desired velocity for all vehicles in the network, in m/s + * sort_vehicles: specifies whether vehicles are to be sorted by position + during a simulation step. If set to True, the environment parameter + self.sorted_ids will return a list of all vehicles sorted in accordance + with the environment + + States + The state consists of the velocities, absolute position, and lane index + of all vehicles in the network. This assumes a constant number of + vehicles. + + Actions + Actions consist of: + + * a (continuous) acceleration from -abs(max_decel) to max_accel, + specified in env_params + * a (continuous) lane-change action from -1 to 1, used to determine the + lateral direction the vehicle will take. + + Lane change actions are performed only if the vehicle has not changed + lanes for the lane change duration specified in env_params. + + Rewards + The reward function is the two-norm of the distance of the speed of the + vehicles in the network from a desired speed, combined with a penalty + to discourage excess lane changes by the rl vehicle. + + Termination + A rollout is terminated if the time horizon is reached or if two + vehicles collide into one another. + """ + + def __init__(self, env_params, sim_params, scenario, simulator='traci'): + for p in ADDITIONAL_ENV_PARAMS.keys(): + if p not in env_params.additional_params: + raise KeyError( + 'Environment parameter "{}" not supplied'.format(p)) + + super().__init__(env_params, sim_params, scenario, simulator) + + @property + def action_space(self): + """See class definition.""" + max_decel = self.env_params.additional_params["max_decel"] + max_accel = self.env_params.additional_params["max_accel"] + + lb = [-abs(max_decel), -1] * self.initial_vehicles.num_rl_vehicles + ub = [max_accel, 1] * self.initial_vehicles.num_rl_vehicles + + return Box(np.array(lb), np.array(ub), dtype=np.float32) + + @property + def observation_space(self): + """See class definition.""" + return Box( + low=0, + high=1, + shape=(3 * self.initial_vehicles.num_vehicles, ), + dtype=np.float32) + + def compute_reward(self, rl_actions, **kwargs): + """See class definition.""" + # compute the system-level performance of vehicles from a velocity + # perspective + reward = rewards.desired_velocity(self, fail=kwargs["fail"]) + + # punish excessive lane changes by reducing the reward by a set value + # every time an rl car changes lanes (10% of max reward) + for veh_id in self.k.vehicle.get_rl_ids(): + if self.k.vehicle.get_last_lc(veh_id) == self.time_counter: + reward -= 0.1 + + return reward + + def get_state(self): + """See class definition.""" + # normalizers + max_speed = self.k.scenario.max_speed() + length = self.k.scenario.length() + max_lanes = max( + self.k.scenario.num_lanes(edge) + for edge in self.k.scenario.get_edge_list()) + + speed = [self.k.vehicle.get_speed(veh_id) / max_speed + for veh_id in self.sorted_ids] + pos = [self.k.vehicle.get_x_by_id(veh_id) / length + for veh_id in self.sorted_ids] + lane = [self.k.vehicle.get_lane(veh_id) / max_lanes + for veh_id in self.sorted_ids] + + return np.array(speed + pos + lane) + + def _apply_rl_actions(self, actions): + """See class definition.""" + acceleration = actions[::2] + direction = actions[1::2] + + # re-arrange actions according to mapping in observation space + sorted_rl_ids = [ + veh_id for veh_id in self.sorted_ids + if veh_id in self.k.vehicle.get_rl_ids() + ] + + # represents vehicles that are allowed to change lanes + non_lane_changing_veh = \ + [self.time_counter <= + self.env_params.additional_params["lane_change_duration"] + + self.k.vehicle.get_last_lc(veh_id) + for veh_id in sorted_rl_ids] + # vehicle that are not allowed to change have their directions set to 0 + direction[non_lane_changing_veh] = \ + np.array([0] * sum(non_lane_changing_veh)) + + self.k.vehicle.apply_acceleration(sorted_rl_ids, acc=acceleration) + self.k.vehicle.apply_lane_change(sorted_rl_ids, direction=direction) + + def additional_command(self): + """Define which vehicles are observed for visualization purposes.""" + # specify observed vehicles + if self.k.vehicle.num_rl_vehicles > 0: + for veh_id in self.k.vehicle.get_human_ids(): + self.k.vehicle.set_observed(veh_id) + + +class LaneChangeAccelPOEnv(LaneChangeAccelEnv): + """POMDP version of LaneChangeAccelEnv. + + Required from env_params: + + * max_accel: maximum acceleration for autonomous vehicles, in m/s^2 + * max_decel: maximum deceleration for autonomous vehicles, in m/s^2 + * lane_change_duration: lane change duration for autonomous vehicles, in s + * target_velocity: desired velocity for all vehicles in the network, in m/s + + States + States are a list of rl vehicles speeds, as well as the speeds and + bumper-to-bumper headways between the rl vehicles and their + leaders/followers in all lanes. There is no assumption on the number of + vehicles in the network, so long as the number of rl vehicles is + static. + + Actions + See parent class. + + Rewards + See parent class. + + Termination + See parent class. + + Attributes + ---------- + num_lanes : int + maximum number of lanes on any edge in the network + visible : list of str + lists of visible vehicles, used for visualization purposes + """ + + def __init__(self, env_params, sim_params, scenario, simulator='traci'): + super().__init__(env_params, sim_params, scenario, simulator) + + self.num_lanes = max(self.k.scenario.num_lanes(edge) + for edge in self.k.scenario.get_edge_list()) + self.visible = [] + + @property + def observation_space(self): + """See class definition.""" + return Box( + low=0, + high=1, + shape=(4 * self.initial_vehicles.num_rl_vehicles * + self.num_lanes + self.initial_vehicles.num_rl_vehicles, ), + dtype=np.float32) + + def get_state(self): + """See class definition.""" + obs = [ + 0 + for _ in range(4 * self.k.vehicle.num_rl_vehicles * self.num_lanes) + ] + + self.visible = [] + for i, rl_id in enumerate(self.k.vehicle.get_rl_ids()): + # normalizers + max_length = self.k.scenario.length() + max_speed = self.k.scenario.max_speed() + + # set to 1000 since the absence of a vehicle implies a large + # headway + headway = [1] * self.num_lanes + tailway = [1] * self.num_lanes + vel_in_front = [0] * self.num_lanes + vel_behind = [0] * self.num_lanes + + lane_leaders = self.k.vehicle.get_lane_leaders(rl_id) + lane_followers = self.k.vehicle.get_lane_followers(rl_id) + lane_headways = self.k.vehicle.get_lane_headways(rl_id) + lane_tailways = self.k.vehicle.get_lane_tailways(rl_id) + headway[0:len(lane_headways)] = lane_headways + tailway[0:len(lane_tailways)] = lane_tailways + + for j, lane_leader in enumerate(lane_leaders): + if lane_leader != '': + lane_headways[j] /= max_length + vel_in_front[j] = self.k.vehicle.get_speed(lane_leader) \ + / max_speed + self.visible.extend([lane_leader]) + for j, lane_follower in enumerate(lane_followers): + if lane_follower != '': + lane_headways[j] /= max_length + vel_behind[j] = self.k.vehicle.get_speed(lane_follower) \ + / max_speed + self.visible.extend([lane_follower]) + + # add the headways, tailways, and speed for all lane leaders + # and followers + obs[4*self.num_lanes*i:4*self.num_lanes*(i+1)] = \ + np.concatenate((headway, tailway, vel_in_front, vel_behind)) + + # add the speed for the ego rl vehicle + obs.append(self.k.vehicle.get_speed(rl_id)) + + return np.array(obs) + + def additional_command(self): + """Define which vehicles are observed for visualization purposes.""" + # specify observed vehicles + for veh_id in self.visible: + self.k.vehicle.set_observed(veh_id) diff --git a/flow/envs/ring/wave_attenuation.py b/flow/envs/ring/wave_attenuation.py new file mode 100644 index 0000000000..ff05d6408a --- /dev/null +++ b/flow/envs/ring/wave_attenuation.py @@ -0,0 +1,281 @@ +""" +Environment used to train a stop-and-go dissipating controller. + +This is the environment that was used in: + +C. Wu, A. Kreidieh, K. Parvate, E. Vinitsky, A. Bayen, "Flow: Architecture and +Benchmarking for Reinforcement Learning in Traffic Control," CoRR, vol. +abs/1710.05465, 2017. [Online]. Available: https://arxiv.org/abs/1710.05465 +""" + +from flow.core.params import InitialConfig +from flow.core.params import NetParams +from flow.envs.base import Env + +from gym.spaces.box import Box + +from copy import deepcopy +import numpy as np +import random +from scipy.optimize import fsolve + +ADDITIONAL_ENV_PARAMS = { + # maximum acceleration of autonomous vehicles + 'max_accel': 1, + # maximum deceleration of autonomous vehicles + 'max_decel': 1, + # bounds on the ranges of ring road lengths the autonomous vehicle is + # trained on + 'ring_length': [220, 270], +} + + +def v_eq_max_function(v, *args): + """Return the error between the desired and actual equivalent gap.""" + num_vehicles, length = args + + # maximum gap in the presence of one rl vehicle + s_eq_max = (length - num_vehicles * 5) / (num_vehicles - 1) + + v0 = 30 + s0 = 2 + tau = 1 + gamma = 4 + + error = s_eq_max - (s0 + v * tau) * (1 - (v / v0) ** gamma) ** -0.5 + + return error + + +class WaveAttenuationEnv(Env): + """Fully observable wave attenuation environment. + + This environment is used to train autonomous vehicles to attenuate the + formation and propagation of waves in a variable density ring road. + + Required from env_params: + + * max_accel: maximum acceleration of autonomous vehicles + * max_decel: maximum deceleration of autonomous vehicles + * ring_length: bounds on the ranges of ring road lengths the autonomous + vehicle is trained on. If set to None, the environment sticks to the ring + road specified in the original scenario definition. + + States + The state consists of the velocities and absolute position of all + vehicles in the network. This assumes a constant number of vehicles. + + Actions + Actions are a list of acceleration for each rl vehicles, bounded by the + maximum accelerations and decelerations specified in EnvParams. + + Rewards + The reward function rewards high average speeds from all vehicles in + the network, and penalizes accelerations by the rl vehicle. + + Termination + A rollout is terminated if the time horizon is reached or if two + vehicles collide into one another. + """ + + def __init__(self, env_params, sim_params, scenario, simulator='traci'): + for p in ADDITIONAL_ENV_PARAMS.keys(): + if p not in env_params.additional_params: + raise KeyError( + 'Environment parameter \'{}\' not supplied'.format(p)) + + super().__init__(env_params, sim_params, scenario, simulator) + + @property + def action_space(self): + """See class definition.""" + return Box( + low=-np.abs(self.env_params.additional_params['max_decel']), + high=self.env_params.additional_params['max_accel'], + shape=(self.initial_vehicles.num_rl_vehicles, ), + dtype=np.float32) + + @property + def observation_space(self): + """See class definition.""" + self.obs_var_labels = ["Velocity", "Absolute_pos"] + return Box( + low=0, + high=1, + shape=(2 * self.initial_vehicles.num_vehicles, ), + dtype=np.float32) + + def _apply_rl_actions(self, rl_actions): + """See class definition.""" + self.k.vehicle.apply_acceleration( + self.k.vehicle.get_rl_ids(), rl_actions) + + def compute_reward(self, rl_actions, **kwargs): + """See class definition.""" + # in the warmup steps + if rl_actions is None: + return 0 + + vel = np.array([ + self.k.vehicle.get_speed(veh_id) + for veh_id in self.k.vehicle.get_ids() + ]) + + if any(vel < -100) or kwargs['fail']: + return 0. + + # reward average velocity + eta_2 = 4. + reward = eta_2 * np.mean(vel) / 20 + + # punish accelerations (should lead to reduced stop-and-go waves) + eta = 4 # 0.25 + mean_actions = np.mean(np.abs(np.array(rl_actions))) + accel_threshold = 0 + + if mean_actions > accel_threshold: + reward += eta * (accel_threshold - mean_actions) + + return float(reward) + + def get_state(self): + """See class definition.""" + speed = [self.k.vehicle.get_speed(veh_id) / self.k.scenario.max_speed() + for veh_id in self.k.vehicle.get_ids()] + pos = [self.k.vehicle.get_x_by_id(veh_id) / self.k.scenario.length() + for veh_id in self.k.vehicle.get_ids()] + + return np.array(speed + pos) + + def additional_command(self): + """Define which vehicles are observed for visualization purposes.""" + # specify observed vehicles + if self.k.vehicle.num_rl_vehicles > 0: + for veh_id in self.k.vehicle.get_human_ids(): + self.k.vehicle.set_observed(veh_id) + + def reset(self): + """See parent class. + + The sumo instance is reset with a new ring length, and a number of + steps are performed with the rl vehicle acting as a human vehicle. + """ + # skip if ring length is None + if self.env_params.additional_params['ring_length'] is None: + return super().reset() + + # reset the step counter + self.step_counter = 0 + + # update the scenario + initial_config = InitialConfig(bunching=50, min_gap=0) + length = random.randint( + self.env_params.additional_params['ring_length'][0], + self.env_params.additional_params['ring_length'][1]) + additional_net_params = { + 'length': + length, + 'lanes': + self.net_params.additional_params['lanes'], + 'speed_limit': + self.net_params.additional_params['speed_limit'], + 'resolution': + self.net_params.additional_params['resolution'] + } + net_params = NetParams(additional_params=additional_net_params) + + self.scenario = self.scenario.__class__( + self.scenario.orig_name, self.scenario.vehicles, + net_params, initial_config) + self.k.vehicle = deepcopy(self.initial_vehicles) + self.k.vehicle.kernel_api = self.k.kernel_api + self.k.vehicle.master_kernel = self.k + + # solve for the velocity upper bound of the ring + v_guess = 4 + v_eq_max = fsolve(v_eq_max_function, np.array(v_guess), + args=(len(self.initial_ids), length))[0] + + print('\n-----------------------') + print('ring length:', net_params.additional_params['length']) + print('v_max:', v_eq_max) + print('-----------------------') + + # restart the sumo instance + self.restart_simulation( + sim_params=self.sim_params, + render=self.sim_params.render) + + # perform the generic reset function + observation = super().reset() + + # reset the timer to zero + self.time_counter = 0 + + return observation + + +class WaveAttenuationPOEnv(WaveAttenuationEnv): + """POMDP version of WaveAttenuationEnv. + + Note that this environment only works when there is one autonomous vehicle + on the network. + + Required from env_params: + + * max_accel: maximum acceleration of autonomous vehicles + * max_decel: maximum deceleration of autonomous vehicles + * ring_length: bounds on the ranges of ring road lengths the autonomous + vehicle is trained on + + States + The state consists of the speed and headway of the ego vehicle, as well + as the difference in speed between the ego vehicle and its leader. + There is no assumption on the number of vehicles in the network. + + Actions + See parent class + + Rewards + See parent class + + Termination + See parent class + + """ + + @property + def observation_space(self): + """See class definition.""" + return Box(low=-float('inf'), high=float('inf'), + shape=(3, ), dtype=np.float32) + + def get_state(self): + """See class definition.""" + rl_id = self.k.vehicle.get_rl_ids()[0] + lead_id = self.k.vehicle.get_leader(rl_id) or rl_id + + # normalizers + max_speed = 15. + if self.env_params.additional_params['ring_length'] is not None: + max_length = self.env_params.additional_params['ring_length'][1] + else: + max_length = self.k.scenario.length() + + observation = np.array([ + self.k.vehicle.get_speed(rl_id) / max_speed, + (self.k.vehicle.get_speed(lead_id) - + self.k.vehicle.get_speed(rl_id)) / max_speed, + (self.k.vehicle.get_x_by_id(lead_id) - + self.k.vehicle.get_x_by_id(rl_id)) % self.k.scenario.length() + / max_length + ]) + + return observation + + def additional_command(self): + """Define which vehicles are observed for visualization purposes.""" + # specify observed vehicles + rl_id = self.k.vehicle.get_rl_ids()[0] + lead_id = self.k.vehicle.get_leader(rl_id) or rl_id + self.k.vehicle.set_observed(lead_id) diff --git a/flow/envs/test.py b/flow/envs/test.py index e653eae8ac..813e4621ea 100644 --- a/flow/envs/test.py +++ b/flow/envs/test.py @@ -1,6 +1,6 @@ """Test environment used to run simulations in the absence of autonomy.""" -from flow.envs.base_env import Env +from flow.envs.base import Env from gym.spaces.box import Box import numpy as np diff --git a/flow/envs/traffic_light_grid.py b/flow/envs/traffic_light_grid.py new file mode 100644 index 0000000000..55753de684 --- /dev/null +++ b/flow/envs/traffic_light_grid.py @@ -0,0 +1,740 @@ +"""Environments for scenarios with traffic lights. + +These environments are used to train traffic lights to regulate traffic flow +through an n x m traffic light grid. +""" + +import numpy as np +import re + +from gym.spaces.box import Box +from gym.spaces.discrete import Discrete +from gym.spaces import Tuple + +from flow.core import rewards +from flow.envs.base import Env + +ADDITIONAL_ENV_PARAMS = { + # minimum switch time for each traffic light (in seconds) + "switch_time": 2.0, + # whether the traffic lights should be actuated by sumo or RL + # options are "controlled" and "actuated" + "tl_type": "controlled", + # determines whether the action space is meant to be discrete or continuous + "discrete": False, +} + +ADDITIONAL_PO_ENV_PARAMS = { + # num of vehicles the agent can observe on each incoming edge + "num_observed": 2, + # velocity to use in reward functions + "target_velocity": 30, +} + + +class TrafficLightGridEnv(Env): + """Environment used to train traffic lights. + + Required from env_params: + + * switch_time: minimum time a light must be constant before + it switches (in seconds). + Earlier RL commands are ignored. + * tl_type: whether the traffic lights should be actuated by sumo or RL, + options are respectively "actuated" and "controlled" + * discrete: determines whether the action space is meant to be discrete or + continuous + + States + An observation is the distance of each vehicle to its intersection, a + number uniquely identifying which edge the vehicle is on, and the speed + of the vehicle. + + Actions + The action space consist of a list of float variables ranging from 0-1 + specifying whether a traffic light is supposed to switch or not. The + actions are sent to the traffic light in the grid from left to right + and then top to bottom. + + Rewards + The reward is the negative per vehicle delay minus a penalty for + switching traffic lights + + Termination + A rollout is terminated once the time horizon is reached. + + Additional + Vehicles are rerouted to the start of their original routes once they + reach the end of the network in order to ensure a constant number of + vehicles. + + Attributes + ---------- + grid_array : dict + Array containing information on the traffic light grid, such as the + length of roads, row_num, col_num, number of initial cars + rows : int + Number of rows in this traffic light grid scenario + cols : int + Number of columns in this traffic light grid scenario + num_traffic_lights : int + Number of intersection in this traffic light grid scenario + tl_type : str + Type of traffic lights, either 'actuated' or 'static' + steps : int + Horizon of this experiment, see EnvParams.horion + obs_var_labels : dict + Referenced in the visualizer. Tells the visualizer which + metrics to track + node_mapping : dict + Dictionary mapping intersections / nodes (nomenclature is used + interchangeably here) to the edges that are leading to said + intersection / node + last_change : np array [num_traffic_lights]x1 np array + Multi-dimensional array keeping track, in timesteps, of how much time + has passed since the last change to yellow for each traffic light + direction : np array [num_traffic_lights]x1 np array + Multi-dimensional array keeping track of which direction in traffic + light is flowing. 0 indicates flow from top to bottom, and + 1 indicates flow from left to right + currently_yellow : np array [num_traffic_lights]x1 np array + Multi-dimensional array keeping track of whether or not each traffic + light is currently yellow. 1 if yellow, 0 if not + min_switch_time : np array [num_traffic_lights]x1 np array + The minimum time in timesteps that a light can be yellow. Serves + as a lower bound + discrete : bool + Indicates whether or not the action space is discrete. See below for + more information: + https://github.com/openai/gym/blob/master/gym/spaces/discrete.py + """ + + def __init__(self, env_params, sim_params, scenario, simulator='traci'): + + for p in ADDITIONAL_ENV_PARAMS.keys(): + if p not in env_params.additional_params: + raise KeyError( + 'Environment parameter "{}" not supplied'.format(p)) + + self.grid_array = scenario.net_params.additional_params["grid_array"] + self.rows = self.grid_array["row_num"] + self.cols = self.grid_array["col_num"] + # self.num_observed = self.grid_array.get("num_observed", 3) + self.num_traffic_lights = self.rows * self.cols + self.tl_type = env_params.additional_params.get('tl_type') + + super().__init__(env_params, sim_params, scenario, simulator) + + # Saving env variables for plotting + self.steps = env_params.horizon + self.obs_var_labels = { + 'edges': np.zeros((self.steps, self.k.vehicle.num_vehicles)), + 'velocities': np.zeros((self.steps, self.k.vehicle.num_vehicles)), + 'positions': np.zeros((self.steps, self.k.vehicle.num_vehicles)) + } + + # Keeps track of the last time the traffic lights in an intersection + # were allowed to change (the last time the lights were allowed to + # change from a red-green state to a red-yellow state.) + self.last_change = np.zeros((self.rows * self.cols, 1)) + # Keeps track of the direction of the intersection (the direction that + # is currently being allowed to flow. 0 indicates flow from top to + # bottom, and 1 indicates flow from left to right.) + self.direction = np.zeros((self.rows * self.cols, 1)) + # Value of 1 indicates that the intersection is in a red-yellow state. + # value 0 indicates that the intersection is in a red-green state. + self.currently_yellow = np.zeros((self.rows * self.cols, 1)) + + # when this hits min_switch_time we change from yellow to red + # the second column indicates the direction that is currently being + # allowed to flow. 0 is flowing top to bottom, 1 is left to right + # For third column, 0 signifies yellow and 1 green or red + self.min_switch_time = env_params.additional_params["switch_time"] + + if self.tl_type != "actuated": + for i in range(self.rows * self.cols): + self.k.traffic_light.set_state( + node_id='center' + str(i), state="GrGr") + self.currently_yellow[i] = 0 + + # # Additional Information for Plotting + # self.edge_mapping = {"top": [], "bot": [], "right": [], "left": []} + # for i, veh_id in enumerate(self.k.vehicle.get_ids()): + # edge = self.k.vehicle.get_edge(veh_id) + # for key in self.edge_mapping: + # if key in edge: + # self.edge_mapping[key].append(i) + # break + + # check whether the action space is meant to be discrete or continuous + self.discrete = env_params.additional_params.get("discrete", False) + + @property + def action_space(self): + """See class definition.""" + if self.discrete: + return Discrete(2 ** self.num_traffic_lights) + else: + return Box( + low=-1, + high=1, + shape=(self.num_traffic_lights,), + dtype=np.float32) + + @property + def observation_space(self): + """See class definition.""" + speed = Box( + low=0, + high=1, + shape=(self.initial_vehicles.num_vehicles,), + dtype=np.float32) + dist_to_intersec = Box( + low=0., + high=np.inf, + shape=(self.initial_vehicles.num_vehicles,), + dtype=np.float32) + edge_num = Box( + low=0., + high=1, + shape=(self.initial_vehicles.num_vehicles,), + dtype=np.float32) + traffic_lights = Box( + low=0., + high=1, + shape=(3 * self.rows * self.cols,), + dtype=np.float32) + return Tuple((speed, dist_to_intersec, edge_num, traffic_lights)) + + def get_state(self): + """See class definition.""" + # compute the normalizers + grid_array = self.net_params.additional_params["grid_array"] + max_dist = max(grid_array["short_length"], + grid_array["long_length"], + grid_array["inner_length"]) + + # get the state arrays + speeds = [ + self.k.vehicle.get_speed(veh_id) / self.k.scenario.max_speed() + for veh_id in self.k.vehicle.get_ids() + ] + dist_to_intersec = [ + self.get_distance_to_intersection(veh_id) / max_dist + for veh_id in self.k.vehicle.get_ids() + ] + edges = [ + self._convert_edge(self.k.vehicle.get_edge(veh_id)) / + (self.k.scenario.network.num_edges - 1) + for veh_id in self.k.vehicle.get_ids() + ] + + state = [ + speeds, dist_to_intersec, edges, + self.last_change.flatten().tolist(), + self.direction.flatten().tolist(), + self.currently_yellow.flatten().tolist() + ] + return np.array(state) + + def _apply_rl_actions(self, rl_actions): + """See class definition.""" + # check if the action space is discrete + if self.discrete: + # convert single value to list of 0's and 1's + rl_mask = [int(x) for x in list('{0:0b}'.format(rl_actions))] + rl_mask = [0] * (self.num_traffic_lights - len(rl_mask)) + rl_mask + else: + # convert values less than 0 to zero and above 0 to 1. 0 indicates + # that should not switch the direction, and 1 indicates that switch + # should happen + rl_mask = rl_actions > 0.0 + + for i, action in enumerate(rl_mask): + if self.currently_yellow[i] == 1: # currently yellow + self.last_change[i] += self.sim_step + # Check if our timer has exceeded the yellow phase, meaning it + # should switch to red + if self.last_change[i] >= self.min_switch_time: + if self.direction[i] == 0: + self.k.traffic_light.set_state( + node_id='center{}'.format(i), + state="GrGr") + else: + self.k.traffic_light.set_state( + node_id='center{}'.format(i), + state='rGrG') + self.currently_yellow[i] = 0 + else: + if action: + if self.direction[i] == 0: + self.k.traffic_light.set_state( + node_id='center{}'.format(i), + state='yryr') + else: + self.k.traffic_light.set_state( + node_id='center{}'.format(i), + state='ryry') + self.last_change[i] = 0.0 + self.direction[i] = not self.direction[i] + self.currently_yellow[i] = 1 + + def compute_reward(self, rl_actions, **kwargs): + """See class definition.""" + return - rewards.min_delay_unscaled(self) \ + - rewards.boolean_action_penalty(rl_actions >= 0.5, gain=1.0) + + # =============================== + # ============ UTILS ============ + # =============================== + + def get_distance_to_intersection(self, veh_ids): + """Determine the distance from a vehicle to its next intersection. + + Parameters + ---------- + veh_ids : str or str list + vehicle(s) identifier(s) + + Returns + ------- + float (or float list) + distance to closest intersection + """ + if isinstance(veh_ids, list): + return [self.get_distance_to_intersection(veh_id) + for veh_id in veh_ids] + return self.find_intersection_dist(veh_ids) + + def find_intersection_dist(self, veh_id): + """Return distance from intersection. + + Return the distance from the vehicle's current position to the position + of the node it is heading toward. + """ + edge_id = self.k.vehicle.get_edge(veh_id) + # FIXME this might not be the best way of handling this + if edge_id == "": + return -10 + if 'center' in edge_id: + return 0 + edge_len = self.k.scenario.edge_length(edge_id) + relative_pos = self.k.vehicle.get_position(veh_id) + dist = edge_len - relative_pos + return dist + + def _convert_edge(self, edges): + """Convert the string edge to a number. + + Start at the bottom left vertical edge and going right and then up, so + the bottom left vertical edge is zero, the right edge beside it is 1. + + The numbers are assigned along the lowest column, then the lowest row, + then the second lowest column, etc. Left goes before right, top goes + before bottom. + + The values are zero indexed. + + Parameters + ---------- + edges : list of str or str + name of the edge(s) + + Returns + ------- + list of int or int + a number uniquely identifying each edge + """ + if isinstance(edges, list): + return [self._split_edge(edge) for edge in edges] + else: + return self._split_edge(edges) + + def _split_edge(self, edge): + """Act as utility function for convert_edge.""" + if edge: + if edge[0] == ":": # center + center_index = int(edge.split("center")[1][0]) + base = ((self.cols + 1) * self.rows * 2) \ + + ((self.rows + 1) * self.cols * 2) + return base + center_index + 1 + else: + pattern = re.compile(r"[a-zA-Z]+") + edge_type = pattern.match(edge).group() + edge = edge.split(edge_type)[1].split('_') + row_index, col_index = [int(x) for x in edge] + if edge_type in ['bot', 'top']: + rows_below = 2 * (self.cols + 1) * row_index + cols_below = 2 * (self.cols * (row_index + 1)) + edge_num = rows_below + cols_below + 2 * col_index + 1 + return edge_num if edge_type == 'bot' else edge_num + 1 + if edge_type in ['left', 'right']: + rows_below = 2 * (self.cols + 1) * row_index + cols_below = 2 * (self.cols * row_index) + edge_num = rows_below + cols_below + 2 * col_index + 1 + return edge_num if edge_type == 'left' else edge_num + 1 + else: + return 0 + + def _get_relative_node(self, agent_id, direction): + """Yield node number of traffic light agent in a given direction. + + For example, the nodes in a traffic light grid with 2 rows and 3 + columns are indexed as follows: + + | | | + --- 3 --- 4 --- 5 --- + | | | + --- 0 --- 1 --- 2 --- + | | | + + See flow.scenarios.traffic_light_grid for more information. + + Example of function usage: + - Seeking the "top" direction to ":center0" would return 3. + - Seeking the "bottom" direction to ":center0" would return -1. + + :param agent_id: agent id of the form ":center#" + :param direction: top, bottom, left, right + :return: node number + """ + ID_IDX = 1 + agent_id_num = int(agent_id.split("center")[ID_IDX]) + if direction == "top": + node = agent_id_num + self.cols + if node >= self.cols * self.rows: + node = -1 + elif direction == "bottom": + node = agent_id_num - self.cols + if node < 0: + node = -1 + elif direction == "left": + if agent_id_num % self.cols == 0: + node = -1 + else: + node = agent_id_num - 1 + elif direction == "right": + if agent_id_num % self.cols == self.cols - 1: + node = -1 + else: + node = agent_id_num + 1 + else: + raise NotImplementedError + + return node + + def additional_command(self): + """See parent class. + + Used to insert vehicles that are on the exit edge and place them + back on their entrance edge. + """ + for veh_id in self.k.vehicle.get_ids(): + self._reroute_if_final_edge(veh_id) + + def _reroute_if_final_edge(self, veh_id): + """Reroute vehicle associated with veh_id. + + Checks if an edge is the final edge. If it is return the route it + should start off at. + """ + edge = self.k.vehicle.get_edge(veh_id) + if edge == "": + return + if edge[0] == ":": # center edge + return + pattern = re.compile(r"[a-zA-Z]+") + edge_type = pattern.match(edge).group() + edge = edge.split(edge_type)[1].split('_') + row_index, col_index = [int(x) for x in edge] + + # find the route that we're going to place the vehicle on if we are + # going to remove it + route_id = None + if edge_type == 'bot' and col_index == self.cols: + route_id = "bot{}_0".format(row_index) + elif edge_type == 'top' and col_index == 0: + route_id = "top{}_{}".format(row_index, self.cols) + elif edge_type == 'left' and row_index == 0: + route_id = "left{}_{}".format(self.rows, col_index) + elif edge_type == 'right' and row_index == self.rows: + route_id = "right0_{}".format(col_index) + + if route_id is not None: + type_id = self.k.vehicle.get_type(veh_id) + lane_index = self.k.vehicle.get_lane(veh_id) + # remove the vehicle + self.k.vehicle.remove(veh_id) + # reintroduce it at the start of the network + self.k.vehicle.add( + veh_id=veh_id, + edge=route_id, + type_id=str(type_id), + lane=str(lane_index), + pos="0", + speed="max") + + def get_closest_to_intersection(self, edges, num_closest, padding=False): + """Return the IDs of the vehicles that are closest to an intersection. + + For each edge in edges, return the IDs (veh_id) of the num_closest + vehicles in edge that are closest to an intersection (the intersection + they are heading towards). + + This function performs no check on whether or not edges are going + towards an intersection or not, it just gets the vehicles that are + closest to the end of their edges. + + If there are less than num_closest vehicles on an edge, the function + performs padding by adding empty strings "" instead of vehicle ids if + the padding parameter is set to True. + + Parameters + ---------- + edges : str | str list + ID of an edge or list of edge IDs. + num_closest : int (> 0) + Number of vehicles to consider on each edge. + padding : bool (default False) + If there are less than num_closest vehicles on an edge, perform + padding by adding empty strings "" instead of vehicle ids if the + padding parameter is set to True (note: leaving padding to False + while passing a list of several edges as parameter can lead to + information loss since you will not know which edge, if any, + contains less than num_closest vehicles). + + Usage + ----- + For example, consider the following network, composed of 4 edges + whose ids are "edge0", "edge1", "edge2" and "edge3", the numbers + being vehicles all headed towards intersection x. The ID of the vehicle + with number n is "veh{n}" (edge "veh0", "veh1"...). + + edge1 + | | + | 7 | + | 8 | + -------------| |------------- + edge0 1 2 3 4 5 6 x edge2 + -------------| |------------- + | 9 | + | 10| + | 11| + edge3 + + And consider the following example calls on the previous network: + + >>> get_closest_to_intersection("edge0", 4) + ["veh6", "veh5", "veh4", "veh3"] + + >>> get_closest_to_intersection("edge0", 8) + ["veh6", "veh5", "veh4", "veh3", "veh2", "veh1"] + + >>> get_closest_to_intersection("edge0", 8, padding=True) + ["veh6", "veh5", "veh4", "veh3", "veh2", "veh1", "", ""] + + >>> get_closest_to_intersection(["edge0", "edge1", "edge2", "edge3"], + 3, padding=True) + ["veh6", "veh5", "veh4", "veh8", "veh7", "", "", "", "", "veh9", + "veh10", "veh11"] + + Returns + ------- + str list + If n is the number of edges given as parameters, then the returned + list contains n * num_closest vehicle IDs. + + Raises + ------ + ValueError + if num_closest <= 0 + """ + if num_closest <= 0: + raise ValueError("Function get_closest_to_intersection called with" + "parameter num_closest={}, but num_closest should" + "be positive".format(num_closest)) + + if isinstance(edges, list): + ids = [self.get_closest_to_intersection(edge, num_closest) + for edge in edges] + # flatten the list and return it + return [veh_id for sublist in ids for veh_id in sublist] + + # get the ids of all the vehicles on the edge 'edges' ordered by + # increasing distance to end of edge (intersection) + veh_ids_ordered = sorted(self.k.vehicle.get_ids_by_edge(edges), + key=self.get_distance_to_intersection) + + # return the ids of the num_closest vehicles closest to the + # intersection, potentially with ""-padding. + pad_lst = [""] * (num_closest - len(veh_ids_ordered)) + return veh_ids_ordered[:num_closest] + (pad_lst if padding else []) + + +class TrafficLightGridPOEnv(TrafficLightGridEnv): + """Environment used to train traffic lights. + + Required from env_params: + + * switch_time: minimum switch time for each traffic light (in seconds). + Earlier RL commands are ignored. + * num_observed: number of vehicles nearest each intersection that is + observed in the state space; defaults to 2 + + States + An observation is the number of observed vehicles in each intersection + closest to the traffic lights, a number uniquely identifying which + edge the vehicle is on, and the speed of the vehicle. + + Actions + The action space consist of a list of float variables ranging from 0-1 + specifying whether a traffic light is supposed to switch or not. The + actions are sent to the traffic light in the grid from left to right + and then top to bottom. + + Rewards + The reward is the delay of each vehicle minus a penalty for switching + traffic lights + + Termination + A rollout is terminated once the time horizon is reached. + + Additional + Vehicles are rerouted to the start of their original routes once they + reach the end of the network in order to ensure a constant number of + vehicles. + + """ + + def __init__(self, env_params, sim_params, scenario, simulator='traci'): + super().__init__(env_params, sim_params, scenario, simulator) + + for p in ADDITIONAL_PO_ENV_PARAMS.keys(): + if p not in env_params.additional_params: + raise KeyError( + 'Environment parameter "{}" not supplied'.format(p)) + + # number of vehicles nearest each intersection that is observed in the + # state space; defaults to 2 + self.num_observed = env_params.additional_params.get("num_observed", 2) + + # used during visualization + self.observed_ids = [] + + @property + def observation_space(self): + """State space that is partially observed. + + Velocities, distance to intersections, edge number (for nearby + vehicles) from each direction, edge information, and traffic light + state. + """ + tl_box = Box( + low=0., + high=1, + shape=(3 * 4 * self.num_observed * self.num_traffic_lights + + 2 * len(self.k.scenario.get_edge_list()) + + 3 * self.num_traffic_lights,), + dtype=np.float32) + return tl_box + + def get_state(self): + """See parent class. + + Returns self.num_observed number of vehicles closest to each traffic + light and for each vehicle its velocity, distance to intersection, + edge_number traffic light state. This is partially observed + """ + speeds = [] + dist_to_intersec = [] + edge_number = [] + max_speed = max( + self.k.scenario.speed_limit(edge) + for edge in self.k.scenario.get_edge_list()) + grid_array = self.net_params.additional_params["grid_array"] + max_dist = max(grid_array["short_length"], grid_array["long_length"], + grid_array["inner_length"]) + all_observed_ids = [] + + for _, edges in self.scenario.node_mapping: + for edge in edges: + observed_ids = \ + self.get_closest_to_intersection(edge, self.num_observed) + all_observed_ids += observed_ids + + # check which edges we have so we can always pad in the right + # positions + speeds += [ + self.k.vehicle.get_speed(veh_id) / max_speed + for veh_id in observed_ids + ] + dist_to_intersec += [ + (self.k.scenario.edge_length( + self.k.vehicle.get_edge(veh_id)) - + self.k.vehicle.get_position(veh_id)) / max_dist + for veh_id in observed_ids + ] + edge_number += \ + [self._convert_edge(self.k.vehicle.get_edge(veh_id)) / + (self.k.scenario.network.num_edges - 1) + for veh_id in observed_ids] + + if len(observed_ids) < self.num_observed: + diff = self.num_observed - len(observed_ids) + speeds += [0] * diff + dist_to_intersec += [0] * diff + edge_number += [0] * diff + + # now add in the density and average velocity on the edges + density = [] + velocity_avg = [] + for edge in self.k.scenario.get_edge_list(): + ids = self.k.vehicle.get_ids_by_edge(edge) + if len(ids) > 0: + vehicle_length = 5 + density += [vehicle_length * len(ids) / + self.k.scenario.edge_length(edge)] + velocity_avg += [np.mean( + [self.k.vehicle.get_speed(veh_id) for veh_id in + ids]) / max_speed] + else: + density += [0] + velocity_avg += [0] + self.observed_ids = all_observed_ids + return np.array( + np.concatenate([ + speeds, dist_to_intersec, edge_number, density, velocity_avg, + self.last_change.flatten().tolist(), + self.direction.flatten().tolist(), + self.currently_yellow.flatten().tolist() + ])) + + def compute_reward(self, rl_actions, **kwargs): + """See class definition.""" + if self.env_params.evaluate: + return - rewards.min_delay_unscaled(self) + else: + return (- rewards.min_delay_unscaled(self) + + rewards.penalize_standstill(self, gain=0.2)) + + def additional_command(self): + """See class definition.""" + # specify observed vehicles + [self.k.vehicle.set_observed(veh_id) for veh_id in self.observed_ids] + + +class TrafficLightGridTestEnv(TrafficLightGridEnv): + """ + Class for use in testing. + + This class overrides RL methods of traffic light grid so we can test + construction without needing to specify RL methods + """ + + def _apply_rl_actions(self, rl_actions): + """See class definition.""" + pass + + def compute_reward(self, rl_actions, **kwargs): + """No return, for testing purposes.""" + return 0 diff --git a/flow/multiagent_envs/__init__.py b/flow/multiagent_envs/__iniy__.py similarity index 74% rename from flow/multiagent_envs/__init__.py rename to flow/multiagent_envs/__iniy__.py index 5639c1b1e8..e270fa3970 100644 --- a/flow/multiagent_envs/__init__.py +++ b/flow/multiagent_envs/__iniy__.py @@ -7,5 +7,10 @@ from flow.multiagent_envs.traffic_light_grid import MultiTrafficLightGridPOEnv from flow.multiagent_envs.highway import MultiAgentHighwayPOEnv -__all__ = ['MultiEnv', 'MultiAgentAccelEnv', 'MultiWaveAttenuationPOEnv', - 'MultiTrafficLightGridPOEnv', 'MultiAgentHighwayPOEnv'] +__all__ = [ + 'MultiEnv', + 'MultiAgentAccelEnv', + 'MultiWaveAttenuationPOEnv', + 'MultiTrafficLightGridPOEnv', + 'MultiAgentHighwayPOEnv' +] diff --git a/flow/multiagent_envs/highway.py b/flow/multiagent_envs/highway.py index cd38ff22e8..c95f8ad76c 100644 --- a/flow/multiagent_envs/highway.py +++ b/flow/multiagent_envs/highway.py @@ -1,190 +1,15 @@ -"""Environment used to train vehicles to improve traffic on a highway.""" -import numpy as np -from gym.spaces.box import Box -from flow.core.rewards import desired_velocity -from flow.multiagent_envs.multiagent_env import MultiEnv +"""Pending deprecation file. +To view the actual content, go to: flow/envs/multiagent/highway.py +""" +from flow.utils.flow_warnings import deprecated +from flow.envs.multiagent.highway import MultiAgentHighwayPOEnv as MAHPOEnv +from flow.envs.multiagent.highway import ADDITIONAL_ENV_PARAMS # noqa: F401 -ADDITIONAL_ENV_PARAMS = { - # maximum acceleration of autonomous vehicles - 'max_accel': 1, - # maximum deceleration of autonomous vehicles - 'max_decel': 1, - # desired velocity for all vehicles in the network, in m/s - "target_velocity": 25 -} +@deprecated('flow.multiagent_envs.highway', + 'flow.envs.multiagent.highway.MultiAgentHighwayPOEnv') +class MultiAgentHighwayPOEnv(MAHPOEnv): + """See parent class.""" -class MultiAgentHighwayPOEnv(MultiEnv): - """Partially observable multi-agent environment for an highway with ramps. - - This environment is used to train autonomous vehicles to attenuate the - formation and propagation of waves in an open highway network. - - The highway can contain an arbitrary number of entrance and exit ramps, and - is intended to be used with the HighwayRampsScenario scenario. - - The policy is shared among the agents, so there can be a non-constant - number of RL vehicles throughout the simulation. - - Required from env_params: - - * max_accel: maximum acceleration for autonomous vehicles, in m/s^2 - * max_decel: maximum deceleration for autonomous vehicles, in m/s^2 - * target_velocity: desired velocity for all vehicles in the network, in m/s - - The following states, actions and rewards are considered for one autonomous - vehicle only, as they will be computed in the same way for each of them. - - States - The observation consists of the speeds and bumper-to-bumper headways of - the vehicles immediately preceding and following autonomous vehicle, as - well as the speed of the autonomous vehicle. - - Actions - The action consists of an acceleration, bound according to the - environment parameters, as well as three values that will be converted - into probabilities via softmax to decide of a lane change (left, none - or right). - - Rewards - The reward function encourages proximity of the system-level velocity - to a desired velocity specified in the environment parameters, while - slightly penalizing small time headways among autonomous vehicles. - - Termination - A rollout is terminated if the time horizon is reached or if two - vehicles collide into one another. - """ - - def __init__(self, env_params, sim_params, scenario, simulator='traci'): - for p in ADDITIONAL_ENV_PARAMS.keys(): - if p not in env_params.additional_params: - raise KeyError( - 'Environment parameter "{}" not supplied'.format(p)) - - super().__init__(env_params, sim_params, scenario, simulator) - - @property - def observation_space(self): - """See class definition.""" - return Box(low=0, high=1, shape=(5, ), dtype=np.float32) - - @property - def action_space(self): - """See class definition.""" - return Box( - low=-np.abs(self.env_params.additional_params['max_decel']), - high=self.env_params.additional_params['max_accel'], - shape=(1,), # (4,), - dtype=np.float32) - - def _apply_rl_actions(self, rl_actions): - """See class definition.""" - # in the warmup steps, rl_actions is None - if rl_actions: - for rl_id, actions in rl_actions.items(): - accel = actions[0] - - # lane_change_softmax = np.exp(actions[1:4]) - # lane_change_softmax /= np.sum(lane_change_softmax) - # lane_change_action = np.random.choice([-1, 0, 1], - # p=lane_change_softmax) - - self.k.vehicle.apply_acceleration(rl_id, accel) - # self.k.vehicle.apply_lane_change(rl_id, lane_change_action) - - def get_state(self): - """See class definition.""" - obs = {} - - # normalizing constants - max_speed = self.k.scenario.max_speed() - max_length = self.k.scenario.length() - - for rl_id in self.k.vehicle.get_rl_ids(): - this_speed = self.k.vehicle.get_speed(rl_id) - lead_id = self.k.vehicle.get_leader(rl_id) - follower = self.k.vehicle.get_follower(rl_id) - - if lead_id in ["", None]: - # in case leader is not visible - lead_speed = max_speed - lead_head = max_length - else: - lead_speed = self.k.vehicle.get_speed(lead_id) - lead_head = self.k.vehicle.get_headway(lead_id) - - if follower in ["", None]: - # in case follower is not visible - follow_speed = 0 - follow_head = max_length - else: - follow_speed = self.k.vehicle.get_speed(follower) - follow_head = self.k.vehicle.get_headway(follower) - - observation = np.array([ - this_speed / max_speed, - (lead_speed - this_speed) / max_speed, - lead_head / max_length, - (this_speed - follow_speed) / max_speed, - follow_head / max_length - ]) - - obs.update({rl_id: observation}) - - return obs - - def compute_reward(self, rl_actions, **kwargs): - """See class definition.""" - # in the warmup steps - if rl_actions is None: - return {} - - rewards = {} - for rl_id in self.k.vehicle.get_rl_ids(): - if self.env_params.evaluate: - # reward is speed of vehicle if we are in evaluation mode - reward = self.k.vehicle.get_speed(rl_id) - elif kwargs['fail']: - # reward is 0 if a collision occurred - reward = 0 - else: - # reward high system-level velocities - cost1 = desired_velocity(self, fail=kwargs['fail']) - - # penalize small time headways - cost2 = 0 - t_min = 1 # smallest acceptable time headway - - lead_id = self.k.vehicle.get_leader(rl_id) - if lead_id not in ["", None] \ - and self.k.vehicle.get_speed(rl_id) > 0: - t_headway = max( - self.k.vehicle.get_headway(rl_id) / - self.k.vehicle.get_speed(rl_id), 0) - cost2 += min((t_headway - t_min) / t_min, 0) - - # weights for cost1, cost2, and cost3, respectively - eta1, eta2 = 1.00, 0.10 - - reward = max(eta1 * cost1 + eta2 * cost2, 0) - - rewards[rl_id] = reward - return rewards - - def additional_command(self): - """See parent class. - - Define which vehicles are observed for visualization purposes. - """ - # specify observed vehicles - for rl_id in self.k.vehicle.get_rl_ids(): - # leader - lead_id = self.k.vehicle.get_leader(rl_id) - if lead_id: - self.k.vehicle.set_observed(lead_id) - # follower - follow_id = self.k.vehicle.get_follower(rl_id) - if follow_id: - self.k.vehicle.set_observed(follow_id) + pass diff --git a/flow/multiagent_envs/loop/loop_accel.py b/flow/multiagent_envs/loop/loop_accel.py index d325de9d2f..f46e3e38dc 100644 --- a/flow/multiagent_envs/loop/loop_accel.py +++ b/flow/multiagent_envs/loop/loop_accel.py @@ -1,52 +1,14 @@ -"""Environment for training the acceleration behavior of vehicles in a loop.""" +"""Pending deprecation file. -import numpy as np -from flow.core import rewards -from flow.envs.loop.loop_accel import AccelEnv -from flow.multiagent_envs.multiagent_env import MultiEnv +To view the actual content, go to: flow/envs/multiagent/traffic_light_grid.py +""" +from flow.utils.flow_warnings import deprecated +from flow.envs.multiagent.ring.accel import MultiAgentAccelEnv as MAAEnv -class MultiAgentAccelEnv(AccelEnv, MultiEnv): - """Adversarial multi-agent env. +@deprecated('flow.multiagent_envs.loop.loop_accel', + 'flow.envs.multiagent.ring.accel.MultiAgentAccelEnv') +class MultiAgentAccelEnv(MAAEnv): + """See parent class.""" - Multi-agent env with an adversarial agent perturbing - the accelerations of the autonomous vehicle - """ - - def _apply_rl_actions(self, rl_actions): - """See class definition.""" - sorted_rl_ids = [ - veh_id for veh_id in self.sorted_ids - if veh_id in self.k.vehicle.get_rl_ids() - ] - av_action = rl_actions['av'] - adv_action = rl_actions['adversary'] - perturb_weight = self.env_params.additional_params['perturb_weight'] - rl_action = av_action + perturb_weight * adv_action - self.k.vehicle.apply_acceleration(sorted_rl_ids, rl_action) - - def compute_reward(self, rl_actions, **kwargs): - """Compute opposing rewards for agents. - - The agent receives the class definition reward, - the adversary receives the negative of the agent reward - """ - if self.env_params.evaluate: - reward = np.mean(self.k.vehicle.get_speed( - self.k.vehicle.get_ids())) - return {'av': reward, 'adversary': -reward} - else: - reward = rewards.desired_velocity(self, fail=kwargs['fail']) - return {'av': reward, 'adversary': -reward} - - def get_state(self, **kwargs): - """See class definition for the state. - - The adversary state and the agent state are identical. - """ - state = np.array([[ - self.k.vehicle.get_speed(veh_id) / self.k.scenario.max_speed(), - self.k.vehicle.get_x_by_id(veh_id) / self.k.scenario.length() - ] for veh_id in self.sorted_ids]) - state = np.ndarray.flatten(state) - return {'av': state, 'adversary': state} + pass diff --git a/flow/multiagent_envs/loop/wave_attenuation.py b/flow/multiagent_envs/loop/wave_attenuation.py index 68bb5b21f3..98f36e2a70 100644 --- a/flow/multiagent_envs/loop/wave_attenuation.py +++ b/flow/multiagent_envs/loop/wave_attenuation.py @@ -1,131 +1,15 @@ -""" -Environment used to train a stop-and-go dissipating controller. - -This is the environment that was used in: +"""Pending deprecation file. -C. Wu, A. Kreidieh, K. Parvate, E. Vinitsky, A. Bayen, "Flow: Architecture and -Benchmarking for Reinforcement Learning in Traffic Control," CoRR, vol. -abs/1710.05465, 2017. [Online]. Available: https://arxiv.org/abs/1710.05465 +To view the actual content, go to: flow/envs/multiagent/traffic_light_grid.py """ +from flow.utils.flow_warnings import deprecated +from flow.envs.multiagent.ring.wave_attenuation import MultiWaveAttenuationPOEnv as MWAPOEnv +from flow.envs.multiagent.ring.wave_attenuation import ADDITIONAL_ENV_PARAMS # noqa: F401 -import numpy as np -from gym.spaces.box import Box -from flow.multiagent_envs.multiagent_env import MultiEnv - -ADDITIONAL_ENV_PARAMS = { - # maximum acceleration of autonomous vehicles - 'max_accel': 1, - # maximum deceleration of autonomous vehicles - 'max_decel': 1, - # bounds on the ranges of ring road lengths the autonomous vehicle is - # trained on - 'ring_length': [220, 270], -} - - -class MultiWaveAttenuationPOEnv(MultiEnv): - """Multiagent shared model version of WaveAttenuationPOEnv. - - Intended to work with Lord Of The Rings Scenario. - Note that this environment current - only works when there is one autonomous vehicle - on each ring. - - Required from env_params: See parent class - - States - See parent class - Actions - See parent class - - Rewards - See parent class - - Termination - See parent class - - """ - - @property - def observation_space(self): - """See class definition.""" - return Box(low=0, high=1, shape=(3,), dtype=np.float32) - - @property - def action_space(self): - """See class definition.""" - add_params = self.net_params.additional_params - num_rings = add_params['num_rings'] - return Box( - low=-np.abs(self.env_params.additional_params['max_decel']), - high=self.env_params.additional_params['max_accel'], - shape=(int(self.initial_vehicles.num_rl_vehicles / num_rings), ), - dtype=np.float32) - - def get_state(self): - """See class definition.""" - obs = {} - for rl_id in self.k.vehicle.get_rl_ids(): - lead_id = self.k.vehicle.get_leader(rl_id) or rl_id - - # normalizers - max_speed = 15. - max_length = self.env_params.additional_params['ring_length'][1] - - observation = np.array([ - self.k.vehicle.get_speed(rl_id) / max_speed, - (self.k.vehicle.get_speed(lead_id) - - self.k.vehicle.get_speed(rl_id)) - / max_speed, - self.k.vehicle.get_headway(rl_id) / max_length - ]) - obs.update({rl_id: observation}) - - return obs - - def _apply_rl_actions(self, rl_actions): - """Split the accelerations by ring.""" - if rl_actions: - rl_ids = list(rl_actions.keys()) - accel = list(rl_actions.values()) - self.k.vehicle.apply_acceleration(rl_ids, accel) - - def compute_reward(self, rl_actions, **kwargs): - """See class definition.""" - # in the warmup steps - if rl_actions is None: - return {} - - rew = {} - for rl_id in rl_actions.keys(): - edge_id = rl_id.split('_')[1] - edges = self.gen_edges(edge_id) - vehs_on_edge = self.k.vehicle.get_ids_by_edge(edges) - vel = np.array([ - self.k.vehicle.get_speed(veh_id) - for veh_id in vehs_on_edge - ]) - if any(vel < -100) or kwargs['fail']: - return 0. - - target_vel = self.env_params.additional_params['target_velocity'] - max_cost = np.array([target_vel] * len(vehs_on_edge)) - max_cost = np.linalg.norm(max_cost) - - cost = vel - target_vel - cost = np.linalg.norm(cost) - - rew[rl_id] = max(max_cost - cost, 0) / max_cost - return rew - def additional_command(self): - """Define which vehicles are observed for visualization purposes.""" - # specify observed vehicles - for rl_id in self.k.vehicle.get_rl_ids(): - lead_id = self.k.vehicle.get_leader(rl_id) or rl_id - self.k.vehicle.set_observed(lead_id) +@deprecated('flow.multiagent_envs.loop.wave_attenuation', + 'flow.envs.multiagent.ring.wave_attenuation.MultiWaveAttenuationPOEnv') +class MultiWaveAttenuationPOEnv(MWAPOEnv): + """See parent class.""" - def gen_edges(self, i): - """Return the edges corresponding to the rl id.""" - return ['top_{}'.format(i), 'left_{}'.format(i), - 'right_{}'.format(i), 'bottom_{}'.format(i)] + pass diff --git a/flow/multiagent_envs/multiagent_env.py b/flow/multiagent_envs/multiagent_env.py index c47d2ed93e..15ff442a79 100644 --- a/flow/multiagent_envs/multiagent_env.py +++ b/flow/multiagent_envs/multiagent_env.py @@ -1,297 +1,14 @@ -"""Environment for training multi-agent experiments.""" +"""Pending deprecation file. -from copy import deepcopy -import numpy as np -import random -import traceback -from gym.spaces import Box +To view the actual content, go to: flow/envs/multiagent/base.py +""" +from flow.utils.flow_warnings import deprecated +from flow.envs.multiagent.base import MultiEnv as MAEnv -from traci.exceptions import FatalTraCIError -from traci.exceptions import TraCIException -from ray.rllib.env import MultiAgentEnv +@deprecated('flow.multiagent_envs.multiagent_env', + 'flow.envs.multiagent.base.MultiEnv') +class MultiEnv(MAEnv): + """See parent class.""" -from flow.envs.base_env import Env -from flow.utils.exceptions import FatalFlowError - - -class MultiEnv(MultiAgentEnv, Env): - """Multi-agent version of base env. See parent class for info.""" - - def step(self, rl_actions): - """Advance the environment by one step. - - Assigns actions to autonomous and human-driven agents (i.e. vehicles, - traffic lights, etc...). Actions that are not assigned are left to the - control of the simulator. The actions are then used to advance the - simulator by the number of time steps requested per environment step. - - Results from the simulations are processed through various classes, - such as the Vehicle and TrafficLight kernels, to produce standardized - methods for identifying specific network state features. Finally, - results from the simulator are used to generate appropriate - observations. - - Parameters - ---------- - rl_actions : array_like - an list of actions provided by the rl algorithm - - Returns - ------- - observation : dict of array_like - agent's observation of the current environment - reward : dict of floats - amount of reward associated with the previous state/action pair - done : dict of bool - indicates whether the episode has ended - info : dict - contains other diagnostic information from the previous action - """ - for _ in range(self.env_params.sims_per_step): - self.time_counter += 1 - self.step_counter += 1 - - # perform acceleration actions for controlled human-driven vehicles - if len(self.k.vehicle.get_controlled_ids()) > 0: - accel = [] - for veh_id in self.k.vehicle.get_controlled_ids(): - accel_contr = self.k.vehicle.get_acc_controller(veh_id) - action = accel_contr.get_action(self) - accel.append(action) - self.k.vehicle.apply_acceleration( - self.k.vehicle.get_controlled_ids(), accel) - - # perform lane change actions for controlled human-driven vehicles - if len(self.k.vehicle.get_controlled_lc_ids()) > 0: - direction = [] - for veh_id in self.k.vehicle.get_controlled_lc_ids(): - target_lane = self.k.vehicle.get_lane_changing_controller( - veh_id).get_action(self) - direction.append(target_lane) - self.k.vehicle.apply_lane_change( - self.k.vehicle.get_controlled_lc_ids(), - direction=direction) - - # perform (optionally) routing actions for all vehicle in the - # network, including rl and sumo-controlled vehicles - routing_ids = [] - routing_actions = [] - for veh_id in self.k.vehicle.get_ids(): - if self.k.vehicle.get_routing_controller(veh_id) is not None: - routing_ids.append(veh_id) - route_contr = self.k.vehicle.get_routing_controller(veh_id) - routing_actions.append(route_contr.choose_route(self)) - self.k.vehicle.choose_routes(routing_ids, routing_actions) - - self.apply_rl_actions(rl_actions) - - self.additional_command() - - # advance the simulation in the simulator by one step - self.k.simulation.simulation_step() - - # store new observations in the vehicles and traffic lights class - self.k.update(reset=False) - - # update the colors of vehicles - if self.sim_params.render: - self.k.vehicle.update_vehicle_colors() - - # crash encodes whether the simulator experienced a collision - crash = self.k.simulation.check_collision() - - # stop collecting new simulation steps if there is a collision - if crash: - break - - states = self.get_state() - done = {key: key in self.k.vehicle.get_arrived_ids() - for key in states.keys()} - if crash: - done['__all__'] = True - else: - done['__all__'] = False - infos = {key: {} for key in states.keys()} - - # compute the reward - if self.env_params.clip_actions: - clipped_actions = self.clip_actions(rl_actions) - reward = self.compute_reward(clipped_actions, fail=crash) - else: - reward = self.compute_reward(rl_actions, fail=crash) - - return states, reward, done, infos - - def reset(self, new_inflow_rate=None): - """Reset the environment. - - This method is performed in between rollouts. It resets the state of - the environment, and re-initializes the vehicles in their starting - positions. - - If "shuffle" is set to True in InitialConfig, the initial positions of - vehicles is recalculated and the vehicles are shuffled. - - Returns - ------- - observation : dict of array_like - the initial observation of the space. The initial reward is assumed - to be zero. - """ - # reset the time counter - self.time_counter = 0 - - # warn about not using restart_instance when using inflows - if len(self.net_params.inflows.get()) > 0 and \ - not self.sim_params.restart_instance: - print( - "**********************************************************\n" - "**********************************************************\n" - "**********************************************************\n" - "WARNING: Inflows will cause computational performance to\n" - "significantly decrease after large number of rollouts. In \n" - "order to avoid this, set SumoParams(restart_instance=True).\n" - "**********************************************************\n" - "**********************************************************\n" - "**********************************************************" - ) - - if self.sim_params.restart_instance or \ - (self.step_counter > 2e6 and self.simulator != 'aimsun'): - self.step_counter = 0 - # issue a random seed to induce randomness into the next rollout - self.sim_params.seed = random.randint(0, 1e5) - - self.k.vehicle = deepcopy(self.initial_vehicles) - self.k.vehicle.master_kernel = self.k - # restart the sumo instance - self.restart_simulation(self.sim_params) - - # perform shuffling (if requested) - elif self.initial_config.shuffle: - self.setup_initial_state() - - # clear all vehicles from the network and the vehicles class - if self.simulator == 'traci': - for veh_id in self.k.kernel_api.vehicle.getIDList(): # FIXME: hack - try: - self.k.vehicle.remove(veh_id) - except (FatalTraCIError, TraCIException): - print(traceback.format_exc()) - - # clear all vehicles from the network and the vehicles class - # FIXME (ev, ak) this is weird and shouldn't be necessary - for veh_id in list(self.k.vehicle.get_ids()): - # do not try to remove the vehicles from the network in the first - # step after initializing the network, as there will be no vehicles - if self.step_counter == 0: - continue - try: - self.k.vehicle.remove(veh_id) - except (FatalTraCIError, TraCIException): - print("Error during start: {}".format(traceback.format_exc())) - - # reintroduce the initial vehicles to the network - for veh_id in self.initial_ids: - type_id, edge, lane_index, pos, speed = \ - self.initial_state[veh_id] - - try: - self.k.vehicle.add( - veh_id=veh_id, - type_id=type_id, - edge=edge, - lane=lane_index, - pos=pos, - speed=speed) - except (FatalTraCIError, TraCIException): - # if a vehicle was not removed in the first attempt, remove it - # now and then reintroduce it - self.k.vehicle.remove(veh_id) - if self.simulator == 'traci': - self.k.kernel_api.vehicle.remove(veh_id) # FIXME: hack - self.k.vehicle.add( - veh_id=veh_id, - type_id=type_id, - edge=edge, - lane=lane_index, - pos=pos, - speed=speed) - - # advance the simulation in the simulator by one step - self.k.simulation.simulation_step() - - # update the information in each kernel to match the current state - self.k.update(reset=True) - - # update the colors of vehicles - if self.sim_params.render: - self.k.vehicle.update_vehicle_colors() - - # check to make sure all vehicles have been spawned - if len(self.initial_ids) > self.k.vehicle.num_vehicles: - missing_vehicles = list( - set(self.initial_ids) - set(self.k.vehicle.get_ids())) - msg = '\nNot enough vehicles have spawned! Bad start?\n' \ - 'Missing vehicles / initial state:\n' - for veh_id in missing_vehicles: - msg += '- {}: {}\n'.format(veh_id, self.initial_state[veh_id]) - raise FatalFlowError(msg=msg) - - # perform (optional) warm-up steps before training - for _ in range(self.env_params.warmup_steps): - observation, _, _, _ = self.step(rl_actions=None) - - # render a frame - self.render(reset=True) - - return self.get_state() - - def clip_actions(self, rl_actions=None): - """Clip the actions passed from the RL agent. - - If no actions are provided at any given step, the rl agents default to - performing actions specified by sumo. - - Parameters - ---------- - rl_actions : array_like - list of actions provided by the RL algorithm - - Returns - ------- - rl_clipped : array_like - The rl_actions clipped according to the box - """ - # ignore if no actions are issued - if rl_actions is None: - return None - - # clip according to the action space requirements - if isinstance(self.action_space, Box): - for key, action in rl_actions.items(): - rl_actions[key] = np.clip( - action, - a_min=self.action_space.low, - a_max=self.action_space.high) - return rl_actions - - def apply_rl_actions(self, rl_actions=None): - """Specify the actions to be performed by the rl agent(s). - - If no actions are provided at any given step, the rl agents default to - performing actions specified by sumo. - - Parameters - ---------- - rl_actions : dict of array_like - dict of list of actions provided by the RL algorithm - """ - # ignore if no actions are issued - if rl_actions is None: - return - - # clip according to the action space requirements - clipped_actions = self.clip_actions(rl_actions) - self._apply_rl_actions(clipped_actions) + pass diff --git a/flow/multiagent_envs/traffic_light_grid.py b/flow/multiagent_envs/traffic_light_grid.py index 9c5b42102a..0469485e75 100644 --- a/flow/multiagent_envs/traffic_light_grid.py +++ b/flow/multiagent_envs/traffic_light_grid.py @@ -1,267 +1,15 @@ -"""Multi-agent environments for scenarios with traffic lights. +"""Pending deprecation file. -These environments are used to train traffic lights to regulate traffic flow -through an n x m grid. +To view the actual content, go to: flow/envs/multiagent/traffic_light_grid.py """ +from flow.utils.flow_warnings import deprecated +from flow.envs.multiagent.traffic_light_grid import MultiTrafficLightGridPOEnv as MTLGPOEnv +from flow.envs.multiagent.traffic_light_grid import ADDITIONAL_ENV_PARAMS # noqa: F401 -import numpy as np -from gym.spaces.box import Box -from gym.spaces.discrete import Discrete -from flow.core import rewards -from flow.envs.green_wave_env import PO_TrafficLightGridEnv -from flow.multiagent_envs.multiagent_env import MultiEnv +@deprecated('flow.multiagent_envs.traffic_light_grid', + 'flow.envs.multiagent.traffic_light_grid.MultiEnv') +class MultiTrafficLightGridPOEnv(MTLGPOEnv): + """See parent class.""" -ADDITIONAL_ENV_PARAMS = { - # num of nearby lights the agent can observe {0, ..., num_traffic_lights-1} - "num_local_lights": 4, # FIXME: not implemented yet - # num of nearby edges the agent can observe {0, ..., num_edges} - "num_local_edges": 4, # FIXME: not implemented yet -} - -# Index for retrieving ID when splitting node name, e.g. ":center#" -ID_IDX = 1 - - -class MultiTrafficLightGridPOEnv(PO_TrafficLightGridEnv, MultiEnv): - """Multiagent shared model version of PO_TrafficLightGridEnv. - - Required from env_params: See parent class - - States - See parent class - - Actions - See parent class - - Rewards - See parent class - - Termination - See parent class - """ - - def __init__(self, env_params, sim_params, scenario, simulator='traci'): - super().__init__(env_params, sim_params, scenario, simulator) - - for p in ADDITIONAL_ENV_PARAMS.keys(): - if p not in env_params.additional_params: - raise KeyError( - 'Environment parameter "{}" not supplied'.format(p)) - - # number of nearest lights to observe, defaults to 4 - self.num_local_lights = env_params.additional_params.get( - "num_local_lights", 4) - - # number of nearest edges to observe, defaults to 4 - self.num_local_edges = env_params.additional_params.get( - "num_local_edges", 4) - - @property - def observation_space(self): - """State space that is partially observed. - - Velocities, distance to intersections, edge number (for nearby - vehicles) from each direction, local edge information, and traffic - light state. - """ - tl_box = Box( - low=0., - high=1, - shape=(3 * 4 * self.num_observed + - 2 * self.num_local_edges + - 3 * (1 + self.num_local_lights), - ), - dtype=np.float32) - return tl_box - - @property - def action_space(self): - """See class definition.""" - if self.discrete: - return Discrete(2) - else: - return Box( - low=-1, - high=1, - shape=(1,), - dtype=np.float32) - - def get_state(self): - """Observations for each traffic light agent. - - :return: dictionary which contains agent-wise observations as follows: - - For the self.num_observed number of vehicles closest and incoming - towards traffic light agent, gives the vehicle velocity, distance to - intersection, edge number. - - For edges in the network, gives the density and average velocity. - - For the self.num_local_lights number of nearest lights (itself - included), gives the traffic light information, including the last - change time, light direction (i.e. phase), and a currently_yellow flag. - """ - # Normalization factors - max_speed = max( - self.k.scenario.speed_limit(edge) - for edge in self.k.scenario.get_edge_list()) - grid_array = self.net_params.additional_params["grid_array"] - max_dist = max(grid_array["short_length"], grid_array["long_length"], - grid_array["inner_length"]) - - # TODO(cathywu) refactor PO_TrafficLightGridEnv with convenience - # methods for observations, but remember to flatten for single-agent - - # Observed vehicle information - speeds = [] - dist_to_intersec = [] - edge_number = [] - all_observed_ids = [] - for _, edges in self.scenario.node_mapping: - local_speeds = [] - local_dists_to_intersec = [] - local_edge_numbers = [] - for edge in edges: - observed_ids = \ - self.get_closest_to_intersection(edge, self.num_observed) - all_observed_ids.append(observed_ids) - - # check which edges we have so we can always pad in the right - # positions - local_speeds.extend( - [self.k.vehicle.get_speed(veh_id) / max_speed for veh_id in - observed_ids]) - local_dists_to_intersec.extend([(self.k.scenario.edge_length( - self.k.vehicle.get_edge( - veh_id)) - self.k.vehicle.get_position( - veh_id)) / max_dist for veh_id in observed_ids]) - local_edge_numbers.extend([self._convert_edge( - self.k.vehicle.get_edge(veh_id)) / ( - self.k.scenario.network.num_edges - 1) for veh_id in - observed_ids]) - - if len(observed_ids) < self.num_observed: - diff = self.num_observed - len(observed_ids) - local_speeds.extend([1] * diff) - local_dists_to_intersec.extend([1] * diff) - local_edge_numbers.extend([0] * diff) - - speeds.append(local_speeds) - dist_to_intersec.append(local_dists_to_intersec) - edge_number.append(local_edge_numbers) - - # Edge information - density = [] - velocity_avg = [] - for edge in self.k.scenario.get_edge_list(): - ids = self.k.vehicle.get_ids_by_edge(edge) - if len(ids) > 0: - # TODO(cathywu) Why is there a 5 here? - density += [5 * len(ids) / self.k.scenario.edge_length(edge)] - velocity_avg += [np.mean( - [self.k.vehicle.get_speed(veh_id) for veh_id in - ids]) / max_speed] - else: - density += [0] - velocity_avg += [0] - density = np.array(density) - velocity_avg = np.array(velocity_avg) - self.observed_ids = all_observed_ids - - # Traffic light information - last_change = self.last_change.flatten() - direction = self.direction.flatten() - currently_yellow = self.currently_yellow.flatten() - # This is a catch-all for when the relative_node method returns a -1 - # (when there is no node in the direction sought). We add a last - # item to the lists here, which will serve as a default value. - # TODO(cathywu) are these values reasonable? - last_change = np.append(last_change, [0]) - direction = np.append(direction, [0]) - currently_yellow = np.append(currently_yellow, [1]) - - obs = {} - # TODO(cathywu) allow differentiation between rl and non-rl lights - node_to_edges = self.scenario.node_mapping - for rl_id in self.k.traffic_light.get_ids(): - rl_id_num = int(rl_id.split("center")[ID_IDX]) - local_edges = node_to_edges[rl_id_num][1] - local_edge_numbers = [self.k.scenario.get_edge_list().index(e) - for e in local_edges] - local_id_nums = [rl_id_num, self._get_relative_node(rl_id, "top"), - self._get_relative_node(rl_id, "bottom"), - self._get_relative_node(rl_id, "left"), - self._get_relative_node(rl_id, "right")] - - observation = np.array(np.concatenate( - [speeds[rl_id_num], dist_to_intersec[rl_id_num], - edge_number[rl_id_num], density[local_edge_numbers], - velocity_avg[local_edge_numbers], last_change[local_id_nums], - direction[local_id_nums], currently_yellow[local_id_nums] - ])) - obs.update({rl_id: observation}) - - return obs - - def _apply_rl_actions(self, rl_actions): - """ - See parent class. - - Issues action for each traffic light agent. - """ - for rl_id, rl_action in rl_actions.items(): - i = int(rl_id.split("center")[ID_IDX]) - if self.discrete: - raise NotImplementedError - else: - # convert values less than 0.0 to zero and above to 1. 0's - # indicate that we should not switch the direction - action = rl_action > 0.0 - - if self.currently_yellow[i] == 1: # currently yellow - self.last_change[i] += self.sim_step - # Check if our timer has exceeded the yellow phase, meaning it - # should switch to red - if self.last_change[i] >= self.min_switch_time: - if self.direction[i] == 0: - self.k.traffic_light.set_state( - node_id='center{}'.format(i), state="GrGr") - else: - self.k.traffic_light.set_state( - node_id='center{}'.format(i), state='rGrG') - self.currently_yellow[i] = 0 - else: - if action: - if self.direction[i] == 0: - self.k.traffic_light.set_state( - node_id='center{}'.format(i), state='yryr') - else: - self.k.traffic_light.set_state( - node_id='center{}'.format(i), state='ryry') - self.last_change[i] = 0.0 - self.direction[i] = not self.direction[i] - self.currently_yellow[i] = 1 - - def compute_reward(self, rl_actions, **kwargs): - """See class definition.""" - if rl_actions is None: - return {} - - if self.env_params.evaluate: - rew = -rewards.min_delay_unscaled(self) - else: - rew = -rewards.min_delay_unscaled(self) \ - + rewards.penalize_standstill(self, gain=0.2) - - # each agent receives reward normalized by number of lights - rew /= self.num_traffic_lights - - rews = {} - for rl_id in rl_actions.keys(): - rews[rl_id] = rew - return rews - - def additional_command(self): - """See class definition.""" - # specify observed vehicles - for veh_ids in self.observed_ids: - for veh_id in veh_ids: - self.k.vehicle.set_observed(veh_id) + pass diff --git a/flow/scenarios/__init__.py b/flow/scenarios/__init__.py index f05b87a059..eb2d50e0a9 100644 --- a/flow/scenarios/__init__.py +++ b/flow/scenarios/__init__.py @@ -1,26 +1,44 @@ """Contains all available scenarios in Flow.""" # base scenario class -from flow.scenarios.base_scenario import Scenario +from flow.scenarios.base import Scenario # custom scenarios from flow.scenarios.bay_bridge import BayBridgeScenario from flow.scenarios.bay_bridge_toll import BayBridgeTollScenario from flow.scenarios.bottleneck import BottleneckScenario -from flow.scenarios.figure_eight import Figure8Scenario -from flow.scenarios.grid import SimpleGridScenario +from flow.scenarios.figure_eight import FigureEightScenario +from flow.scenarios.traffic_light_grid import TrafficLightGridScenario from flow.scenarios.highway import HighwayScenario -from flow.scenarios.loop import LoopScenario +from flow.scenarios.ring import RingScenario from flow.scenarios.merge import MergeScenario -from flow.scenarios.loop_merge import TwoLoopsOneMergingScenario -from flow.scenarios.multi_loop import MultiLoopScenario +from flow.scenarios.multi_ring import MultiRingScenario from flow.scenarios.minicity import MiniCityScenario from flow.scenarios.highway_ramps import HighwayRampsScenario +# deprecated classes whose names have changed +from flow.scenarios.figure_eight import Figure8Scenario +from flow.scenarios.loop import LoopScenario +from flow.scenarios.grid import SimpleGridScenario +from flow.scenarios.multi_loop import MultiLoopScenario + + __all__ = [ - "Scenario", "BayBridgeScenario", "BayBridgeTollScenario", - "BottleneckScenario", "Figure8Scenario", "SimpleGridScenario", - "HighwayScenario", "LoopScenario", "MergeScenario", - "TwoLoopsOneMergingScenario", "MultiLoopScenario", "MiniCityScenario", - "HighwayRampsScenario" + "Scenario", + "BayBridgeScenario", + "BayBridgeTollScenario", + "BottleneckScenario", + "FigureEightScenario", + "TrafficLightGridScenario", + "HighwayScenario", + "RingScenario", + "MergeScenario", + "MultiRingScenario", + "MiniCityScenario", + "HighwayRampsScenario", + # deprecated classes + "Figure8Scenario", + "LoopScenario", + "SimpleGridScenario", + "MultiLoopScenario", ] diff --git a/flow/scenarios/base.py b/flow/scenarios/base.py new file mode 100755 index 0000000000..4b13f78fc2 --- /dev/null +++ b/flow/scenarios/base.py @@ -0,0 +1,811 @@ +"""Contains the base scenario class.""" + +from flow.core.params import InitialConfig +from flow.core.params import TrafficLightParams +from flow.core.params import SumoCarFollowingParams +from flow.core.params import SumoLaneChangeParams +import time +import xml.etree.ElementTree as ElementTree +from lxml import etree +from collections import defaultdict + +# default sumo probability value TODO (ak): remove +DEFAULT_PROBABILITY = 0 +# default sumo vehicle length value (in meters) TODO (ak): remove +DEFAULT_LENGTH = 5 +# default sumo vehicle class class TODO (ak): remove +DEFAULT_VCLASS = 0 + + +class Scenario(object): + """Base scenario class. + + Initializes a new scenario. Scenarios are used to specify features of + a network, including the positions of nodes, properties of the edges + and junctions connecting these nodes, properties of vehicles and + traffic lights, and other features as well. These features can later be + acquired from this class via a plethora of get methods (see + documentation). + + This class uses network specific features to generate the necessary network + configuration files needed to initialize a simulation instance. The methods + of this class are called by the base scenario class. + + The network files can be created in one of three ways: + + * Custom networks can be generated by defining the properties of the + network's directed graph. This is done by defining the nodes and edges + properties using the ``specify_nodes`` and ``specify_edges`` methods, + respectively, as well as other properties via methods including + ``specify_types``, ``specify_connections``, etc... For more on this, + see the tutorial on creating custom scenarios or refer to some of the + available scenarios. + + * Scenario data can be collected from an OpenStreetMap (.osm) file. The + .osm file is specified in the NetParams object. For example: + + >>> from flow.core.params import NetParams + >>> net_params = NetParams(osm_path='/path/to/osm_file.osm') + + In this case, no ``specify_nodes`` and ``specify_edges`` methods are + needed. However, a ``specify_routes`` method is still needed to specify + the appropriate routes vehicles can traverse in the network. + + * Scenario data can be collected from an sumo-specific network (.net.xml) + file. This file is specified in the NetParams object. For example: + + >>> from flow.core.params import NetParams + >>> net_params = NetParams(template='/path/to/template') + + In this case, no ``specify_nodes`` and ``specify_edges`` methods are + needed. However, a ``specify_routes`` method is still needed to specify + the appropriate routes vehicles can traverse in the network. + + This class can be instantiated once and reused in multiple experiments. + Note that this function stores all the relevant parameters. The + generate() function still needs to be called separately. + + Attributes + ---------- + orig_name : str + the variable provided under the `name` parameter to this object upon + instantiation + name : str + the variable provided under the `name` parameter to this object upon + instantiation, appended with a timestamp variable. This timestamp is + meant to differentiate generated scenario files during parallelism + vehicles : flow.core.params.VehicleParams + vehicle specific parameters, used to specify the types and number of + vehicles at the start of a simulation + net_params : flow.core.params.NetParams + network specific parameters, used primarily to identify properties of a + network such as the lengths of edges and the number of lanes in each + edge. This attribute is very network-specific, and should contain the + variables denoted by the `ADDITIONAL_NET_PARAMS` dict in each scenario + class file + initial_config : flow.core.params.InitialConfig + specifies parameters that affect the positioning of vehicle in the + network at the start of a simulation. For more, see flow/core/params.py + traffic_lights : flow.core.params.TrafficLightParams + used to describe the positions and types of traffic lights in the + network. For more, see flow/core/params.py + nodes : list of dict or None + list of nodes that are assigned to the scenario via the `specify_nodes` + method. All nodes in this variable are expected to have the following + properties: + + * **name**: a unique identifier for the node + * **x**: x-coordinate of the node, in meters + * **y**: y-coordinate of the node, in meters + + If the scenario is meant to generate the network from an OpenStreetMap + or template file, this variable is set to None + edges : list of dict or None + edges that are assigned to the scenario via the `specify_edges` method. + This include the shape, position, and properties of all edges in the + network. These properties include the following mandatory properties: + + * **id**: name of the edge + * **from**: name of the node the edge starts from + * **to**: the name of the node the edges ends at + * **length**: length of the edge + + In addition, either the following properties need to be specifically + defined or a **type** variable property must be defined with equivalent + attributes in `self.types`: + + * **numLanes**: the number of lanes on the edge + * **speed**: the speed limit for vehicles on the edge + + Moreover, the following attributes may optionally be available: + + * **shape**: the positions of intermediary nodes used to define the + shape of an edge. If no shape is specified, then the edge will appear + as a straight line. + + Note that, if the scenario is meant to generate the network from an + OpenStreetMap or template file, this variable is set to None + types : list of dict or None + A variable used to ease the definition of the properties of various + edges. Each element in the list consists of a dict consisting of the + following property: + + * **id**: name of the edge type. Edges in the `self.edges` attribute + with a similar value under the "type" key will adopt the properties + of other components of this list, such as "speed" and "numLanes". + + If the type variable is None, then no types are available within the + scenario. Furthermore, a proper example of this variable being used can + be found under `specify_types` in flow/scenarios/ring.py. + + Note that, if the scenario is meant to generate the network from an + OpenStreetMap or template file, this variable is set to None + connections : list of dict or None + A variable used to describe how any specific node's incoming and + outgoing edges/lane pairs are connected. If no connections are + specified, sumo generates default connections. + + If the connections attribute is set to None, then the connections + within the network will be specified by the simulator. + + Note that, if the scenario is meant to generate the network from an + OpenStreetMap or template file, this variable is set to None + routes : dict + A variable whose keys are the starting edge of a specific route, and + whose values are the list of edges a vehicle is meant to traverse + starting from that edge. These are only applied at the start of a + simulation; vehicles are allowed to reroute within the environment + immediately afterwards. + edge_starts : list of (str, float) + a list of tuples in which the first element of the tuple is the name of + the edge/intersection/internal_link, and the second value is the + distance of the link from some global reference, i.e. [(link_0, pos_0), + (link_1, pos_1), ...] + internal_edge_starts : list of (str, float) + A variable similar to `edge_starts` but for junctions within the + network. If no junctions are available, this variable will return the + default variable: `[(':', -1)]` needed by sumo simulations. + intersection_edge_starts : list of (str, float) + A variable similar to `edge_starts` but for intersections within + the network. This variable will be deprecated in future releases. + + Example + ------- + The following examples are derived from the `RingScenario` Scenario class + located in flow/scenarios/ring.py, and should serve as an example of the + types of outputs to be expected from the different variables of a scenario + class. + + First of all, the ring road scenario class can be instantiated by running + the following commands (note if this this unclear please refer to Tutorial + 1): + + >>> from flow.scenarios import RingScenario + >>> from flow.core.params import NetParams, VehicleParams + >>> + >>> scenario = RingScenario( + >>> name='test', + >>> vehicles=VehicleParams(), + >>> net_params=NetParams( + >>> additional_params={ + >>> 'length': 230, + >>> 'lanes': 1, + >>> 'speed_limit': 30, + >>> 'resolution': 40, + >>> } + >>> ) + >>> ) + + The various attributes then look as follows: + + >>> print(scenario.nodes) + >>> [{'id': 'bottom', 'x': '0', 'y': '-36.60563691113593'}, + >>> {'id': 'right', 'x': '36.60563691113593', 'y': '0'}, + >>> {'id': 'top', 'x': '0', 'y': '36.60563691113593'}, + >>> {'id': 'left', 'x': '-36.60563691113593', 'y': '0'}] + + + >>> print(scenario.edges) + >>> [ + >>> {'id': 'bottom', + >>> 'type': 'edgeType', + >>> 'from': 'bottom', + >>> 'to': 'right', + >>> 'length': '57.5', + >>> 'shape': '0.00,-36.61 1.47,-36.58 2.95,-36.49 4.41,-36.34 ' + >>> '5.87,-36.13 7.32,-35.87 8.76,-35.54 10.18,-35.16 ' + >>> '11.59,-34.72 12.98,-34.23 14.35,-33.68 15.69,-33.07 ' + >>> '17.01,-32.41 18.30,-31.70 19.56,-30.94 20.79,-30.13 ' + >>> '21.99,-29.26 23.15,-28.35 24.27,-27.40 25.36,-26.40 ' + >>> '26.40,-25.36 27.40,-24.27 28.35,-23.15 29.26,-21.99 ' + >>> '30.13,-20.79 30.94,-19.56 31.70,-18.30 32.41,-17.01 ' + >>> '33.07,-15.69 33.68,-14.35 34.23,-12.98 34.72,-11.59 ' + >>> '35.16,-10.18 35.54,-8.76 35.87,-7.32 36.13,-5.87 ' + >>> '36.34,-4.41 36.49,-2.95 36.58,-1.47 36.61,0.00' + >>> }, + >>> {'id': 'right', + >>> 'type': 'edgeType', + >>> 'from': 'right', + >>> 'to': 'top', + >>> 'length': '57.5', + >>> 'shape': '36.61,0.00 36.58,1.47 36.49,2.95 36.34,4.41 36.13,5.87 ' + >>> '35.87,7.32 35.54,8.76 35.16,10.18 34.72,11.59 ' + >>> '34.23,12.98 33.68,14.35 33.07,15.69 32.41,17.01 ' + >>> '31.70,18.30 30.94,19.56 30.13,20.79 29.26,21.99 ' + >>> '28.35,23.15 27.40,24.27 26.40,25.36 25.36,26.40 ' + >>> '24.27,27.40 23.15,28.35 21.99,29.26 20.79,30.13 ' + >>> '19.56,30.94 18.30,31.70 17.01,32.41 15.69,33.07 ' + >>> '14.35,33.68 12.98,34.23 11.59,34.72 10.18,35.16 ' + >>> '8.76,35.54 7.32,35.87 5.87,36.13 4.41,36.34 2.95,36.49 ' + >>> '1.47,36.58 0.00,36.61' + >>> }, + >>> {'id': 'top', + >>> 'type': 'edgeType', + >>> 'from': 'top', + >>> 'to': 'left', + >>> 'length': '57.5', + >>> 'shape': '0.00,36.61 -1.47,36.58 -2.95,36.49 -4.41,36.34 ' + >>> '-5.87,36.13 -7.32,35.87 -8.76,35.54 -10.18,35.16 ' + >>> '-11.59,34.72 -12.98,34.23 -14.35,33.68 -15.69,33.07 ' + >>> '-17.01,32.41 -18.30,31.70 -19.56,30.94 -20.79,30.13 ' + >>> '-21.99,29.26 -23.15,28.35 -24.27,27.40 -25.36,26.40 ' + >>> '-26.40,25.36 -27.40,24.27 -28.35,23.15 -29.26,21.99 ' + >>> '-30.13,20.79 -30.94,19.56 -31.70,18.30 -32.41,17.01 ' + >>> '-33.07,15.69 -33.68,14.35 -34.23,12.98 -34.72,11.59 ' + >>> '-35.16,10.18 -35.54,8.76 -35.87,7.32 -36.13,5.87 ' + >>> '-36.34,4.41 -36.49,2.95 -36.58,1.47 -36.61,0.00' + >>> }, + >>> {'id': 'left', + >>> 'type': 'edgeType', + >>> 'from': 'left', + >>> 'to': 'bottom', + >>> 'length': '57.5', + >>> 'shape': '-36.61,0.00 -36.58,-1.47 -36.49,-2.95 -36.34,-4.41 ' + >>> '-36.13,-5.87 -35.87,-7.32 -35.54,-8.76 -35.16,-10.18 ' + >>> '-34.72,-11.59 -34.23,-12.98 -33.68,-14.35 ' + >>> '-33.07,-15.69 -32.41,-17.01 -31.70,-18.30 ' + >>> '-30.94,-19.56 -30.13,-20.79 -29.26,-21.99 ' + >>> '-28.35,-23.15 -27.40,-24.27 -26.40,-25.36 ' + >>> '-25.36,-26.40 -24.27,-27.40 -23.15,-28.35 ' + >>> '-21.99,-29.26 -20.79,-30.13 -19.56,-30.94 ' + >>> '-18.30,-31.70 -17.01,-32.41 -15.69,-33.07 ' + >>> '-14.35,-33.68 -12.98,-34.23 -11.59,-34.72 ' + >>> '-10.18,-35.16 -8.76,-35.54 -7.32,-35.87 -5.87,-36.13 ' + >>> '-4.41,-36.34 -2.95,-36.49 -1.47,-36.58 -0.00,-36.61' + >>> } + >>> ] + + + >>> print(scenario.types) + >>> [{'id': 'edgeType', 'numLanes': '1', 'speed': '30'}] + + >>> print(scenario.connections) + >>> None + + >>> print(scenario.routes) + >>> { + >>> 'top': ['top', 'left', 'bottom', 'right'], + >>> 'left': ['left', 'bottom', 'right', 'top'], + >>> 'bottom': ['bottom', 'right', 'top', 'left'], + >>> 'right': ['right', 'top', 'left', 'bottom'] + >>> } + + + >>> print(scenario.edge_starts) + >>> [('bottom', 0), ('right', 57.5), ('top', 115.0), ('left', 172.5)] + + Finally, the ring scenario does not contain any junctions or intersections, + and as a result the `internal_edge_starts` and `intersection_edge_starts` + attributes are both set to None. For an example of a network with junctions + and intersections, please refer to: flow/scenarios/figure_eight.py. + + >>> print(scenario.internal_edge_starts) + >>> [(':', -1)] + + >>> print(scenario.intersection_edge_starts) + >>> [] + """ + + def __init__(self, + name, + vehicles, + net_params, + initial_config=InitialConfig(), + traffic_lights=TrafficLightParams()): + """Instantiate the base scenario class. + + Attributes + ---------- + name : str + A tag associated with the scenario + vehicles : flow.core.params.VehicleParams + see flow/core/params.py + net_params : flow.core.params.NetParams + see flow/core/params.py + initial_config : flow.core.params.InitialConfig + see flow/core/params.py + traffic_lights : flow.core.params.TrafficLightParams + see flow/core/params.py + """ + self.orig_name = name # To avoid repeated concatenation upon reset + self.name = name + time.strftime('_%Y%m%d-%H%M%S') + str(time.time()) + + self.vehicles = vehicles + self.net_params = net_params + self.initial_config = initial_config + self.traffic_lights = traffic_lights + + # specify routes vehicles can take + self.routes = self.specify_routes(net_params) + + if net_params.template is None and net_params.osm_path is None: + # specify the attributes of the nodes + self.nodes = self.specify_nodes(net_params) + # collect the attributes of each edge + self.edges = self.specify_edges(net_params) + # specify the types attributes (default is None) + self.types = self.specify_types(net_params) + # specify the connection attributes (default is None) + self.connections = self.specify_connections(net_params) + + # this is to be used if file paths other than the the network geometry + # file is specified + elif type(net_params.template) is dict: + if 'rou' in net_params.template: + veh, rou = self._vehicle_infos(net_params.template['rou']) + + vtypes = self._vehicle_type(net_params.template.get('vtype')) + cf = self._get_cf_params(vtypes) + lc = self._get_lc_params(vtypes) + + # add the vehicle types to the VehicleParams object + for t in vtypes: + vehicles.add(veh_id=t, car_following_params=cf[t], + lane_change_params=lc[t], num_vehicles=0) + + # add the routes of the vehicles that will be departed later + # under the name of the vehicle. This will later be identified + # by k.vehicles._add_departed + self.routes = rou + + # vehicles to be added with different departure times + self.template_vehicles = veh + + self.types = None + self.nodes = None + self.edges = None + self.connections = None + + # osm_path or template as type str + else: + self.nodes = None + self.edges = None + self.types = None + self.connections = None + + # optional parameters, used to get positions from some global reference + self.edge_starts = self.specify_edge_starts() + self.internal_edge_starts = self.specify_internal_edge_starts() + self.intersection_edge_starts = [] # this will be deprecated + + # TODO: convert to property + def specify_edge_starts(self): + """Define edge starts for road sections in the network. + + This is meant to provide some global reference frame for the road + edges in the network. + + By default, the edge starts are specified from the network + configuration file. Note that, the values are arbitrary but do not + allow the positions of any two edges to overlap, thereby making them + compatible with all starting position methods for vehicles. + + Returns + ------- + list of (str, float) + list of edge names and starting positions, + ex: [(edge0, pos0), (edge1, pos1), ...] + """ + return None + + # TODO: convert to property + def specify_internal_edge_starts(self): + """Define the edge starts for internal edge nodes. + + This is meant to provide some global reference frame for the internal + edges in the network. + + These edges are the result of finite-length connections between road + sections. This methods does not need to be specified if "no-internal- + links" is set to True in net_params. + + By default, all internal edge starts are given a position of -1. This + may be overridden; however, in general we do not worry about internal + edges and junctions in large networks. + + Returns + ------- + list of (str, float) + list of internal junction names and starting positions, + ex: [(internal0, pos0), (internal1, pos1), ...] + """ + return [(':', -1)] + + # TODO: convert to property + def specify_nodes(self, net_params): + """Specify the attributes of nodes in the network. + + Parameters + ---------- + net_params : flow.core.params.NetParams + see flow/core/params.py + + Returns + ------- + list of dict + + A list of node attributes (a separate dict for each node). Nodes + attributes must include: + + * id {string} -- name of the node + * x {float} -- x coordinate of the node + * y {float} -- y coordinate of the node + + Other attributes may also be specified. See: + http://sumo.dlr.de/wiki/Networks/Building_Networks_from_own_XML-descriptions#Node_Descriptions + """ + raise NotImplementedError + + # TODO: convert to property + def specify_edges(self, net_params): + """Specify the attributes of edges connecting pairs on nodes. + + Parameters + ---------- + net_params : flow.core.params.NetParams + see flow/core/params.py + + Returns + ------- + list of dict + + A list of edges attributes (a separate dict for each edge). Edge + attributes must include: + + * id {string} -- name of the edge + * from {string} -- name of node the directed edge starts from + * to {string} -- name of the node the directed edge ends at + + In addition, the attributes must contain at least one of the + following: + + * "numLanes" {int} and "speed" {float} -- the number of lanes and + speed limit of the edge, respectively + * type {string} -- a type identifier for the edge, which can be + used if several edges are supposed to possess the same number of + lanes, speed limits, etc... + + Other attributes may also be specified. See: + http://sumo.dlr.de/wiki/Networks/Building_Networks_from_own_XML-descriptions#Edge_Descriptions + """ + raise NotImplementedError + + # TODO: convert to property + def specify_types(self, net_params): + """Specify the attributes of various edge types (if any exist). + + Parameters + ---------- + net_params : flow.core.params.NetParams + see flow/core/params.py + + Returns + ------- + list of dict + A list of type attributes for specific groups of edges. If none are + specified, no .typ.xml file is created. + + For information on type attributes, see: + http://sumo.dlr.de/wiki/Networks/Building_Networks_from_own_XML-descriptions#Type_Descriptions + """ + return None + + # TODO: convert to property + def specify_connections(self, net_params): + """Specify the attributes of connections. + + These attributes are used to describe how any specific node's incoming + and outgoing edges/lane pairs are connected. If no connections are + specified, sumo generates default connections. + + Parameters + ---------- + net_params : flow.core.params.NetParams + see flow/core/params.py + + Returns + ------- + list of dict + A list of connection attributes. If none are specified, no .con.xml + file is created. + + For information on type attributes, see: + http://sumo.dlr.de/wiki/Networks/Building_Networks_from_own_XML-descriptions#Connection_Descriptions + """ + return None + + # TODO: convert to property + def specify_routes(self, net_params): + """Specify the routes vehicles can take starting from any edge. + + Routes can be specified in one of three ways: + + * In this case of deterministic routes (as is the case in the ring road + scenario), the routes can be specified as dictionary where the key + element represents the starting edge and the element is a single list + of edges the vehicle must traverse, with the first edge corresponding + to the edge the vehicle begins on. Note that the edges must be + connected for the route to be valid. + + For example (from flow/scenarios/ring.py): + + >>> def specify_routes(self, net_params): + >>> return { + >>> "top": ["top", "left", "bottom", "right"], + >>> "left": ["left", "bottom", "right", "top"], + >>> "bottom": ["bottom", "right", "top", "left"], + >>> "right": ["right", "top", "left", "bottom"] + >>> } + + * Alternatively, if the routes are meant to be stochastic, each element + can consist of a list of (route, probability) tuples, where the first + element in the tuple is one of the routes a vehicle can take from a + specific starting edge, and the second element is the probability + that vehicles will choose that route. Note that, in this case, the + sum of probability values for each dictionary key must sum up to one. + + For example, if we were to imagine the edge "right" in the ring road + examples where split into two edges, "right_0" and "right_1", the + routes for vehicles in this network in the probabilistic setting can + be: + + >>> def specify_routes(self, net_params): + >>> return { + >>> "top": [ + >>> (["top", "left", "bottom", "right_0"], 0.9), + >>> (["top", "left", "bottom", "right_1"], 0.1) + >>> ], + >>> "left": [ + >>> (["left", "bottom", "right_0", "top"], 0.3), + >>> (["left", "bottom", "right_1", "top"], 0.7) + >>> ], + >>> "bottom": [ + >>> (["bottom", "right_0", "top", "left"], 0.5), + >>> (["bottom", "right_1", "top", "left"], 0.5) + >>> ], + >>> "right_0": [ + >>> (["right_0", "top", "left", "bottom"], 1) + >>> ], + >>> "right_1": [ + >>> (["right_1", "top", "left", "bottom"], 1) + >>> ] + >>> } + + * Finally, if you would like to assign a specific starting edge and + route to a vehicle with a specific ID, you can do so by adding a + element into the dictionary whose key is the name of the vehicle and + whose content is the list of edges the vehicle is meant to traverse + as soon as it is introduced to the network. + + As an example, assume we have 4 vehicles named 'human_0', 'human_1', + 'human_2', and 'human_3' in the original ring road. Then, an + appropriate definition of the routes may look something like: + + >>> def specify_routes(self, net_params): + >>> return { + >>> "human_0": ["top", "left", "bottom", "right"], + >>> "human_1": ["left", "bottom", "right", "top"], + >>> "human_2": ["bottom", "right", "top", "left"], + >>> "human_3": ["right", "top", "left", "bottom"] + >>> } + + **Note**: This feature is experimental, and may not always work as + expected (for example if the starting positions and routes of a + specific vehicle do not match). + + The `define_routes` method is optional, and need not be defined. If it + is not implemented, vehicles that enter a network are assigned routes + consisting solely on their current edges, and exit the network once + they reach the end of their edge. Routes, however, can be reassigned + during simulation via a routing controller (see + flow/controllers/routing_controllers.py). + + Parameters + ---------- + net_params : flow.core.params.NetParams + see flow/core/params.py + + Returns + ------- + dict + Key = name of the starting edge + Element = list of edges a vehicle starting from this edge must + traverse *OR* a list of (route, probability) tuples for each + starting edge + """ + return None + + @staticmethod + def gen_custom_start_pos(cls, net_params, initial_config, num_vehicles): + """Generate a user defined set of starting positions. + + Parameters + ---------- + cls : flow.core.kernel.scenario.KernelScenario + flow scenario kernel, with all the relevant methods implemented + net_params : flow.core.params.NetParams + network-specific parameters + initial_config : flow.core.params.InitialConfig + see flow/core/params.py + num_vehicles : int + number of vehicles to be placed on the network + + Returns + ------- + list of tuple (float, float) + list of start positions [(edge0, pos0), (edge1, pos1), ...] + list of int + list of start lanes + list of float + list of start speeds + """ + raise NotImplementedError + + @staticmethod + def _vehicle_infos(file_names): + """Import of vehicle from a configuration file. + + This is a utility function for computing vehicle information. It + imports a network configuration file, and returns the information on + the vehicle and add it into the Vehicle object. + + Parameters + ---------- + file_names : list of str + path to the xml file to load + + Returns + ------- + dict + + * Key = id of the vehicle + * Element = dict of departure speed, vehicle type, depart Position, + depart edges + """ + # this is meant to deal with the case that there is only one rou file + if isinstance(file_names, str): + file_names = [file_names] + + vehicle_data = dict() + routes_data = dict() + type_data = defaultdict(int) + + for filename in file_names: + # import the .net.xml file containing all edge/type data + parser = etree.XMLParser(recover=True) + tree = ElementTree.parse(filename, parser=parser) + root = tree.getroot() + + # collect the departure properties and routes and vehicles whose + # properties are instantiated within the .rou.xml file. This will + # only apply if such data is within the file (it is not implemented + # by scenarios in Flow). + for vehicle in root.findall('vehicle'): + # collect the edges the vehicle is meant to traverse + route = vehicle.find('route') + route_edges = route.attrib["edges"].split(' ') + + # collect the names of each vehicle type and number of vehicles + # of each type + type_vehicle = vehicle.attrib['type'] + type_data[type_vehicle] += 1 + + vehicle_data[vehicle.attrib['id']] = { + 'departSpeed': vehicle.attrib['departSpeed'], + 'depart': vehicle.attrib['depart'], + 'typeID': type_vehicle, + 'departPos': vehicle.attrib['departPos'], + } + + routes_data[vehicle.attrib['id']] = route_edges + + # collect the edges the vehicle is meant to traverse for the given + # sets of routes that are not associated with individual vehicles + for route in root.findall('route'): + route_edges = route.attrib["edges"].split(' ') + routes_data[route.attrib['id']] = route_edges + + return vehicle_data, routes_data + + @staticmethod + def _vehicle_type(filename): + """Import vehicle type data from a *.add.xml file. + + This is a utility function for outputting all the type of vehicle. + + Parameters + ---------- + filename : str + path to the vtypes.add.xml file to load + + Returns + ------- + dict or None + the key is the vehicle_type id and the value is a dict we've type + of the vehicle, depart edges, depart Speed, departPos. If no + filename is provided, this method returns None as well. + """ + if filename is None: + return None + + parser = etree.XMLParser(recover=True) + tree = ElementTree.parse(filename, parser=parser) + + root = tree.getroot() + veh_type = {} + + # this hack is meant to support the LuST scenario and Flow scenarios + root = [root] if len(root.findall('vTypeDistribution')) == 0 \ + else root.findall('vTypeDistribution') + + for r in root: + for vtype in r.findall('vType'): + # TODO: make for everything + veh_type[vtype.attrib['id']] = { + 'vClass': vtype.attrib.get('vClass', DEFAULT_VCLASS), + 'accel': vtype.attrib['accel'], + 'decel': vtype.attrib['decel'], + 'sigma': vtype.attrib['sigma'], + 'length': vtype.attrib.get('length', DEFAULT_LENGTH), + 'minGap': vtype.attrib['minGap'], + 'maxSpeed': vtype.attrib['maxSpeed'], + 'probability': vtype.attrib.get( + 'probability', DEFAULT_PROBABILITY), + 'speedDev': vtype.attrib['speedDev'] + } + + return veh_type + + @staticmethod + def _get_cf_params(vtypes): + """Return the car-following sumo params from vtypes.""" + ret = {} + for typ in vtypes: + # TODO: add vClass + ret[typ] = SumoCarFollowingParams( + speed_mode='all_checks', + accel=float(vtypes[typ]['accel']), + decel=float(vtypes[typ]['decel']), + sigma=float(vtypes[typ]['sigma']), + length=float(vtypes[typ]['length']), + min_gap=float(vtypes[typ]['minGap']), + max_speed=float(vtypes[typ]['maxSpeed']), + probability=float(vtypes[typ]['probability']), + speed_dev=float(vtypes[typ]['speedDev']) + ) + + return ret + + @staticmethod + def _get_lc_params(vtypes): + """Return the lane change sumo params from vtypes.""" + ret = {} + for typ in vtypes: + ret[typ] = SumoLaneChangeParams(lane_change_mode=1621) + + return ret + + def __str__(self): + """Return the name of the scenario and the number of vehicles.""" + return 'Scenario ' + self.name + ' with ' + \ + str(self.vehicles.num_vehicles) + ' vehicles.' diff --git a/flow/scenarios/base_scenario.py b/flow/scenarios/base_scenario.py old mode 100755 new mode 100644 index 7445ee7a85..f753862a58 --- a/flow/scenarios/base_scenario.py +++ b/flow/scenarios/base_scenario.py @@ -1,811 +1,14 @@ -"""Contains the base scenario class.""" +"""Pending deprecation file. -from flow.core.params import InitialConfig -from flow.core.params import TrafficLightParams -from flow.core.params import SumoCarFollowingParams -from flow.core.params import SumoLaneChangeParams -import time -import xml.etree.ElementTree as ElementTree -from lxml import etree -from collections import defaultdict +To view the actual content, go to: flow/scenarios/base.py +""" +from flow.utils.flow_warnings import deprecated +from flow.scenarios.base import Scenario as Scen -# default sumo probability value TODO (ak): remove -DEFAULT_PROBABILITY = 0 -# default sumo vehicle length value (in meters) TODO (ak): remove -DEFAULT_LENGTH = 5 -# default sumo vehicle class class TODO (ak): remove -DEFAULT_VCLASS = 0 +@deprecated('flow.scenarios.base_scenario', + 'flow.scenarios.base.Scenario') +class Scenario(Scen): + """See parent class.""" -class Scenario(object): - """Base scenario class. - - Initializes a new scenario. Scenarios are used to specify features of - a network, including the positions of nodes, properties of the edges - and junctions connecting these nodes, properties of vehicles and - traffic lights, and other features as well. These features can later be - acquired from this class via a plethora of get methods (see - documentation). - - This class uses network specific features to generate the necessary network - configuration files needed to initialize a simulation instance. The methods - of this class are called by the base scenario class. - - The network files can be created in one of three ways: - - * Custom networks can be generated by defining the properties of the - network's directed graph. This is done by defining the nodes and edges - properties using the ``specify_nodes`` and ``specify_edges`` methods, - respectively, as well as other properties via methods including - ``specify_types``, ``specify_connections``, etc... For more on this, - see the tutorial on creating custom scenarios or refer to some of the - available scenarios. - - * Scenario data can be collected from an OpenStreetMap (.osm) file. The - .osm file is specified in the NetParams object. For example: - - >>> from flow.core.params import NetParams - >>> net_params = NetParams(osm_path='/path/to/osm_file.osm') - - In this case, no ``specify_nodes`` and ``specify_edges`` methods are - needed. However, a ``specify_routes`` method is still needed to specify - the appropriate routes vehicles can traverse in the network. - - * Scenario data can be collected from an sumo-specific network (.net.xml) - file. This file is specified in the NetParams object. For example: - - >>> from flow.core.params import NetParams - >>> net_params = NetParams(template='/path/to/template') - - In this case, no ``specify_nodes`` and ``specify_edges`` methods are - needed. However, a ``specify_routes`` method is still needed to specify - the appropriate routes vehicles can traverse in the network. - - This class can be instantiated once and reused in multiple experiments. - Note that this function stores all the relevant parameters. The - generate() function still needs to be called separately. - - Attributes - ---------- - orig_name : str - the variable provided under the `name` parameter to this object upon - instantiation - name : str - the variable provided under the `name` parameter to this object upon - instantiation, appended with a timestamp variable. This timestamp is - meant to differentiate generated scenario files during parallelism - vehicles : flow.core.params.VehicleParams - vehicle specific parameters, used to specify the types and number of - vehicles at the start of a simulation - net_params : flow.core.params.NetParams - network specific parameters, used primarily to identify properties of a - network such as the lengths of edges and the number of lanes in each - edge. This attribute is very network-specific, and should contain the - variables denoted by the `ADDITIONAL_NET_PARAMS` dict in each scenario - class file - initial_config : flow.core.params.InitialConfig - specifies parameters that affect the positioning of vehicle in the - network at the start of a simulation. For more, see flow/core/params.py - traffic_lights : flow.core.params.TrafficLightParams - used to describe the positions and types of traffic lights in the - network. For more, see flow/core/params.py - nodes : list of dict or None - list of nodes that are assigned to the scenario via the `specify_nodes` - method. All nodes in this variable are expected to have the following - properties: - - * **name**: a unique identifier for the node - * **x**: x-coordinate of the node, in meters - * **y**: y-coordinate of the node, in meters - - If the scenario is meant to generate the network from an OpenStreetMap - or template file, this variable is set to None - edges : list of dict or None - edges that are assigned to the scenario via the `specify_edges` method. - This include the shape, position, and properties of all edges in the - network. These properties include the following mandatory properties: - - * **id**: name of the edge - * **from**: name of the node the edge starts from - * **to**: the name of the node the edges ends at - * **length**: length of the edge - - In addition, either the following properties need to be specifically - defined or a **type** variable property must be defined with equivalent - attributes in `self.types`: - - * **numLanes**: the number of lanes on the edge - * **speed**: the speed limit for vehicles on the edge - - Moreover, the following attributes may optionally be available: - - * **shape**: the positions of intermediary nodes used to define the - shape of an edge. If no shape is specified, then the edge will appear - as a straight line. - - Note that, if the scenario is meant to generate the network from an - OpenStreetMap or template file, this variable is set to None - types : list of dict or None - A variable used to ease the definition of the properties of various - edges. Each element in the list consists of a dict consisting of the - following property: - - * **id**: name of the edge type. Edges in the `self.edges` attribute - with a similar value under the "type" key will adopt the properties - of other components of this list, such as "speed" and "numLanes". - - If the type variable is None, then no types are available within the - scenario. Furthermore, a proper example of this variable being used can - be found under `specify_types` in flow/scenarios/loop.py. - - Note that, if the scenario is meant to generate the network from an - OpenStreetMap or template file, this variable is set to None - connections : list of dict or None - A variable used to describe how any specific node's incoming and - outgoing edges/lane pairs are connected. If no connections are - specified, sumo generates default connections. - - If the connections attribute is set to None, then the connections - within the network will be specified by the simulator. - - Note that, if the scenario is meant to generate the network from an - OpenStreetMap or template file, this variable is set to None - routes : dict - A variable whose keys are the starting edge of a specific route, and - whose values are the list of edges a vehicle is meant to traverse - starting from that edge. These are only applied at the start of a - simulation; vehicles are allowed to reroute within the environment - immediately afterwards. - edge_starts : list of (str, float) - a list of tuples in which the first element of the tuple is the name of - the edge/intersection/internal_link, and the second value is the - distance of the link from some global reference, i.e. [(link_0, pos_0), - (link_1, pos_1), ...] - internal_edge_starts : list of (str, float) - A variable similar to `edge_starts` but for junctions within the - network. If no junctions are available, this variable will return the - default variable: `[(':', -1)]` needed by sumo simulations. - intersection_edge_starts : list of (str, float) - A variable similar to `edge_starts` but for intersections within - the network. This variable will be deprecated in future releases. - - Example - ------- - The following examples are derived from the `LoopScenario` Scenario class - located in flow/scenarios/loop.py, and should serve as an example of the - types of outputs to be expected from the different variables of a scenario - class. - - First of all, the ring road scenario class can be instantiated by running - the following commands (note if this this unclear please refer to Tutorial - 1): - - >>> from flow.scenarios import LoopScenario - >>> from flow.core.params import NetParams, VehicleParams - >>> - >>> scenario = LoopScenario( - >>> name='test', - >>> vehicles=VehicleParams(), - >>> net_params=NetParams( - >>> additional_params={ - >>> 'length': 230, - >>> 'lanes': 1, - >>> 'speed_limit': 30, - >>> 'resolution': 40, - >>> } - >>> ) - >>> ) - - The various attributes then look as follows: - - >>> print(scenario.nodes) - >>> [{'id': 'bottom', 'x': '0', 'y': '-36.60563691113593'}, - >>> {'id': 'right', 'x': '36.60563691113593', 'y': '0'}, - >>> {'id': 'top', 'x': '0', 'y': '36.60563691113593'}, - >>> {'id': 'left', 'x': '-36.60563691113593', 'y': '0'}] - - - >>> print(scenario.edges) - >>> [ - >>> {'id': 'bottom', - >>> 'type': 'edgeType', - >>> 'from': 'bottom', - >>> 'to': 'right', - >>> 'length': '57.5', - >>> 'shape': '0.00,-36.61 1.47,-36.58 2.95,-36.49 4.41,-36.34 ' - >>> '5.87,-36.13 7.32,-35.87 8.76,-35.54 10.18,-35.16 ' - >>> '11.59,-34.72 12.98,-34.23 14.35,-33.68 15.69,-33.07 ' - >>> '17.01,-32.41 18.30,-31.70 19.56,-30.94 20.79,-30.13 ' - >>> '21.99,-29.26 23.15,-28.35 24.27,-27.40 25.36,-26.40 ' - >>> '26.40,-25.36 27.40,-24.27 28.35,-23.15 29.26,-21.99 ' - >>> '30.13,-20.79 30.94,-19.56 31.70,-18.30 32.41,-17.01 ' - >>> '33.07,-15.69 33.68,-14.35 34.23,-12.98 34.72,-11.59 ' - >>> '35.16,-10.18 35.54,-8.76 35.87,-7.32 36.13,-5.87 ' - >>> '36.34,-4.41 36.49,-2.95 36.58,-1.47 36.61,0.00' - >>> }, - >>> {'id': 'right', - >>> 'type': 'edgeType', - >>> 'from': 'right', - >>> 'to': 'top', - >>> 'length': '57.5', - >>> 'shape': '36.61,0.00 36.58,1.47 36.49,2.95 36.34,4.41 36.13,5.87 ' - >>> '35.87,7.32 35.54,8.76 35.16,10.18 34.72,11.59 ' - >>> '34.23,12.98 33.68,14.35 33.07,15.69 32.41,17.01 ' - >>> '31.70,18.30 30.94,19.56 30.13,20.79 29.26,21.99 ' - >>> '28.35,23.15 27.40,24.27 26.40,25.36 25.36,26.40 ' - >>> '24.27,27.40 23.15,28.35 21.99,29.26 20.79,30.13 ' - >>> '19.56,30.94 18.30,31.70 17.01,32.41 15.69,33.07 ' - >>> '14.35,33.68 12.98,34.23 11.59,34.72 10.18,35.16 ' - >>> '8.76,35.54 7.32,35.87 5.87,36.13 4.41,36.34 2.95,36.49 ' - >>> '1.47,36.58 0.00,36.61' - >>> }, - >>> {'id': 'top', - >>> 'type': 'edgeType', - >>> 'from': 'top', - >>> 'to': 'left', - >>> 'length': '57.5', - >>> 'shape': '0.00,36.61 -1.47,36.58 -2.95,36.49 -4.41,36.34 ' - >>> '-5.87,36.13 -7.32,35.87 -8.76,35.54 -10.18,35.16 ' - >>> '-11.59,34.72 -12.98,34.23 -14.35,33.68 -15.69,33.07 ' - >>> '-17.01,32.41 -18.30,31.70 -19.56,30.94 -20.79,30.13 ' - >>> '-21.99,29.26 -23.15,28.35 -24.27,27.40 -25.36,26.40 ' - >>> '-26.40,25.36 -27.40,24.27 -28.35,23.15 -29.26,21.99 ' - >>> '-30.13,20.79 -30.94,19.56 -31.70,18.30 -32.41,17.01 ' - >>> '-33.07,15.69 -33.68,14.35 -34.23,12.98 -34.72,11.59 ' - >>> '-35.16,10.18 -35.54,8.76 -35.87,7.32 -36.13,5.87 ' - >>> '-36.34,4.41 -36.49,2.95 -36.58,1.47 -36.61,0.00' - >>> }, - >>> {'id': 'left', - >>> 'type': 'edgeType', - >>> 'from': 'left', - >>> 'to': 'bottom', - >>> 'length': '57.5', - >>> 'shape': '-36.61,0.00 -36.58,-1.47 -36.49,-2.95 -36.34,-4.41 ' - >>> '-36.13,-5.87 -35.87,-7.32 -35.54,-8.76 -35.16,-10.18 ' - >>> '-34.72,-11.59 -34.23,-12.98 -33.68,-14.35 ' - >>> '-33.07,-15.69 -32.41,-17.01 -31.70,-18.30 ' - >>> '-30.94,-19.56 -30.13,-20.79 -29.26,-21.99 ' - >>> '-28.35,-23.15 -27.40,-24.27 -26.40,-25.36 ' - >>> '-25.36,-26.40 -24.27,-27.40 -23.15,-28.35 ' - >>> '-21.99,-29.26 -20.79,-30.13 -19.56,-30.94 ' - >>> '-18.30,-31.70 -17.01,-32.41 -15.69,-33.07 ' - >>> '-14.35,-33.68 -12.98,-34.23 -11.59,-34.72 ' - >>> '-10.18,-35.16 -8.76,-35.54 -7.32,-35.87 -5.87,-36.13 ' - >>> '-4.41,-36.34 -2.95,-36.49 -1.47,-36.58 -0.00,-36.61' - >>> } - >>> ] - - - >>> print(scenario.types) - >>> [{'id': 'edgeType', 'numLanes': '1', 'speed': '30'}] - - >>> print(scenario.connections) - >>> None - - >>> print(scenario.routes) - >>> { - >>> 'top': ['top', 'left', 'bottom', 'right'], - >>> 'left': ['left', 'bottom', 'right', 'top'], - >>> 'bottom': ['bottom', 'right', 'top', 'left'], - >>> 'right': ['right', 'top', 'left', 'bottom'] - >>> } - - - >>> print(scenario.edge_starts) - >>> [('bottom', 0), ('right', 57.5), ('top', 115.0), ('left', 172.5)] - - Finally, the loop scenario does not contain any junctions or intersections, - and as a result the `internal_edge_starts` and `intersection_edge_starts` - attributes are both set to None. For an example of a network with junctions - and intersections, please refer to: flow/scenarios/figure_eight.py. - - >>> print(scenario.internal_edge_starts) - >>> [(':', -1)] - - >>> print(scenario.intersection_edge_starts) - >>> [] - """ - - def __init__(self, - name, - vehicles, - net_params, - initial_config=InitialConfig(), - traffic_lights=TrafficLightParams()): - """Instantiate the base scenario class. - - Attributes - ---------- - name : str - A tag associated with the scenario - vehicles : flow.core.params.VehicleParams - see flow/core/params.py - net_params : flow.core.params.NetParams - see flow/core/params.py - initial_config : flow.core.params.InitialConfig - see flow/core/params.py - traffic_lights : flow.core.params.TrafficLightParams - see flow/core/params.py - """ - self.orig_name = name # To avoid repeated concatenation upon reset - self.name = name + time.strftime('_%Y%m%d-%H%M%S') + str(time.time()) - - self.vehicles = vehicles - self.net_params = net_params - self.initial_config = initial_config - self.traffic_lights = traffic_lights - - # specify routes vehicles can take - self.routes = self.specify_routes(net_params) - - if net_params.template is None and net_params.osm_path is None: - # specify the attributes of the nodes - self.nodes = self.specify_nodes(net_params) - # collect the attributes of each edge - self.edges = self.specify_edges(net_params) - # specify the types attributes (default is None) - self.types = self.specify_types(net_params) - # specify the connection attributes (default is None) - self.connections = self.specify_connections(net_params) - - # this is to be used if file paths other than the the network geometry - # file is specified - elif type(net_params.template) is dict: - if 'rou' in net_params.template: - veh, rou = self._vehicle_infos(net_params.template['rou']) - - vtypes = self._vehicle_type(net_params.template.get('vtype')) - cf = self._get_cf_params(vtypes) - lc = self._get_lc_params(vtypes) - - # add the vehicle types to the VehicleParams object - for t in vtypes: - vehicles.add(veh_id=t, car_following_params=cf[t], - lane_change_params=lc[t], num_vehicles=0) - - # add the routes of the vehicles that will be departed later - # under the name of the vehicle. This will later be identified - # by k.vehicles._add_departed - self.routes = rou - - # vehicles to be added with different departure times - self.template_vehicles = veh - - self.types = None - self.nodes = None - self.edges = None - self.connections = None - - # osm_path or template as type str - else: - self.nodes = None - self.edges = None - self.types = None - self.connections = None - - # optional parameters, used to get positions from some global reference - self.edge_starts = self.specify_edge_starts() - self.internal_edge_starts = self.specify_internal_edge_starts() - self.intersection_edge_starts = [] # this will be deprecated - - # TODO: convert to property - def specify_edge_starts(self): - """Define edge starts for road sections in the network. - - This is meant to provide some global reference frame for the road - edges in the network. - - By default, the edge starts are specified from the network - configuration file. Note that, the values are arbitrary but do not - allow the positions of any two edges to overlap, thereby making them - compatible with all starting position methods for vehicles. - - Returns - ------- - list of (str, float) - list of edge names and starting positions, - ex: [(edge0, pos0), (edge1, pos1), ...] - """ - return None - - # TODO: convert to property - def specify_internal_edge_starts(self): - """Define the edge starts for internal edge nodes. - - This is meant to provide some global reference frame for the internal - edges in the network. - - These edges are the result of finite-length connections between road - sections. This methods does not need to be specified if "no-internal- - links" is set to True in net_params. - - By default, all internal edge starts are given a position of -1. This - may be overridden; however, in general we do not worry about internal - edges and junctions in large networks. - - Returns - ------- - list of (str, float) - list of internal junction names and starting positions, - ex: [(internal0, pos0), (internal1, pos1), ...] - """ - return [(':', -1)] - - # TODO: convert to property - def specify_nodes(self, net_params): - """Specify the attributes of nodes in the network. - - Parameters - ---------- - net_params : flow.core.params.NetParams - see flow/core/params.py - - Returns - ------- - list of dict - - A list of node attributes (a separate dict for each node). Nodes - attributes must include: - - * id {string} -- name of the node - * x {float} -- x coordinate of the node - * y {float} -- y coordinate of the node - - Other attributes may also be specified. See: - http://sumo.dlr.de/wiki/Networks/Building_Networks_from_own_XML-descriptions#Node_Descriptions - """ - raise NotImplementedError - - # TODO: convert to property - def specify_edges(self, net_params): - """Specify the attributes of edges connecting pairs on nodes. - - Parameters - ---------- - net_params : flow.core.params.NetParams - see flow/core/params.py - - Returns - ------- - list of dict - - A list of edges attributes (a separate dict for each edge). Edge - attributes must include: - - * id {string} -- name of the edge - * from {string} -- name of node the directed edge starts from - * to {string} -- name of the node the directed edge ends at - - In addition, the attributes must contain at least one of the - following: - - * "numLanes" {int} and "speed" {float} -- the number of lanes and - speed limit of the edge, respectively - * type {string} -- a type identifier for the edge, which can be - used if several edges are supposed to possess the same number of - lanes, speed limits, etc... - - Other attributes may also be specified. See: - http://sumo.dlr.de/wiki/Networks/Building_Networks_from_own_XML-descriptions#Edge_Descriptions - """ - raise NotImplementedError - - # TODO: convert to property - def specify_types(self, net_params): - """Specify the attributes of various edge types (if any exist). - - Parameters - ---------- - net_params : flow.core.params.NetParams - see flow/core/params.py - - Returns - ------- - list of dict - A list of type attributes for specific groups of edges. If none are - specified, no .typ.xml file is created. - - For information on type attributes, see: - http://sumo.dlr.de/wiki/Networks/Building_Networks_from_own_XML-descriptions#Type_Descriptions - """ - return None - - # TODO: convert to property - def specify_connections(self, net_params): - """Specify the attributes of connections. - - These attributes are used to describe how any specific node's incoming - and outgoing edges/lane pairs are connected. If no connections are - specified, sumo generates default connections. - - Parameters - ---------- - net_params : flow.core.params.NetParams - see flow/core/params.py - - Returns - ------- - list of dict - A list of connection attributes. If none are specified, no .con.xml - file is created. - - For information on type attributes, see: - http://sumo.dlr.de/wiki/Networks/Building_Networks_from_own_XML-descriptions#Connection_Descriptions - """ - return None - - # TODO: convert to property - def specify_routes(self, net_params): - """Specify the routes vehicles can take starting from any edge. - - Routes can be specified in one of three ways: - - * In this case of deterministic routes (as is the case in the ring road - scenario), the routes can be specified as dictionary where the key - element represents the starting edge and the element is a single list - of edges the vehicle must traverse, with the first edge corresponding - to the edge the vehicle begins on. Note that the edges must be - connected for the route to be valid. - - For example (from flow/scenarios/loop.py): - - >>> def specify_routes(self, net_params): - >>> return { - >>> "top": ["top", "left", "bottom", "right"], - >>> "left": ["left", "bottom", "right", "top"], - >>> "bottom": ["bottom", "right", "top", "left"], - >>> "right": ["right", "top", "left", "bottom"] - >>> } - - * Alternatively, if the routes are meant to be stochastic, each element - can consist of a list of (route, probability) tuples, where the first - element in the tuple is one of the routes a vehicle can take from a - specific starting edge, and the second element is the probability - that vehicles will choose that route. Note that, in this case, the - sum of probability values for each dictionary key must sum up to one. - - For example, if we were to imagine the edge "right" in the ring road - examples where split into two edges, "right_0" and "right_1", the - routes for vehicles in this network in the probabilistic setting can - be: - - >>> def specify_routes(self, net_params): - >>> return { - >>> "top": [ - >>> (["top", "left", "bottom", "right_0"], 0.9), - >>> (["top", "left", "bottom", "right_1"], 0.1) - >>> ], - >>> "left": [ - >>> (["left", "bottom", "right_0", "top"], 0.3), - >>> (["left", "bottom", "right_1", "top"], 0.7) - >>> ], - >>> "bottom": [ - >>> (["bottom", "right_0", "top", "left"], 0.5), - >>> (["bottom", "right_1", "top", "left"], 0.5) - >>> ], - >>> "right_0": [ - >>> (["right_0", "top", "left", "bottom"], 1) - >>> ], - >>> "right_1": [ - >>> (["right_1", "top", "left", "bottom"], 1) - >>> ] - >>> } - - * Finally, if you would like to assign a specific starting edge and - route to a vehicle with a specific ID, you can do so by adding a - element into the dictionary whose key is the name of the vehicle and - whose content is the list of edges the vehicle is meant to traverse - as soon as it is introduced to the network. - - As an example, assume we have 4 vehicles named 'human_0', 'human_1', - 'human_2', and 'human_3' in the original ring road. Then, an - appropriate definition of the routes may look something like: - - >>> def specify_routes(self, net_params): - >>> return { - >>> "human_0": ["top", "left", "bottom", "right"], - >>> "human_1": ["left", "bottom", "right", "top"], - >>> "human_2": ["bottom", "right", "top", "left"], - >>> "human_3": ["right", "top", "left", "bottom"] - >>> } - - **Note**: This feature is experimental, and may not always work as - expected (for example if the starting positions and routes of a - specific vehicle do not match). - - The `define_routes` method is optional, and need not be defined. If it - is not implemented, vehicles that enter a network are assigned routes - consisting solely on their current edges, and exit the network once - they reach the end of their edge. Routes, however, can be reassigned - during simulation via a routing controller (see - flow/controllers/routing_controllers.py). - - Parameters - ---------- - net_params : flow.core.params.NetParams - see flow/core/params.py - - Returns - ------- - dict - Key = name of the starting edge - Element = list of edges a vehicle starting from this edge must - traverse *OR* a list of (route, probability) tuples for each - starting edge - """ - return None - - @staticmethod - def gen_custom_start_pos(cls, net_params, initial_config, num_vehicles): - """Generate a user defined set of starting positions. - - Parameters - ---------- - cls : flow.core.kernel.scenario.KernelScenario - flow scenario kernel, with all the relevant methods implemented - net_params : flow.core.params.NetParams - network-specific parameters - initial_config : flow.core.params.InitialConfig - see flow/core/params.py - num_vehicles : int - number of vehicles to be placed on the network - - Returns - ------- - list of tuple (float, float) - list of start positions [(edge0, pos0), (edge1, pos1), ...] - list of int - list of start lanes - list of float - list of start speeds - """ - raise NotImplementedError - - @staticmethod - def _vehicle_infos(file_names): - """Import of vehicle from a configuration file. - - This is a utility function for computing vehicle information. It - imports a network configuration file, and returns the information on - the vehicle and add it into the Vehicle object. - - Parameters - ---------- - file_names : list of str - path to the xml file to load - - Returns - ------- - dict - - * Key = id of the vehicle - * Element = dict of departure speed, vehicle type, depart Position, - depart edges - """ - # this is meant to deal with the case that there is only one rou file - if isinstance(file_names, str): - file_names = [file_names] - - vehicle_data = dict() - routes_data = dict() - type_data = defaultdict(int) - - for filename in file_names: - # import the .net.xml file containing all edge/type data - parser = etree.XMLParser(recover=True) - tree = ElementTree.parse(filename, parser=parser) - root = tree.getroot() - - # collect the departure properties and routes and vehicles whose - # properties are instantiated within the .rou.xml file. This will - # only apply if such data is within the file (it is not implemented - # by scenarios in Flow). - for vehicle in root.findall('vehicle'): - # collect the edges the vehicle is meant to traverse - route = vehicle.find('route') - route_edges = route.attrib["edges"].split(' ') - - # collect the names of each vehicle type and number of vehicles - # of each type - type_vehicle = vehicle.attrib['type'] - type_data[type_vehicle] += 1 - - vehicle_data[vehicle.attrib['id']] = { - 'departSpeed': vehicle.attrib['departSpeed'], - 'depart': vehicle.attrib['depart'], - 'typeID': type_vehicle, - 'departPos': vehicle.attrib['departPos'], - } - - routes_data[vehicle.attrib['id']] = route_edges - - # collect the edges the vehicle is meant to traverse for the given - # sets of routes that are not associated with individual vehicles - for route in root.findall('route'): - route_edges = route.attrib["edges"].split(' ') - routes_data[route.attrib['id']] = route_edges - - return vehicle_data, routes_data - - @staticmethod - def _vehicle_type(filename): - """Import vehicle type data from a *.add.xml file. - - This is a utility function for outputting all the type of vehicle. - - Parameters - ---------- - filename : str - path to the vtypes.add.xml file to load - - Returns - ------- - dict or None - the key is the vehicle_type id and the value is a dict we've type - of the vehicle, depart edges, depart Speed, departPos. If no - filename is provided, this method returns None as well. - """ - if filename is None: - return None - - parser = etree.XMLParser(recover=True) - tree = ElementTree.parse(filename, parser=parser) - - root = tree.getroot() - veh_type = {} - - # this hack is meant to support the LuST scenario and Flow scenarios - root = [root] if len(root.findall('vTypeDistribution')) == 0 \ - else root.findall('vTypeDistribution') - - for r in root: - for vtype in r.findall('vType'): - # TODO: make for everything - veh_type[vtype.attrib['id']] = { - 'vClass': vtype.attrib.get('vClass', DEFAULT_VCLASS), - 'accel': vtype.attrib['accel'], - 'decel': vtype.attrib['decel'], - 'sigma': vtype.attrib['sigma'], - 'length': vtype.attrib.get('length', DEFAULT_LENGTH), - 'minGap': vtype.attrib['minGap'], - 'maxSpeed': vtype.attrib['maxSpeed'], - 'probability': vtype.attrib.get( - 'probability', DEFAULT_PROBABILITY), - 'speedDev': vtype.attrib['speedDev'] - } - - return veh_type - - @staticmethod - def _get_cf_params(vtypes): - """Return the car-following sumo params from vtypes.""" - ret = {} - for typ in vtypes: - # TODO: add vClass - ret[typ] = SumoCarFollowingParams( - speed_mode='all_checks', - accel=float(vtypes[typ]['accel']), - decel=float(vtypes[typ]['decel']), - sigma=float(vtypes[typ]['sigma']), - length=float(vtypes[typ]['length']), - min_gap=float(vtypes[typ]['minGap']), - max_speed=float(vtypes[typ]['maxSpeed']), - probability=float(vtypes[typ]['probability']), - speed_dev=float(vtypes[typ]['speedDev']) - ) - - return ret - - @staticmethod - def _get_lc_params(vtypes): - """Return the lane change sumo params from vtypes.""" - ret = {} - for typ in vtypes: - ret[typ] = SumoLaneChangeParams(lane_change_mode=1621) - - return ret - - def __str__(self): - """Return the name of the scenario and the number of vehicles.""" - return 'Scenario ' + self.name + ' with ' + \ - str(self.vehicles.num_vehicles) + ' vehicles.' + pass diff --git a/flow/scenarios/bay_bridge.py b/flow/scenarios/bay_bridge.py index fdb0f1d793..3d721e37f6 100644 --- a/flow/scenarios/bay_bridge.py +++ b/flow/scenarios/bay_bridge.py @@ -1,6 +1,6 @@ """Contains the Bay Bridge scenario class.""" -from flow.scenarios.base_scenario import Scenario +from flow.scenarios.base import Scenario # Use this to ensure that vehicles are only placed in the edges of the Bay # Bridge moving from Oakland to San Francisco. diff --git a/flow/scenarios/bay_bridge_toll.py b/flow/scenarios/bay_bridge_toll.py index 6e908d2726..b2f8400133 100644 --- a/flow/scenarios/bay_bridge_toll.py +++ b/flow/scenarios/bay_bridge_toll.py @@ -1,6 +1,6 @@ """Contains the Bay Bridge toll scenario class.""" -from flow.scenarios.base_scenario import Scenario +from flow.scenarios.base import Scenario # Use this to ensure that vehicles are only placed in the edges of the Bay # Bridge moving from Oakland to San Francisco. diff --git a/flow/scenarios/bottleneck.py b/flow/scenarios/bottleneck.py index 475622564e..981debb74d 100644 --- a/flow/scenarios/bottleneck.py +++ b/flow/scenarios/bottleneck.py @@ -2,7 +2,7 @@ from flow.core.params import InitialConfig from flow.core.params import TrafficLightParams -from flow.scenarios.base_scenario import Scenario +from flow.scenarios.base import Scenario import numpy as np ADDITIONAL_NET_PARAMS = { diff --git a/flow/scenarios/figure_eight.py b/flow/scenarios/figure_eight.py index 9d2cf68329..87b807d964 100755 --- a/flow/scenarios/figure_eight.py +++ b/flow/scenarios/figure_eight.py @@ -3,9 +3,10 @@ import numpy as np from numpy import pi, sin, cos, linspace +from flow.utils.flow_warnings import deprecated from flow.core.params import InitialConfig from flow.core.params import TrafficLightParams -from flow.scenarios.base_scenario import Scenario +from flow.scenarios.base import Scenario ADDITIONAL_NET_PARAMS = { # radius of the circular components @@ -19,13 +20,13 @@ } -class Figure8Scenario(Scenario): +class FigureEightScenario(Scenario): """Figure eight scenario class. The figure eight network is an extension of the ring road network: Two rings, placed at opposite ends of the network, are connected by an intersection with road segments of length equal to the diameter of the - rings. Serves as a simulation of a closed loop intersection. + rings. Serves as a simulation of a closed ring intersection. Requires from net_params: @@ -40,9 +41,9 @@ class Figure8Scenario(Scenario): >>> from flow.core.params import NetParams >>> from flow.core.params import VehicleParams >>> from flow.core.params import InitialConfig - >>> from flow.scenarios import Figure8Scenario + >>> from flow.scenarios import FigureEightScenario >>> - >>> scenario = Figure8Scenario( + >>> scenario = FigureEightScenario( >>> name='figure_eight', >>> vehicles=VehicleParams(), >>> net_params=NetParams( @@ -261,3 +262,11 @@ def specify_internal_edge_starts(self): ] return internal_edgestarts + + +@deprecated('flow.scenarios.figure_eight', + 'flow.scenarios.figure_eight.FigureEightScenario') +class Figure8Scenario(FigureEightScenario): + """See parent class.""" + + pass diff --git a/flow/scenarios/grid.py b/flow/scenarios/grid.py index bc11561525..c0b7c28ba9 100644 --- a/flow/scenarios/grid.py +++ b/flow/scenarios/grid.py @@ -1,604 +1,15 @@ -"""Contains the grid scenario class.""" +"""Pending deprecation file. -import numpy as np +To view the actual content, go to: flow/scenarios/traffic_light_grid.py +""" +from flow.utils.flow_warnings import deprecated +from flow.scenarios.traffic_light_grid import TrafficLightGridScenario +from flow.scenarios.traffic_light_grid import ADDITIONAL_NET_PARAMS # noqa: F401 -from flow.scenarios.base_scenario import Scenario -from flow.core.params import InitialConfig -from flow.core.params import TrafficLightParams -from collections import defaultdict -ADDITIONAL_NET_PARAMS = { - # dictionary of grid array data - "grid_array": { - # number of horizontal rows of edges - "row_num": 3, - # number of vertical columns of edges - "col_num": 2, - # length of inner edges in the grid network - "inner_length": None, - # length of edges where vehicles enter the network - "short_length": None, - # length of edges where vehicles exit the network - "long_length": None, - # number of cars starting at the edges heading to the top - "cars_top": 20, - # number of cars starting at the edges heading to the bottom - "cars_bot": 20, - # number of cars starting at the edges heading to the left - "cars_left": 20, - # number of cars starting at the edges heading to the right - "cars_right": 20, - }, - # number of lanes in the horizontal edges - "horizontal_lanes": 1, - # number of lanes in the vertical edges - "vertical_lanes": 1, - # speed limit for all edges, may be represented as a float value, or a - # dictionary with separate values for vertical and horizontal lanes - "speed_limit": { - "horizontal": 35, - "vertical": 35 - } -} +@deprecated('flow.scenarios.grid', + 'flow.scenarios.traffic_light_grid.TrafficLightGridScenario') +class SimpleGridScenario(TrafficLightGridScenario): + """See parent class.""" - -class SimpleGridScenario(Scenario): - """Grid scenario class. - - The grid scenario consists of m vertical lanes and n horizontal lanes, - with a total of nxm intersections where the vertical and horizontal - edges meet. - - Requires from net_params: - - * **grid_array** : dictionary of grid array data, with the following keys - - * **row_num** : number of horizontal rows of edges - * **col_num** : number of vertical columns of edges - * **inner_length** : length of inner edges in the grid network - * **short_length** : length of edges that vehicles start on - * **long_length** : length of final edge in route - * **cars_top** : number of cars starting at the edges heading to the top - * **cars_bot** : number of cars starting at the edges heading to the - bottom - * **cars_left** : number of cars starting at the edges heading to the - left - * **cars_right** : number of cars starting at the edges heading to the - right - - * **horizontal_lanes** : number of lanes in the horizontal edges - * **vertical_lanes** : number of lanes in the vertical edges - * **speed_limit** : speed limit for all edges. This may be represented as a - float value, or a dictionary with separate values for vertical and - horizontal lanes. - - Usage - ----- - >>> from flow.core.params import NetParams - >>> from flow.core.params import VehicleParams - >>> from flow.core.params import InitialConfig - >>> from flow.scenarios import SimpleGridScenario - >>> - >>> scenario = SimpleGridScenario( - >>> name='grid', - >>> vehicles=VehicleParams(), - >>> net_params=NetParams( - >>> additional_params={ - >>> 'grid_array': { - >>> 'row_num': 3, - >>> 'col_num': 2, - >>> 'inner_length': 500, - >>> 'short_length': 500, - >>> 'long_length': 500, - >>> 'cars_top': 20, - >>> 'cars_bot': 20, - >>> 'cars_left': 20, - >>> 'cars_right': 20, - >>> }, - >>> 'horizontal_lanes': 1, - >>> 'vertical_lanes': 1, - >>> 'speed_limit': { - >>> 'vertical': 35, - >>> 'horizontal': 35 - >>> } - >>> }, - >>> ) - >>> ) - """ - - def __init__(self, - name, - vehicles, - net_params, - initial_config=InitialConfig(), - traffic_lights=TrafficLightParams()): - """Initialize an n*m grid scenario.""" - optional = ["tl_logic"] - for p in ADDITIONAL_NET_PARAMS.keys(): - if p not in net_params.additional_params and p not in optional: - raise KeyError('Network parameter "{}" not supplied'.format(p)) - - for p in ADDITIONAL_NET_PARAMS["grid_array"].keys(): - if p not in net_params.additional_params["grid_array"]: - raise KeyError( - 'Grid array parameter "{}" not supplied'.format(p)) - - # retrieve all additional parameters - # refer to the ADDITIONAL_NET_PARAMS dict for more documentation - self.vertical_lanes = net_params.additional_params["vertical_lanes"] - self.horizontal_lanes = net_params.additional_params[ - "horizontal_lanes"] - self.speed_limit = net_params.additional_params["speed_limit"] - if not isinstance(self.speed_limit, dict): - self.speed_limit = { - "horizontal": self.speed_limit, - "vertical": self.speed_limit - } - - self.grid_array = net_params.additional_params["grid_array"] - self.row_num = self.grid_array["row_num"] - self.col_num = self.grid_array["col_num"] - self.inner_length = self.grid_array["inner_length"] - self.short_length = self.grid_array["short_length"] - self.long_length = self.grid_array["long_length"] - self.cars_heading_top = self.grid_array["cars_top"] - self.cars_heading_bot = self.grid_array["cars_bot"] - self.cars_heading_left = self.grid_array["cars_left"] - self.cars_heading_right = self.grid_array["cars_right"] - - # specifies whether or not there will be traffic lights at the - # intersections (True by default) - self.use_traffic_lights = net_params.additional_params.get( - "traffic_lights", True) - - # radius of the inner nodes (ie of the intersections) - self.inner_nodes_radius = 2.9 + 3.3 * max(self.vertical_lanes, - self.horizontal_lanes) - - # total number of edges in the scenario - self.num_edges = 4 * ((self.col_num + 1) * self.row_num + self.col_num) - - # name of the scenario (DO NOT CHANGE) - self.name = "BobLoblawsLawBlog" - - super().__init__(name, vehicles, net_params, initial_config, - traffic_lights) - - def specify_nodes(self, net_params): - """See parent class.""" - return self._inner_nodes + self._outer_nodes - - def specify_edges(self, net_params): - """See parent class.""" - return self._inner_edges + self._outer_edges - - def specify_routes(self, net_params): - """See parent class.""" - routes = defaultdict(list) - - # build row routes (vehicles go from left to right and vice versa) - for i in range(self.row_num): - bot_id = "bot{}_0".format(i) - top_id = "top{}_{}".format(i, self.col_num) - for j in range(self.col_num + 1): - routes[bot_id] += ["bot{}_{}".format(i, j)] - routes[top_id] += ["top{}_{}".format(i, self.col_num - j)] - - # build column routes (vehicles go from top to bottom and vice versa) - for j in range(self.col_num): - left_id = "left{}_{}".format(self.row_num, j) - right_id = "right0_{}".format(j) - for i in range(self.row_num + 1): - routes[left_id] += ["left{}_{}".format(self.row_num - i, j)] - routes[right_id] += ["right{}_{}".format(i, j)] - - return routes - - def specify_types(self, net_params): - """See parent class.""" - types = [{ - "id": "horizontal", - "numLanes": self.horizontal_lanes, - "speed": self.speed_limit["horizontal"] - }, { - "id": "vertical", - "numLanes": self.vertical_lanes, - "speed": self.speed_limit["vertical"] - }] - - return types - - # =============================== - # ============ UTILS ============ - # =============================== - - @property - def _inner_nodes(self): - """Build out the inner nodes of the scenario. - - The inner nodes correspond to the intersections between the roads. They - are numbered from bottom left, increasing first across the columns and - then across the rows. - - For example, the nodes in a grid with 2 rows and 3 columns would be - indexed as follows: - - | | | - --- 3 --- 4 --- 5 --- - | | | - --- 0 --- 1 --- 2 --- - | | | - - The id of a node is then "center{index}", for instance "center0" for - node 0, "center1" for node 1 etc. - - Returns - ------- - list - List of inner nodes - """ - node_type = "traffic_light" if self.use_traffic_lights else "priority" - - nodes = [] - for row in range(self.row_num): - for col in range(self.col_num): - nodes.append({ - "id": "center{}".format(row * self.col_num + col), - "x": col * self.inner_length, - "y": row * self.inner_length, - "type": node_type, - "radius": self.inner_nodes_radius - }) - - return nodes - - @property - def _outer_nodes(self): - """Build out the outer nodes of the scenario. - - The outer nodes correspond to the extremities of the roads. There are - two at each extremity, one where the vehicles enter the scenario - (inflow) and one where the vehicles exit the scenario (outflow). - - Consider the following scenario with 2 rows and 3 columns, where the - extremities are marked by 'x', the rows are labeled from 0 to 1 and the - columns are labeled from 0 to 2: - - x x x - | | | - (1) x----|-----|-----|----x (*) - | | | - (0) x----|-----|-----|----x - | | | - x x x - (0) (1) (2) - - On row i, there are two nodes at the left extremity of the row, labeled - "left_row_short{i}" and "left_row_long{i}", as well as two nodes at the - right extremity labeled "right_row_short{i}" and "right_row_long{i}". - - On column j, there are two nodes at the bottom extremity of the column, - labeled "bot_col_short{j}" and "bot_col_long{j}", as well as two nodes - at the top extremity labeled "top_col_short{j}" and "top_col_long{j}". - - The "short" nodes correspond to where vehicles enter the network while - the "long" nodes correspond to where vehicles exit the network. - - For example, at extremity (*) on row (1): - - the id of the input node is "right_row_short1" - - the id of the output node is "right_row_long1" - - Returns - ------- - list - List of outer nodes - """ - nodes = [] - - def new_node(x, y, name, i): - return [{"id": name + str(i), "x": x, "y": y, "type": "priority"}] - - # build nodes at the extremities of columns - for col in range(self.col_num): - x = col * self.inner_length - y = (self.row_num - 1) * self.inner_length - nodes += new_node(x, - self.short_length, "bot_col_short", col) - nodes += new_node(x, - self.long_length, "bot_col_long", col) - nodes += new_node(x, y + self.short_length, "top_col_short", col) - nodes += new_node(x, y + self.long_length, "top_col_long", col) - - # build nodes at the extremities of rows - for row in range(self.row_num): - x = (self.col_num - 1) * self.inner_length - y = row * self.inner_length - nodes += new_node(- self.short_length, y, "left_row_short", row) - nodes += new_node(- self.long_length, y, "left_row_long", row) - nodes += new_node(x + self.short_length, y, "right_row_short", row) - nodes += new_node(x + self.long_length, y, "right_row_long", row) - - return nodes - - @property - def _inner_edges(self): - """Build out the inner edges of the scenario. - - The inner edges are the edges joining the inner nodes to each other. - - Consider the following scenario with n = 2 rows and m = 3 columns, - where the rows are indexed from 0 to 1 and the columns from 0 to 2, and - the inner nodes are marked by 'x': - - | | | - (1) ----x-----x-----x---- - | | | - (0) ----x-----x-(*)-x---- - | | | - (0) (1) (2) - - There are n * (m - 1) = 4 horizontal inner edges and (n - 1) * m = 3 - vertical inner edges, all that multiplied by two because each edge - consists of two roads going in opposite directions traffic-wise. - - On an horizontal edge, the id of the top road is "top{i}_{j}" and the - id of the bottom road is "bot{i}_{j}", where i is the index of the row - where the edge is and j is the index of the column to the right of it. - - On a vertical edge, the id of the right road is "right{i}_{j}" and the - id of the left road is "left{i}_{j}", where i is the index of the row - above the edge and j is the index of the column where the edge is. - - For example, on edge (*) on row (0): the id of the bottom road (traffic - going from left to right) is "bot0_2" and the id of the top road - (traffic going from right to left) is "top0_2". - - Returns - ------- - list - List of inner edges - """ - edges = [] - - def new_edge(index, from_node, to_node, orientation, lane): - return [{ - "id": lane + index, - "type": orientation, - "priority": 78, - "from": "center" + str(from_node), - "to": "center" + str(to_node), - "length": self.inner_length - }] - - # Build the horizontal inner edges - for i in range(self.row_num): - for j in range(self.col_num - 1): - node_index = i * self.col_num + j - index = "{}_{}".format(i, j + 1) - edges += new_edge(index, node_index + 1, node_index, - "horizontal", "top") - edges += new_edge(index, node_index, node_index + 1, - "horizontal", "bot") - - # Build the vertical inner edges - for i in range(self.row_num - 1): - for j in range(self.col_num): - node_index = i * self.col_num + j - index = "{}_{}".format(i + 1, j) - edges += new_edge(index, node_index, node_index + self.col_num, - "vertical", "right") - edges += new_edge(index, node_index + self.col_num, node_index, - "vertical", "left") - - return edges - - @property - def _outer_edges(self): - """Build out the outer edges of the scenario. - - The outer edges are the edges joining the inner nodes to the outer - nodes. - - Consider the following scenario with n = 2 rows and m = 3 columns, - where the rows are indexed from 0 to 1 and the columns from 0 to 2, the - inner nodes are marked by 'x' and the outer nodes by 'o': - - o o o - | | | - (1) o---x----x----x-(*)-o - | | | - (0) o---x----x----x-----o - | | | - o o o - (0) (1) (2) - - There are n * 2 = 4 horizontal outer edges and m * 2 = 6 vertical outer - edges, all that multiplied by two because each edge consists of two - roads going in opposite directions traffic-wise. - - On row i, there are four horizontal edges: the left ones labeled - "bot{i}_0" (in) and "top{i}_0" (out) and the right ones labeled - "bot{i}_{m}" (out) and "top{i}_{m}" (in). - - On column j, there are four vertical edges: the bottom ones labeled - "left0_{j}" (out) and "right0_{j}" (in) and the top ones labeled - "left{n}_{j}" (in) and "right{n}_{j}" (out). - - For example, on edge (*) on row (1): the id of the bottom road (out) - is "bot1_3" and the id of the top road is "top1_3". - - Edges labeled by "in" are edges where vehicles enter the network while - edges labeled by "out" are edges where vehicles exit the network. - - Returns - ------- - list - List of outer edges - """ - edges = [] - - def new_edge(index, from_node, to_node, orientation, length): - return [{ - "id": index, - "type": {"v": "vertical", "h": "horizontal"}[orientation], - "priority": 78, - "from": from_node, - "to": to_node, - "length": length - }] - - for i in range(self.col_num): - # bottom edges - id1 = "right0_{}".format(i) - id2 = "left0_{}".format(i) - node1 = "bot_col_short{}".format(i) - node2 = "center{}".format(i) - node3 = "bot_col_long{}".format(i) - edges += new_edge(id1, node1, node2, "v", self.short_length) - edges += new_edge(id2, node2, node3, "v", self.long_length) - - # top edges - id1 = "left{}_{}".format(self.row_num, i) - id2 = "right{}_{}".format(self.row_num, i) - node1 = "top_col_short{}".format(i) - node2 = "center{}".format((self.row_num - 1) * self.col_num + i) - node3 = "top_col_long{}".format(i) - edges += new_edge(id1, node1, node2, "v", self.short_length) - edges += new_edge(id2, node2, node3, "v", self.long_length) - - for j in range(self.row_num): - # left edges - id1 = "bot{}_0".format(j) - id2 = "top{}_0".format(j) - node1 = "left_row_short{}".format(j) - node2 = "center{}".format(j * self.col_num) - node3 = "left_row_long{}".format(j) - edges += new_edge(id1, node1, node2, "h", self.short_length) - edges += new_edge(id2, node2, node3, "h", self.long_length) - - # right edges - id1 = "top{}_{}".format(j, self.col_num) - id2 = "bot{}_{}".format(j, self.col_num) - node1 = "right_row_short{}".format(j) - node2 = "center{}".format((j + 1) * self.col_num - 1) - node3 = "right_row_long{}".format(j) - edges += new_edge(id1, node1, node2, "h", self.short_length) - edges += new_edge(id2, node2, node3, "h", self.long_length) - - return edges - - def specify_connections(self, net_params): - """Build out connections at each inner node. - - Connections describe what happens at the intersections. Here we link - lanes in straight lines, which means vehicles cannot turn at - intersections, they can only continue in a straight line. - """ - con_dict = {} - - def new_con(side, from_id, to_id, lane, signal_group): - return [{ - "from": side + from_id, - "to": side + to_id, - "fromLane": str(lane), - "toLane": str(lane), - "signal_group": signal_group - }] - - # build connections at each inner node - for i in range(self.row_num): - for j in range(self.col_num): - node_id = "{}_{}".format(i, j) - right_node_id = "{}_{}".format(i, j + 1) - top_node_id = "{}_{}".format(i + 1, j) - - conn = [] - for lane in range(self.vertical_lanes): - conn += new_con("bot", node_id, right_node_id, lane, 1) - conn += new_con("top", right_node_id, node_id, lane, 1) - for lane in range(self.horizontal_lanes): - conn += new_con("right", node_id, top_node_id, lane, 2) - conn += new_con("left", top_node_id, node_id, lane, 2) - - node_id = "center{}".format(i * self.col_num + j) - con_dict[node_id] = conn - - return con_dict - - # TODO necessary? - def specify_edge_starts(self): - """See parent class.""" - edgestarts = [] - for i in range(self.col_num + 1): - for j in range(self.row_num + 1): - index = "{}_{}".format(j, i) - if i != self.col_num: - edgestarts += [("left" + index, 0 + i * 50 + j * 5000), - ("right" + index, 10 + i * 50 + j * 5000)] - if j != self.row_num: - edgestarts += [("top" + index, 15 + i * 50 + j * 5000), - ("bot" + index, 20 + i * 50 + j * 5000)] - - return edgestarts - - # TODO necessary? - @staticmethod - def gen_custom_start_pos(cls, net_params, initial_config, num_vehicles): - """See parent class.""" - grid_array = net_params.additional_params["grid_array"] - row_num = grid_array["row_num"] - col_num = grid_array["col_num"] - cars_heading_left = grid_array["cars_left"] - cars_heading_right = grid_array["cars_right"] - cars_heading_top = grid_array["cars_top"] - cars_heading_bot = grid_array["cars_bot"] - - start_pos = [] - - x0 = 6 # position of the first car - dx = 10 # distance between each car - - start_lanes = [] - for i in range(col_num): - start_pos += [("right0_{}".format(i), x0 + k * dx) - for k in range(cars_heading_right)] - start_pos += [("left{}_{}".format(row_num, i), x0 + k * dx) - for k in range(cars_heading_left)] - horz_lanes = np.random.randint(low=0, high=net_params.additional_params["horizontal_lanes"], - size=cars_heading_left + cars_heading_right).tolist() - start_lanes += horz_lanes - - for i in range(row_num): - start_pos += [("top{}_{}".format(i, col_num), x0 + k * dx) - for k in range(cars_heading_top)] - start_pos += [("bot{}_0".format(i), x0 + k * dx) - for k in range(cars_heading_bot)] - vert_lanes = np.random.randint(low=0, high=net_params.additional_params["vertical_lanes"], - size=cars_heading_left + cars_heading_right).tolist() - start_lanes += vert_lanes - - return start_pos, start_lanes - - @property - def node_mapping(self): - """Map nodes to edges. - - Returns a list of pairs (node, connected edges) of all inner nodes - and for each of them, the 4 edges that leave this node. - - The nodes are listed in alphabetical order, and within that, edges are - listed in order: [bot, right, top, left]. - """ - mapping = {} - - for row in range(self.row_num): - for col in range(self.col_num): - node_id = "center{}".format(row * self.col_num + col) - - top_edge_id = "left{}_{}".format(row + 1, col) - bot_edge_id = "right{}_{}".format(row, col) - right_edge_id = "top{}_{}".format(row, col + 1) - left_edge_id = "bot{}_{}".format(row, col) - - mapping[node_id] = [left_edge_id, bot_edge_id, - right_edge_id, top_edge_id] - - return sorted(mapping.items(), key=lambda x: x[0]) + pass diff --git a/flow/scenarios/highway.py b/flow/scenarios/highway.py index e22a918200..10ff07707c 100644 --- a/flow/scenarios/highway.py +++ b/flow/scenarios/highway.py @@ -1,6 +1,6 @@ """Contains the highway scenario class.""" -from flow.scenarios.base_scenario import Scenario +from flow.scenarios.base import Scenario from flow.core.params import InitialConfig from flow.core.params import TrafficLightParams import numpy as np diff --git a/flow/scenarios/highway_ramps.py b/flow/scenarios/highway_ramps.py index 7799ff97e5..74f5ae25e2 100644 --- a/flow/scenarios/highway_ramps.py +++ b/flow/scenarios/highway_ramps.py @@ -1,6 +1,6 @@ """Contains the highway with ramps scenario class.""" -from flow.scenarios.base_scenario import Scenario +from flow.scenarios.base import Scenario from flow.core.params import InitialConfig, TrafficLightParams from collections import defaultdict from numpy import pi, sin, cos diff --git a/flow/scenarios/loop.py b/flow/scenarios/loop.py old mode 100755 new mode 100644 index 463e1e8172..96839218ee --- a/flow/scenarios/loop.py +++ b/flow/scenarios/loop.py @@ -1,216 +1,15 @@ -"""Contains the ring road scenario class.""" +"""Pending deprecation file. -from flow.scenarios.base_scenario import Scenario -from flow.core.params import InitialConfig -from flow.core.params import TrafficLightParams -from numpy import pi, sin, cos, linspace +To view the actual content, go to: flow/scenarios/ring.py +""" +from flow.utils.flow_warnings import deprecated +from flow.scenarios.ring import RingScenario +from flow.scenarios.ring import ADDITIONAL_NET_PARAMS # noqa: F401 -ADDITIONAL_NET_PARAMS = { - # length of the ring road - "length": 230, - # number of lanes - "lanes": 1, - # speed limit for all edges - "speed_limit": 30, - # resolution of the curves on the ring - "resolution": 40 -} +@deprecated('flow.scenarios.loop', + 'flow.scenarios.ring.RingScenario') +class LoopScenario(RingScenario): + """See parent class.""" -class LoopScenario(Scenario): - """Ring road scenario. - - This network consists of nodes at the top, bottom, left, and right - peripheries of the circles, connected by four 90 degree arcs. It is - parametrized by the length of the entire network and the number of lanes - and speed limit of the edges. - - Requires from net_params: - - * **length** : length of the circle - * **lanes** : number of lanes in the circle - * **speed_limit** : max speed limit of the circle - * **resolution** : number of nodes resolution - - Usage - ----- - >>> from flow.core.params import NetParams - >>> from flow.core.params import VehicleParams - >>> from flow.core.params import InitialConfig - >>> from flow.scenarios import LoopScenario - >>> - >>> scenario = LoopScenario( - >>> name='ring_road', - >>> vehicles=VehicleParams(), - >>> net_params=NetParams( - >>> additional_params={ - >>> 'length': 230, - >>> 'lanes': 1, - >>> 'speed_limit': 30, - >>> 'resolution': 40 - >>> }, - >>> ) - >>> ) - """ - - def __init__(self, - name, - vehicles, - net_params, - initial_config=InitialConfig(), - traffic_lights=TrafficLightParams()): - """Initialize a loop scenario.""" - for p in ADDITIONAL_NET_PARAMS.keys(): - if p not in net_params.additional_params: - raise KeyError('Network parameter "{}" not supplied'.format(p)) - - super().__init__(name, vehicles, net_params, initial_config, - traffic_lights) - - def specify_nodes(self, net_params): - """See parent class.""" - length = net_params.additional_params["length"] - r = length / (2 * pi) - - nodes = [{ - "id": "bottom", - "x": 0, - "y": -r - }, { - "id": "right", - "x": r, - "y": 0 - }, { - "id": "top", - "x": 0, - "y": r - }, { - "id": "left", - "x": -r, - "y": 0 - }] - - return nodes - - def specify_edges(self, net_params): - """See parent class.""" - length = net_params.additional_params["length"] - resolution = net_params.additional_params["resolution"] - r = length / (2 * pi) - edgelen = length / 4. - - edges = [{ - "id": - "bottom", - "type": - "edgeType", - "from": - "bottom", - "to": - "right", - "length": - edgelen, - "shape": - [ - (r * cos(t), r * sin(t)) - for t in linspace(-pi / 2, 0, resolution) - ] - }, { - "id": - "right", - "type": - "edgeType", - "from": - "right", - "to": - "top", - "length": - edgelen, - "shape": - [ - (r * cos(t), r * sin(t)) - for t in linspace(0, pi / 2, resolution) - ] - }, { - "id": - "top", - "type": - "edgeType", - "from": - "top", - "to": - "left", - "length": - edgelen, - "shape": - [ - (r * cos(t), r * sin(t)) - for t in linspace(pi / 2, pi, resolution) - ] - }, { - "id": - "left", - "type": - "edgeType", - "from": - "left", - "to": - "bottom", - "length": - edgelen, - "shape": - [ - (r * cos(t), r * sin(t)) - for t in linspace(pi, 3 * pi / 2, resolution) - ] - }] - - return edges - - def specify_types(self, net_params): - """See parent class.""" - lanes = net_params.additional_params["lanes"] - speed_limit = net_params.additional_params["speed_limit"] - - types = [{ - "id": "edgeType", - "numLanes": lanes, - "speed": speed_limit - }] - - return types - - def specify_routes(self, net_params): - """See parent class.""" - rts = { - "top": ["top", "left", "bottom", "right"], - "left": ["left", "bottom", "right", "top"], - "bottom": ["bottom", "right", "top", "left"], - "right": ["right", "top", "left", "bottom"] - } - - return rts - - def specify_edge_starts(self): - """See parent class.""" - ring_length = self.net_params.additional_params["length"] - junction_length = 0.1 # length of inter-edge junctions - - edgestarts = [("bottom", 0), - ("right", 0.25 * ring_length + junction_length), - ("top", 0.5 * ring_length + 2 * junction_length), - ("left", 0.75 * ring_length + 3 * junction_length)] - - return edgestarts - - def specify_internal_edge_starts(self): - """See parent class.""" - ring_length = self.net_params.additional_params["length"] - junction_length = 0.1 # length of inter-edge junctions - - edgestarts = [(":right_0", 0.25 * ring_length), - (":top_0", 0.5 * ring_length + junction_length), - (":left_0", 0.75 * ring_length + 2 * junction_length), - (":bottom_0", ring_length + 3 * junction_length)] - - return edgestarts + pass diff --git a/flow/scenarios/loop_merge.py b/flow/scenarios/loop_merge.py deleted file mode 100644 index 4b34b1cd18..0000000000 --- a/flow/scenarios/loop_merge.py +++ /dev/null @@ -1,267 +0,0 @@ -"""Contains the loop merge scenario class.""" - -from flow.scenarios.base_scenario import Scenario -from flow.core.params import InitialConfig -from flow.core.params import TrafficLightParams -from numpy import pi, sin, cos, linspace - -ADDITIONAL_NET_PARAMS = { - # radius of the loops - "ring_radius": 50, - # length of the straight edges connected the outer loop to the inner loop - "lane_length": 75, - # number of lanes in the inner loop - "inner_lanes": 3, - # number of lanes in the outer loop - "outer_lanes": 2, - # max speed limit in the network - "speed_limit": 30, - # resolution of the curved portions - "resolution": 40, -} - - -class TwoLoopsOneMergingScenario(Scenario): - """Two loop merge scenario. - - This network is expected to simulate a closed loop representation of a - merge. It consists of two rings that merge together for half the length of - the smaller ring. - - Requires from net_params: - - * **ring_radius** : radius of the loops - * **lane_length** : length of the straight edges connected the outer loop - to the inner loop - * **inner_lanes** : number of lanes in the inner loop - * **outer_lanes** : number of lanes in the outer loop - * **speed_limit** : max speed limit in the network - * **resolution** : resolution of the curved portions - - Usage - ----- - >>> from flow.core.params import NetParams - >>> from flow.core.params import VehicleParams - >>> from flow.core.params import InitialConfig - >>> from flow.scenarios import TwoLoopsOneMergingScenario - >>> - >>> scenario = TwoLoopsOneMergingScenario( - >>> name='two_loops_merge', - >>> vehicles=VehicleParams(), - >>> net_params=NetParams( - >>> additional_params={ - >>> 'ring_radius': 50, - >>> 'lane_length': 75, - >>> 'inner_lanes': 3, - >>> 'outer_lanes': 2, - >>> 'speed_limit': 30, - >>> 'resolution': 40 - >>> }, - >>> ) - >>> ) - """ - - def __init__(self, - name, - vehicles, - net_params, - initial_config=InitialConfig(), - traffic_lights=TrafficLightParams()): - """Initialize a two loop scenario.""" - for p in ADDITIONAL_NET_PARAMS.keys(): - if p not in net_params.additional_params: - raise KeyError('Network parameter "{}" not supplied'.format(p)) - - radius = net_params.additional_params["ring_radius"] - x = net_params.additional_params["lane_length"] - - self.inner_lanes = net_params.additional_params["inner_lanes"] - self.outer_lanes = net_params.additional_params["outer_lanes"] - - self.junction_length = 0.3 - self.intersection_length = 25.5 # calibrate when the radius changes - - net_params.additional_params["length"] = \ - 2 * x + 2 * pi * radius + \ - 2 * self.intersection_length + 2 * self.junction_length - - num_vehicles = vehicles.num_vehicles - num_merge_vehicles = sum("merge" in vehicles.get_type(veh_id) - for veh_id in vehicles.ids) - self.n_inner_vehicles = num_merge_vehicles - self.n_outer_vehicles = num_vehicles - num_merge_vehicles - - radius = net_params.additional_params["ring_radius"] - length_loop = 2 * pi * radius - self.length_loop = length_loop - - super().__init__(name, vehicles, net_params, initial_config, - traffic_lights) - - def specify_nodes(self, net_params): - """See parent class.""" - r = net_params.additional_params["ring_radius"] - x = net_params.additional_params["lane_length"] - - nodes = [{ - "id": "top_left", - "x": 0, - "y": r, - "type": "priority" - }, { - "id": "bottom_left", - "x": 0, - "y": -r, - "type": "priority" - }, { - "id": "top_right", - "x": x, - "y": r, - "type": "priority" - }, { - "id": "bottom_right", - "x": x, - "y": -r, - "type": "priority" - }] - - return nodes - - def specify_edges(self, net_params): - """See parent class.""" - r = net_params.additional_params["ring_radius"] - x = net_params.additional_params["lane_length"] - - ring_edgelen = pi * r - resolution = 40 - - edges = [{ - "id": - "center", - "from": - "bottom_left", - "to": - "top_left", - "type": - "edgeType", - "length": - ring_edgelen, - "priority": - 46, - "shape": - [ - (r * cos(t), r * sin(t)) - for t in linspace(-pi / 2, pi / 2, resolution) - ], - "numLanes": - self.inner_lanes - }, { - "id": "top", - "from": "top_right", - "to": "top_left", - "type": "edgeType", - "length": x, - "priority": 46, - "numLanes": self.outer_lanes - }, { - "id": "bottom", - "from": "bottom_left", - "to": "bottom_right", - "type": "edgeType", - "length": x, - "numLanes": self.outer_lanes - }, { - "id": - "left", - "from": - "top_left", - "to": - "bottom_left", - "type": - "edgeType", - "length": - ring_edgelen, - "shape": - [ - (r * cos(t), r * sin(t)) - for t in linspace(pi / 2, 3 * pi / 2, resolution) - ], - "numLanes": - self.inner_lanes - }, { - "id": - "right", - "from": - "bottom_right", - "to": - "top_right", - "type": - "edgeType", - "length": - ring_edgelen, - "shape": - [ - (x + r * cos(t), r * sin(t)) - for t in linspace(-pi / 2, pi / 2, resolution) - ], - "numLanes": - self.outer_lanes - }] - - return edges - - def specify_types(self, net_params): - """See parent class.""" - speed_limit = net_params.additional_params["speed_limit"] - - types = [{"id": "edgeType", "speed": speed_limit}] - return types - - def specify_routes(self, net_params): - """See parent class.""" - rts = { - "top": ["top", "left", "bottom", "right", "top"], - "bottom": ["bottom", "right", "top", "left", "bottom"], - "right": ["right", "top", "left", "bottom"], - "left": ["left", "center", "left"], - "center": ["center", "left", "center"] - } - - return rts - - def specify_edge_starts(self): - """See parent class.""" - r = self.net_params.additional_params["ring_radius"] - lane_length = self.net_params.additional_params["lane_length"] - - ring_edgelen = pi * r - - edgestarts = [ - ("left", self.intersection_length), - ("center", ring_edgelen + 2 * self.intersection_length), - ("bottom", 2 * ring_edgelen + 2 * self.intersection_length), - ("right", 2 * ring_edgelen + lane_length + - 2 * self.intersection_length + self.junction_length), - ("top", 3 * ring_edgelen + lane_length + - 2 * self.intersection_length + 2 * self.junction_length) - ] - - return edgestarts - - def specify_internal_edge_starts(self): - """See parent class.""" - r = self.net_params.additional_params["ring_radius"] - lane_length = self.net_params.additional_params["lane_length"] - - ring_edgelen = pi * r - - internal_edgestarts = [ - (":top_left", 0), (":bottom_left", - ring_edgelen + self.intersection_length), - (":bottom_right", - 2 * ring_edgelen + lane_length + 2 * self.intersection_length), - (":top_right", 3 * ring_edgelen + lane_length + - 2 * self.intersection_length + self.junction_length) - ] - - return internal_edgestarts diff --git a/flow/scenarios/merge.py b/flow/scenarios/merge.py index 3b53418c04..7f11d5f900 100644 --- a/flow/scenarios/merge.py +++ b/flow/scenarios/merge.py @@ -1,6 +1,6 @@ """Contains the merge scenario class.""" -from flow.scenarios.base_scenario import Scenario +from flow.scenarios.base import Scenario from flow.core.params import InitialConfig from flow.core.params import TrafficLightParams from numpy import pi, sin, cos diff --git a/flow/scenarios/minicity.py b/flow/scenarios/minicity.py index b5f4c8d022..a218385c6c 100644 --- a/flow/scenarios/minicity.py +++ b/flow/scenarios/minicity.py @@ -2,7 +2,7 @@ from flow.core.params import InitialConfig from flow.core.params import TrafficLightParams -from flow.scenarios.base_scenario import Scenario +from flow.scenarios.base import Scenario import numpy as np from numpy import linspace, pi, sin, cos diff --git a/flow/scenarios/multi_loop.py b/flow/scenarios/multi_loop.py index eeb55de9e0..2b7c6666d1 100644 --- a/flow/scenarios/multi_loop.py +++ b/flow/scenarios/multi_loop.py @@ -1,319 +1,15 @@ -"""Contains the ring road scenario class.""" +"""Pending deprecation file. -from flow.scenarios.base_scenario import Scenario -from flow.core.params import InitialConfig -from flow.core.params import TrafficLightParams -from numpy import pi, sin, cos, linspace, ceil, sqrt +To view the actual content, go to: flow/scenarios/multi_ring.py +""" +from flow.utils.flow_warnings import deprecated +from flow.scenarios.multi_ring import MultiRingScenario +from flow.scenarios.multi_ring import ADDITIONAL_NET_PARAMS # noqa: F401 -ADDITIONAL_NET_PARAMS = { - # length of the ring road - "length": 230, - # number of lanes - "lanes": 1, - # speed limit for all edges - "speed_limit": 30, - # resolution of the curves on the ring - "resolution": 40, - # number of rings in the system - "num_rings": 7 -} -VEHICLE_LENGTH = 5 # length of vehicles in the network, in meters +@deprecated('flow.scenarios.multi_loop', + 'flow.scenarios.multi_ring.MultiRingScenario') +class MultiLoopScenario(MultiRingScenario): + """See parent class.""" - -class MultiLoopScenario(Scenario): - """Ring road scenario. - - This network is similar to `LoopScenario`, but generates multiple separate - ring roads in the same simulation. - - Requires from net_params: - - * **length** : length of the circle - * **lanes** : number of lanes in the circle - * **speed_limit** : max speed limit of the circle - * **resolution** : number of nodes resolution - * **num_ring** : number of rings in the system - - Usage - ----- - >>> from flow.core.params import NetParams - >>> from flow.core.params import VehicleParams - >>> from flow.core.params import InitialConfig - >>> from flow.scenarios import MultiLoopScenario - >>> - >>> scenario = MultiLoopScenario( - >>> name='multi_ring_road', - >>> vehicles=VehicleParams(), - >>> net_params=NetParams( - >>> additional_params={ - >>> 'length': 230, - >>> 'lanes': 1, - >>> 'speed_limit': 30, - >>> 'resolution': 40, - >>> 'num_rings': 7 - >>> }, - >>> ) - >>> ) - """ - - def __init__(self, - name, - vehicles, - net_params, - initial_config=InitialConfig(), - traffic_lights=TrafficLightParams()): - """Initialize a loop scenario.""" - for p in ADDITIONAL_NET_PARAMS.keys(): - if p not in net_params.additional_params: - raise KeyError('Network parameter "{}" not supplied'.format(p)) - - self.length = net_params.additional_params["length"] - self.lanes = net_params.additional_params["lanes"] - self.num_rings = net_params.additional_params["num_rings"] - - super().__init__(name, vehicles, net_params, initial_config, - traffic_lights) - - def specify_edge_starts(self): - """See parent class.""" - edgelen = self.length / 4 - shift = 4 * edgelen - - edgestarts = [] - for i in range(self.num_rings): - edgestarts += [("bottom_{}".format(i), 0 + i * shift), - ("right_{}".format(i), edgelen + i * shift), - ("top_{}".format(i), 2 * edgelen + i * shift), - ("left_{}".format(i), 3 * edgelen + i * shift)] - - return edgestarts - - @staticmethod - def gen_custom_start_pos(cls, net_params, initial_config, num_vehicles): - """Generate uniformly spaced starting positions on each ring. - - It is assumed that there are an equal number of vehicles per ring. - If the perturbation term in initial_config is set to some positive - value, then the start positions are perturbed from a uniformly spaced - distribution by a gaussian whose std is equal to this perturbation - term. - """ - (x0, min_gap, bunching, lanes_distr, available_length, - available_edges, initial_config) = \ - cls._get_start_pos_util(initial_config, num_vehicles) - - length = net_params.additional_params["length"] - num_rings = net_params.additional_params["num_rings"] - increment = available_length / num_vehicles - vehs_per_ring = num_vehicles / num_rings - - x = x0 - car_count = 0 - startpositions, startlanes = [], [] - - # generate uniform starting positions - while car_count < num_vehicles: - # collect the position and lane number of each new vehicle - pos = cls.get_edge(x) - - # place vehicles side-by-side in all available lanes on this edge - for lane in range(min(cls.num_lanes(pos[0]), lanes_distr)): - car_count += 1 - startpositions.append(pos) - edge, pos = startpositions[-1] - startpositions[-1] = edge, pos % length - startlanes.append(lane) - - if car_count == num_vehicles: - break - - # 1e-13 prevents an extra car from being added in wrong place - x = (x + increment + VEHICLE_LENGTH + min_gap) + 1e-13 - if (car_count % vehs_per_ring) == 0: - # if we have put in the right number of cars, - # move onto the next ring - ring_num = int(car_count / vehs_per_ring) - x = length * ring_num + 1e-13 - - # add a perturbation to each vehicle, while not letting the vehicle - # leave its current edge - # if initial_config.perturbation > 0: - # for i in range(num_vehicles): - # perturb = np.random.normal(0, initial_config.perturbation) - # edge, pos = startpositions[i] - # pos = max(0, min(self.edge_length(edge), pos + perturb)) - # startpositions[i] = (edge, pos) - - return startpositions, startlanes - - def specify_nodes(self, net_params): - """See parent class.""" - length = net_params.additional_params["length"] - ring_num = net_params.additional_params["num_rings"] - - r = length / (2 * pi) - ring_spacing = 4 * r - num_rows = num_cols = int(ceil(sqrt(ring_num))) - - nodes = [] - i = 0 - for j in range(num_rows): - for k in range(num_cols): - nodes += [{ - "id": "bottom_{}".format(i), - "x": 0 + j * ring_spacing, - "y": -r + k * ring_spacing - }, { - "id": "right_{}".format(i), - "x": r + j * ring_spacing, - "y": 0 + k * ring_spacing - }, { - "id": "top_{}".format(i), - "x": 0 + j * ring_spacing, - "y": r + k * ring_spacing - }, { - "id": "left_{}".format(i), - "x": -r + j * ring_spacing, - "y": 0 + k * ring_spacing - }] - i += 1 - # FIXME this break if we don't have an exact square - if i >= ring_num: - break - if i >= ring_num: - break - - return nodes - - def specify_edges(self, net_params): - """See parent class.""" - length = net_params.additional_params["length"] - resolution = net_params.additional_params["resolution"] - ring_num = net_params.additional_params["num_rings"] - num_rows = num_cols = int(ceil(sqrt(ring_num))) - r = length / (2 * pi) - ring_spacing = 4 * r - edgelen = length / 4. - edges = [] - - i = 0 - - for j in range(num_rows): - for k in range(num_cols): - edges += [{ - "id": - "bottom_{}".format(i), - "type": - "edgeType", - "from": - "bottom_{}".format(i), - "to": - "right_{}".format(i), - "length": - edgelen, - "shape": - [ - (r * cos(t) + j * ring_spacing, - r * sin(t) + k * ring_spacing) - for t in linspace(-pi / 2, 0, resolution) - ] - }, { - "id": - "right_{}".format(i), - "type": - "edgeType", - "from": - "right_{}".format(i), - "to": - "top_{}".format(i), - "length": - edgelen, - "shape": - [ - (r * cos(t) + j * ring_spacing, - r * sin(t) + k * ring_spacing) - for t in linspace(0, pi / 2, resolution) - ] - }, { - "id": - "top_{}".format(i), - "type": - "edgeType", - "from": - "top_{}".format(i), - "to": - "left_{}".format(i), - "length": - edgelen, - "shape": - [ - (r * cos(t) + j * ring_spacing, - r * sin(t) + k * ring_spacing) - for t in linspace(pi / 2, pi, resolution) - ] - }, { - "id": - "left_{}".format(i), - "type": - "edgeType", - "from": - "left_{}".format(i), - "to": - "bottom_{}".format(i), - "length": - edgelen, - "shape": - [ - (r * cos(t) + j * ring_spacing, - r * sin(t) + k * ring_spacing) - for t in linspace(pi, 3 * pi / 2, resolution) - ] - }] - i += 1 - if i >= ring_num: - break - if i >= ring_num: - break - - return edges - - def specify_types(self, net_params): - """See parent class.""" - lanes = net_params.additional_params["lanes"] - speed_limit = net_params.additional_params["speed_limit"] - - types = [{ - "id": "edgeType", - "numLanes": lanes, - "speed": speed_limit - }] - - return types - - def specify_routes(self, net_params): - """See parent class.""" - ring_num = net_params.additional_params["num_rings"] - rts = {} - for i in range(ring_num): - rts.update({ - "top_{}".format(i): - ["top_{}".format(i), - "left_{}".format(i), - "bottom_{}".format(i), - "right_{}".format(i)], - "left_{}".format(i): ["left_{}".format(i), - "bottom_{}".format(i), - "right_{}".format(i), - "top_{}".format(i)], - "bottom_{}".format(i): ["bottom_{}".format(i), - "right_{}".format(i), - "top_{}".format(i), - "left_{}".format(i)], - "right_{}".format(i): ["right_{}".format(i), - "top_{}".format(i), - "left_{}".format(i), - "bottom_{}".format(i)] - }) - - return rts + pass diff --git a/flow/scenarios/multi_ring.py b/flow/scenarios/multi_ring.py new file mode 100644 index 0000000000..da487b76a1 --- /dev/null +++ b/flow/scenarios/multi_ring.py @@ -0,0 +1,319 @@ +"""Contains the ring road scenario class.""" + +from flow.scenarios.base import Scenario +from flow.core.params import InitialConfig +from flow.core.params import TrafficLightParams +from numpy import pi, sin, cos, linspace, ceil, sqrt + +ADDITIONAL_NET_PARAMS = { + # length of the ring road + "length": 230, + # number of lanes + "lanes": 1, + # speed limit for all edges + "speed_limit": 30, + # resolution of the curves on the ring + "resolution": 40, + # number of rings in the system + "num_rings": 7 +} + +VEHICLE_LENGTH = 5 # length of vehicles in the network, in meters + + +class MultiRingScenario(Scenario): + """Ring road scenario. + + This network is similar to `RingScenario`, but generates multiple separate + ring roads in the same simulation. + + Requires from net_params: + + * **length** : length of the circle + * **lanes** : number of lanes in the circle + * **speed_limit** : max speed limit of the circle + * **resolution** : number of nodes resolution + * **num_ring** : number of rings in the system + + Usage + ----- + >>> from flow.core.params import NetParams + >>> from flow.core.params import VehicleParams + >>> from flow.core.params import InitialConfig + >>> from flow.scenarios import MultiRingScenario + >>> + >>> scenario = MultiRingScenario( + >>> name='multi_ring_road', + >>> vehicles=VehicleParams(), + >>> net_params=NetParams( + >>> additional_params={ + >>> 'length': 230, + >>> 'lanes': 1, + >>> 'speed_limit': 30, + >>> 'resolution': 40, + >>> 'num_rings': 7 + >>> }, + >>> ) + >>> ) + """ + + def __init__(self, + name, + vehicles, + net_params, + initial_config=InitialConfig(), + traffic_lights=TrafficLightParams()): + """Initialize a ring scenario.""" + for p in ADDITIONAL_NET_PARAMS.keys(): + if p not in net_params.additional_params: + raise KeyError('Network parameter "{}" not supplied'.format(p)) + + self.length = net_params.additional_params["length"] + self.lanes = net_params.additional_params["lanes"] + self.num_rings = net_params.additional_params["num_rings"] + + super().__init__(name, vehicles, net_params, initial_config, + traffic_lights) + + def specify_edge_starts(self): + """See parent class.""" + edgelen = self.length / 4 + shift = 4 * edgelen + + edgestarts = [] + for i in range(self.num_rings): + edgestarts += [("bottom_{}".format(i), 0 + i * shift), + ("right_{}".format(i), edgelen + i * shift), + ("top_{}".format(i), 2 * edgelen + i * shift), + ("left_{}".format(i), 3 * edgelen + i * shift)] + + return edgestarts + + @staticmethod + def gen_custom_start_pos(cls, net_params, initial_config, num_vehicles): + """Generate uniformly spaced starting positions on each ring. + + It is assumed that there are an equal number of vehicles per ring. + If the perturbation term in initial_config is set to some positive + value, then the start positions are perturbed from a uniformly spaced + distribution by a gaussian whose std is equal to this perturbation + term. + """ + (x0, min_gap, bunching, lanes_distr, available_length, + available_edges, initial_config) = \ + cls._get_start_pos_util(initial_config, num_vehicles) + + length = net_params.additional_params["length"] + num_rings = net_params.additional_params["num_rings"] + increment = available_length / num_vehicles + vehs_per_ring = num_vehicles / num_rings + + x = x0 + car_count = 0 + startpositions, startlanes = [], [] + + # generate uniform starting positions + while car_count < num_vehicles: + # collect the position and lane number of each new vehicle + pos = cls.get_edge(x) + + # place vehicles side-by-side in all available lanes on this edge + for lane in range(min(cls.num_lanes(pos[0]), lanes_distr)): + car_count += 1 + startpositions.append(pos) + edge, pos = startpositions[-1] + startpositions[-1] = edge, pos % length + startlanes.append(lane) + + if car_count == num_vehicles: + break + + # 1e-13 prevents an extra car from being added in wrong place + x = (x + increment + VEHICLE_LENGTH + min_gap) + 1e-13 + if (car_count % vehs_per_ring) == 0: + # if we have put in the right number of cars, + # move onto the next ring + ring_num = int(car_count / vehs_per_ring) + x = length * ring_num + 1e-13 + + # add a perturbation to each vehicle, while not letting the vehicle + # leave its current edge + # if initial_config.perturbation > 0: + # for i in range(num_vehicles): + # perturb = np.random.normal(0, initial_config.perturbation) + # edge, pos = startpositions[i] + # pos = max(0, min(self.edge_length(edge), pos + perturb)) + # startpositions[i] = (edge, pos) + + return startpositions, startlanes + + def specify_nodes(self, net_params): + """See parent class.""" + length = net_params.additional_params["length"] + ring_num = net_params.additional_params["num_rings"] + + r = length / (2 * pi) + ring_spacing = 4 * r + num_rows = num_cols = int(ceil(sqrt(ring_num))) + + nodes = [] + i = 0 + for j in range(num_rows): + for k in range(num_cols): + nodes += [{ + "id": "bottom_{}".format(i), + "x": 0 + j * ring_spacing, + "y": -r + k * ring_spacing + }, { + "id": "right_{}".format(i), + "x": r + j * ring_spacing, + "y": 0 + k * ring_spacing + }, { + "id": "top_{}".format(i), + "x": 0 + j * ring_spacing, + "y": r + k * ring_spacing + }, { + "id": "left_{}".format(i), + "x": -r + j * ring_spacing, + "y": 0 + k * ring_spacing + }] + i += 1 + # FIXME this break if we don't have an exact square + if i >= ring_num: + break + if i >= ring_num: + break + + return nodes + + def specify_edges(self, net_params): + """See parent class.""" + length = net_params.additional_params["length"] + resolution = net_params.additional_params["resolution"] + ring_num = net_params.additional_params["num_rings"] + num_rows = num_cols = int(ceil(sqrt(ring_num))) + r = length / (2 * pi) + ring_spacing = 4 * r + edgelen = length / 4. + edges = [] + + i = 0 + + for j in range(num_rows): + for k in range(num_cols): + edges += [{ + "id": + "bottom_{}".format(i), + "type": + "edgeType", + "from": + "bottom_{}".format(i), + "to": + "right_{}".format(i), + "length": + edgelen, + "shape": + [ + (r * cos(t) + j * ring_spacing, + r * sin(t) + k * ring_spacing) + for t in linspace(-pi / 2, 0, resolution) + ] + }, { + "id": + "right_{}".format(i), + "type": + "edgeType", + "from": + "right_{}".format(i), + "to": + "top_{}".format(i), + "length": + edgelen, + "shape": + [ + (r * cos(t) + j * ring_spacing, + r * sin(t) + k * ring_spacing) + for t in linspace(0, pi / 2, resolution) + ] + }, { + "id": + "top_{}".format(i), + "type": + "edgeType", + "from": + "top_{}".format(i), + "to": + "left_{}".format(i), + "length": + edgelen, + "shape": + [ + (r * cos(t) + j * ring_spacing, + r * sin(t) + k * ring_spacing) + for t in linspace(pi / 2, pi, resolution) + ] + }, { + "id": + "left_{}".format(i), + "type": + "edgeType", + "from": + "left_{}".format(i), + "to": + "bottom_{}".format(i), + "length": + edgelen, + "shape": + [ + (r * cos(t) + j * ring_spacing, + r * sin(t) + k * ring_spacing) + for t in linspace(pi, 3 * pi / 2, resolution) + ] + }] + i += 1 + if i >= ring_num: + break + if i >= ring_num: + break + + return edges + + def specify_types(self, net_params): + """See parent class.""" + lanes = net_params.additional_params["lanes"] + speed_limit = net_params.additional_params["speed_limit"] + + types = [{ + "id": "edgeType", + "numLanes": lanes, + "speed": speed_limit + }] + + return types + + def specify_routes(self, net_params): + """See parent class.""" + ring_num = net_params.additional_params["num_rings"] + rts = {} + for i in range(ring_num): + rts.update({ + "top_{}".format(i): + ["top_{}".format(i), + "left_{}".format(i), + "bottom_{}".format(i), + "right_{}".format(i)], + "left_{}".format(i): ["left_{}".format(i), + "bottom_{}".format(i), + "right_{}".format(i), + "top_{}".format(i)], + "bottom_{}".format(i): ["bottom_{}".format(i), + "right_{}".format(i), + "top_{}".format(i), + "left_{}".format(i)], + "right_{}".format(i): ["right_{}".format(i), + "top_{}".format(i), + "left_{}".format(i), + "bottom_{}".format(i)] + }) + + return rts diff --git a/flow/scenarios/ring.py b/flow/scenarios/ring.py new file mode 100755 index 0000000000..eec4953196 --- /dev/null +++ b/flow/scenarios/ring.py @@ -0,0 +1,216 @@ +"""Contains the ring road scenario class.""" + +from flow.scenarios.base import Scenario +from flow.core.params import InitialConfig +from flow.core.params import TrafficLightParams +from numpy import pi, sin, cos, linspace + +ADDITIONAL_NET_PARAMS = { + # length of the ring road + "length": 230, + # number of lanes + "lanes": 1, + # speed limit for all edges + "speed_limit": 30, + # resolution of the curves on the ring + "resolution": 40 +} + + +class RingScenario(Scenario): + """Ring road scenario. + + This network consists of nodes at the top, bottom, left, and right + peripheries of the circles, connected by four 90 degree arcs. It is + parametrized by the length of the entire network and the number of lanes + and speed limit of the edges. + + Requires from net_params: + + * **length** : length of the circle + * **lanes** : number of lanes in the circle + * **speed_limit** : max speed limit of the circle + * **resolution** : number of nodes resolution + + Usage + ----- + >>> from flow.core.params import NetParams + >>> from flow.core.params import VehicleParams + >>> from flow.core.params import InitialConfig + >>> from flow.scenarios import RingScenario + >>> + >>> scenario = RingScenario( + >>> name='ring_road', + >>> vehicles=VehicleParams(), + >>> net_params=NetParams( + >>> additional_params={ + >>> 'length': 230, + >>> 'lanes': 1, + >>> 'speed_limit': 30, + >>> 'resolution': 40 + >>> }, + >>> ) + >>> ) + """ + + def __init__(self, + name, + vehicles, + net_params, + initial_config=InitialConfig(), + traffic_lights=TrafficLightParams()): + """Initialize a ring scenario.""" + for p in ADDITIONAL_NET_PARAMS.keys(): + if p not in net_params.additional_params: + raise KeyError('Network parameter "{}" not supplied'.format(p)) + + super().__init__(name, vehicles, net_params, initial_config, + traffic_lights) + + def specify_nodes(self, net_params): + """See parent class.""" + length = net_params.additional_params["length"] + r = length / (2 * pi) + + nodes = [{ + "id": "bottom", + "x": 0, + "y": -r + }, { + "id": "right", + "x": r, + "y": 0 + }, { + "id": "top", + "x": 0, + "y": r + }, { + "id": "left", + "x": -r, + "y": 0 + }] + + return nodes + + def specify_edges(self, net_params): + """See parent class.""" + length = net_params.additional_params["length"] + resolution = net_params.additional_params["resolution"] + r = length / (2 * pi) + edgelen = length / 4. + + edges = [{ + "id": + "bottom", + "type": + "edgeType", + "from": + "bottom", + "to": + "right", + "length": + edgelen, + "shape": + [ + (r * cos(t), r * sin(t)) + for t in linspace(-pi / 2, 0, resolution) + ] + }, { + "id": + "right", + "type": + "edgeType", + "from": + "right", + "to": + "top", + "length": + edgelen, + "shape": + [ + (r * cos(t), r * sin(t)) + for t in linspace(0, pi / 2, resolution) + ] + }, { + "id": + "top", + "type": + "edgeType", + "from": + "top", + "to": + "left", + "length": + edgelen, + "shape": + [ + (r * cos(t), r * sin(t)) + for t in linspace(pi / 2, pi, resolution) + ] + }, { + "id": + "left", + "type": + "edgeType", + "from": + "left", + "to": + "bottom", + "length": + edgelen, + "shape": + [ + (r * cos(t), r * sin(t)) + for t in linspace(pi, 3 * pi / 2, resolution) + ] + }] + + return edges + + def specify_types(self, net_params): + """See parent class.""" + lanes = net_params.additional_params["lanes"] + speed_limit = net_params.additional_params["speed_limit"] + + types = [{ + "id": "edgeType", + "numLanes": lanes, + "speed": speed_limit + }] + + return types + + def specify_routes(self, net_params): + """See parent class.""" + rts = { + "top": ["top", "left", "bottom", "right"], + "left": ["left", "bottom", "right", "top"], + "bottom": ["bottom", "right", "top", "left"], + "right": ["right", "top", "left", "bottom"] + } + + return rts + + def specify_edge_starts(self): + """See parent class.""" + ring_length = self.net_params.additional_params["length"] + junction_length = 0.1 # length of inter-edge junctions + + edgestarts = [("bottom", 0), + ("right", 0.25 * ring_length + junction_length), + ("top", 0.5 * ring_length + 2 * junction_length), + ("left", 0.75 * ring_length + 3 * junction_length)] + + return edgestarts + + def specify_internal_edge_starts(self): + """See parent class.""" + ring_length = self.net_params.additional_params["length"] + junction_length = 0.1 # length of inter-edge junctions + + edgestarts = [(":right_0", 0.25 * ring_length), + (":top_0", 0.5 * ring_length + junction_length), + (":left_0", 0.75 * ring_length + 2 * junction_length), + (":bottom_0", ring_length + 3 * junction_length)] + + return edgestarts diff --git a/flow/scenarios/traffic_light_grid.py b/flow/scenarios/traffic_light_grid.py new file mode 100644 index 0000000000..c9af6073eb --- /dev/null +++ b/flow/scenarios/traffic_light_grid.py @@ -0,0 +1,603 @@ +"""Contains the traffic light grid scenario class.""" + +from flow.scenarios.base import Scenario +from flow.core.params import InitialConfig +from flow.core.params import TrafficLightParams +from collections import defaultdict +import numpy as np + +ADDITIONAL_NET_PARAMS = { + # dictionary of traffic light grid array data + "grid_array": { + # number of horizontal rows of edges + "row_num": 3, + # number of vertical columns of edges + "col_num": 2, + # length of inner edges in the traffic light grid network + "inner_length": None, + # length of edges where vehicles enter the network + "short_length": None, + # length of edges where vehicles exit the network + "long_length": None, + # number of cars starting at the edges heading to the top + "cars_top": 20, + # number of cars starting at the edges heading to the bottom + "cars_bot": 20, + # number of cars starting at the edges heading to the left + "cars_left": 20, + # number of cars starting at the edges heading to the right + "cars_right": 20, + }, + # number of lanes in the horizontal edges + "horizontal_lanes": 1, + # number of lanes in the vertical edges + "vertical_lanes": 1, + # speed limit for all edges, may be represented as a float value, or a + # dictionary with separate values for vertical and horizontal lanes + "speed_limit": { + "horizontal": 35, + "vertical": 35 + } +} + + +class TrafficLightGridScenario(Scenario): + """Traffic Light Grid scenario class. + + The traffic light grid scenario consists of m vertical lanes and n + horizontal lanes, with a total of nxm intersections where the vertical + and horizontal edges meet. + + Requires from net_params: + + * **grid_array** : dictionary of grid array data, with the following keys + + * **row_num** : number of horizontal rows of edges + * **col_num** : number of vertical columns of edges + * **inner_length** : length of inner edges in traffic light grid network + * **short_length** : length of edges that vehicles start on + * **long_length** : length of final edge in route + * **cars_top** : number of cars starting at the edges heading to the top + * **cars_bot** : number of cars starting at the edges heading to the + bottom + * **cars_left** : number of cars starting at the edges heading to the + left + * **cars_right** : number of cars starting at the edges heading to the + right + + * **horizontal_lanes** : number of lanes in the horizontal edges + * **vertical_lanes** : number of lanes in the vertical edges + * **speed_limit** : speed limit for all edges. This may be represented as a + float value, or a dictionary with separate values for vertical and + horizontal lanes. + + Usage + ----- + >>> from flow.core.params import NetParams + >>> from flow.core.params import VehicleParams + >>> from flow.core.params import InitialConfig + >>> from flow.scenarios import TrafficLightGridScenario + >>> + >>> scenario = TrafficLightGridScenario( + >>> name='grid', + >>> vehicles=VehicleParams(), + >>> net_params=NetParams( + >>> additional_params={ + >>> 'grid_array': { + >>> 'row_num': 3, + >>> 'col_num': 2, + >>> 'inner_length': 500, + >>> 'short_length': 500, + >>> 'long_length': 500, + >>> 'cars_top': 20, + >>> 'cars_bot': 20, + >>> 'cars_left': 20, + >>> 'cars_right': 20, + >>> }, + >>> 'horizontal_lanes': 1, + >>> 'vertical_lanes': 1, + >>> 'speed_limit': { + >>> 'vertical': 35, + >>> 'horizontal': 35 + >>> } + >>> }, + >>> ) + >>> ) + """ + + def __init__(self, + name, + vehicles, + net_params, + initial_config=InitialConfig(), + traffic_lights=TrafficLightParams()): + """Initialize an n*m traffic light grid scenario.""" + optional = ["tl_logic"] + for p in ADDITIONAL_NET_PARAMS.keys(): + if p not in net_params.additional_params and p not in optional: + raise KeyError('Network parameter "{}" not supplied'.format(p)) + + for p in ADDITIONAL_NET_PARAMS["grid_array"].keys(): + if p not in net_params.additional_params["grid_array"]: + raise KeyError( + 'Grid array parameter "{}" not supplied'.format(p)) + + # retrieve all additional parameters + # refer to the ADDITIONAL_NET_PARAMS dict for more documentation + self.vertical_lanes = net_params.additional_params["vertical_lanes"] + self.horizontal_lanes = net_params.additional_params[ + "horizontal_lanes"] + self.speed_limit = net_params.additional_params["speed_limit"] + if not isinstance(self.speed_limit, dict): + self.speed_limit = { + "horizontal": self.speed_limit, + "vertical": self.speed_limit + } + + self.grid_array = net_params.additional_params["grid_array"] + self.row_num = self.grid_array["row_num"] + self.col_num = self.grid_array["col_num"] + self.inner_length = self.grid_array["inner_length"] + self.short_length = self.grid_array["short_length"] + self.long_length = self.grid_array["long_length"] + self.cars_heading_top = self.grid_array["cars_top"] + self.cars_heading_bot = self.grid_array["cars_bot"] + self.cars_heading_left = self.grid_array["cars_left"] + self.cars_heading_right = self.grid_array["cars_right"] + + # specifies whether or not there will be traffic lights at the + # intersections (True by default) + self.use_traffic_lights = net_params.additional_params.get( + "traffic_lights", True) + + # radius of the inner nodes (ie of the intersections) + self.inner_nodes_radius = 2.9 + 3.3 * max(self.vertical_lanes, + self.horizontal_lanes) + + # total number of edges in the scenario + self.num_edges = 4 * ((self.col_num + 1) * self.row_num + self.col_num) + + # name of the scenario (DO NOT CHANGE) + self.name = "BobLoblawsLawBlog" + + super().__init__(name, vehicles, net_params, initial_config, + traffic_lights) + + def specify_nodes(self, net_params): + """See parent class.""" + return self._inner_nodes + self._outer_nodes + + def specify_edges(self, net_params): + """See parent class.""" + return self._inner_edges + self._outer_edges + + def specify_routes(self, net_params): + """See parent class.""" + routes = defaultdict(list) + + # build row routes (vehicles go from left to right and vice versa) + for i in range(self.row_num): + bot_id = "bot{}_0".format(i) + top_id = "top{}_{}".format(i, self.col_num) + for j in range(self.col_num + 1): + routes[bot_id] += ["bot{}_{}".format(i, j)] + routes[top_id] += ["top{}_{}".format(i, self.col_num - j)] + + # build column routes (vehicles go from top to bottom and vice versa) + for j in range(self.col_num): + left_id = "left{}_{}".format(self.row_num, j) + right_id = "right0_{}".format(j) + for i in range(self.row_num + 1): + routes[left_id] += ["left{}_{}".format(self.row_num - i, j)] + routes[right_id] += ["right{}_{}".format(i, j)] + + return routes + + def specify_types(self, net_params): + """See parent class.""" + types = [{ + "id": "horizontal", + "numLanes": self.horizontal_lanes, + "speed": self.speed_limit["horizontal"] + }, { + "id": "vertical", + "numLanes": self.vertical_lanes, + "speed": self.speed_limit["vertical"] + }] + + return types + + # =============================== + # ============ UTILS ============ + # =============================== + + @property + def _inner_nodes(self): + """Build out the inner nodes of the scenario. + + The inner nodes correspond to the intersections between the roads. They + are numbered from bottom left, increasing first across the columns and + then across the rows. + + For example, the nodes in a traffic light grid with 2 rows and 3 columns + would be indexed as follows: + + | | | + --- 3 --- 4 --- 5 --- + | | | + --- 0 --- 1 --- 2 --- + | | | + + The id of a node is then "center{index}", for instance "center0" for + node 0, "center1" for node 1 etc. + + Returns + ------- + list + List of inner nodes + """ + node_type = "traffic_light" if self.use_traffic_lights else "priority" + + nodes = [] + for row in range(self.row_num): + for col in range(self.col_num): + nodes.append({ + "id": "center{}".format(row * self.col_num + col), + "x": col * self.inner_length, + "y": row * self.inner_length, + "type": node_type, + "radius": self.inner_nodes_radius + }) + + return nodes + + @property + def _outer_nodes(self): + """Build out the outer nodes of the scenario. + + The outer nodes correspond to the extremities of the roads. There are + two at each extremity, one where the vehicles enter the scenario + (inflow) and one where the vehicles exit the scenario (outflow). + + Consider the following scenario with 2 rows and 3 columns, where the + extremities are marked by 'x', the rows are labeled from 0 to 1 and the + columns are labeled from 0 to 2: + + x x x + | | | + (1) x----|-----|-----|----x (*) + | | | + (0) x----|-----|-----|----x + | | | + x x x + (0) (1) (2) + + On row i, there are two nodes at the left extremity of the row, labeled + "left_row_short{i}" and "left_row_long{i}", as well as two nodes at the + right extremity labeled "right_row_short{i}" and "right_row_long{i}". + + On column j, there are two nodes at the bottom extremity of the column, + labeled "bot_col_short{j}" and "bot_col_long{j}", as well as two nodes + at the top extremity labeled "top_col_short{j}" and "top_col_long{j}". + + The "short" nodes correspond to where vehicles enter the network while + the "long" nodes correspond to where vehicles exit the network. + + For example, at extremity (*) on row (1): + - the id of the input node is "right_row_short1" + - the id of the output node is "right_row_long1" + + Returns + ------- + list + List of outer nodes + """ + nodes = [] + + def new_node(x, y, name, i): + return [{"id": name + str(i), "x": x, "y": y, "type": "priority"}] + + # build nodes at the extremities of columns + for col in range(self.col_num): + x = col * self.inner_length + y = (self.row_num - 1) * self.inner_length + nodes += new_node(x, - self.short_length, "bot_col_short", col) + nodes += new_node(x, - self.long_length, "bot_col_long", col) + nodes += new_node(x, y + self.short_length, "top_col_short", col) + nodes += new_node(x, y + self.long_length, "top_col_long", col) + + # build nodes at the extremities of rows + for row in range(self.row_num): + x = (self.col_num - 1) * self.inner_length + y = row * self.inner_length + nodes += new_node(- self.short_length, y, "left_row_short", row) + nodes += new_node(- self.long_length, y, "left_row_long", row) + nodes += new_node(x + self.short_length, y, "right_row_short", row) + nodes += new_node(x + self.long_length, y, "right_row_long", row) + + return nodes + + @property + def _inner_edges(self): + """Build out the inner edges of the scenario. + + The inner edges are the edges joining the inner nodes to each other. + + Consider the following scenario with n = 2 rows and m = 3 columns, + where the rows are indexed from 0 to 1 and the columns from 0 to 2, and + the inner nodes are marked by 'x': + + | | | + (1) ----x-----x-----x---- + | | | + (0) ----x-----x-(*)-x---- + | | | + (0) (1) (2) + + There are n * (m - 1) = 4 horizontal inner edges and (n - 1) * m = 3 + vertical inner edges, all that multiplied by two because each edge + consists of two roads going in opposite directions traffic-wise. + + On an horizontal edge, the id of the top road is "top{i}_{j}" and the + id of the bottom road is "bot{i}_{j}", where i is the index of the row + where the edge is and j is the index of the column to the right of it. + + On a vertical edge, the id of the right road is "right{i}_{j}" and the + id of the left road is "left{i}_{j}", where i is the index of the row + above the edge and j is the index of the column where the edge is. + + For example, on edge (*) on row (0): the id of the bottom road (traffic + going from left to right) is "bot0_2" and the id of the top road + (traffic going from right to left) is "top0_2". + + Returns + ------- + list + List of inner edges + """ + edges = [] + + def new_edge(index, from_node, to_node, orientation, lane): + return [{ + "id": lane + index, + "type": orientation, + "priority": 78, + "from": "center" + str(from_node), + "to": "center" + str(to_node), + "length": self.inner_length + }] + + # Build the horizontal inner edges + for i in range(self.row_num): + for j in range(self.col_num - 1): + node_index = i * self.col_num + j + index = "{}_{}".format(i, j + 1) + edges += new_edge(index, node_index + 1, node_index, + "horizontal", "top") + edges += new_edge(index, node_index, node_index + 1, + "horizontal", "bot") + + # Build the vertical inner edges + for i in range(self.row_num - 1): + for j in range(self.col_num): + node_index = i * self.col_num + j + index = "{}_{}".format(i + 1, j) + edges += new_edge(index, node_index, node_index + self.col_num, + "vertical", "right") + edges += new_edge(index, node_index + self.col_num, node_index, + "vertical", "left") + + return edges + + @property + def _outer_edges(self): + """Build out the outer edges of the scenario. + + The outer edges are the edges joining the inner nodes to the outer + nodes. + + Consider the following scenario with n = 2 rows and m = 3 columns, + where the rows are indexed from 0 to 1 and the columns from 0 to 2, the + inner nodes are marked by 'x' and the outer nodes by 'o': + + o o o + | | | + (1) o---x----x----x-(*)-o + | | | + (0) o---x----x----x-----o + | | | + o o o + (0) (1) (2) + + There are n * 2 = 4 horizontal outer edges and m * 2 = 6 vertical outer + edges, all that multiplied by two because each edge consists of two + roads going in opposite directions traffic-wise. + + On row i, there are four horizontal edges: the left ones labeled + "bot{i}_0" (in) and "top{i}_0" (out) and the right ones labeled + "bot{i}_{m}" (out) and "top{i}_{m}" (in). + + On column j, there are four vertical edges: the bottom ones labeled + "left0_{j}" (out) and "right0_{j}" (in) and the top ones labeled + "left{n}_{j}" (in) and "right{n}_{j}" (out). + + For example, on edge (*) on row (1): the id of the bottom road (out) + is "bot1_3" and the id of the top road is "top1_3". + + Edges labeled by "in" are edges where vehicles enter the network while + edges labeled by "out" are edges where vehicles exit the network. + + Returns + ------- + list + List of outer edges + """ + edges = [] + + def new_edge(index, from_node, to_node, orientation, length): + return [{ + "id": index, + "type": {"v": "vertical", "h": "horizontal"}[orientation], + "priority": 78, + "from": from_node, + "to": to_node, + "length": length + }] + + for i in range(self.col_num): + # bottom edges + id1 = "right0_{}".format(i) + id2 = "left0_{}".format(i) + node1 = "bot_col_short{}".format(i) + node2 = "center{}".format(i) + node3 = "bot_col_long{}".format(i) + edges += new_edge(id1, node1, node2, "v", self.short_length) + edges += new_edge(id2, node2, node3, "v", self.long_length) + + # top edges + id1 = "left{}_{}".format(self.row_num, i) + id2 = "right{}_{}".format(self.row_num, i) + node1 = "top_col_short{}".format(i) + node2 = "center{}".format((self.row_num - 1) * self.col_num + i) + node3 = "top_col_long{}".format(i) + edges += new_edge(id1, node1, node2, "v", self.short_length) + edges += new_edge(id2, node2, node3, "v", self.long_length) + + for j in range(self.row_num): + # left edges + id1 = "bot{}_0".format(j) + id2 = "top{}_0".format(j) + node1 = "left_row_short{}".format(j) + node2 = "center{}".format(j * self.col_num) + node3 = "left_row_long{}".format(j) + edges += new_edge(id1, node1, node2, "h", self.short_length) + edges += new_edge(id2, node2, node3, "h", self.long_length) + + # right edges + id1 = "top{}_{}".format(j, self.col_num) + id2 = "bot{}_{}".format(j, self.col_num) + node1 = "right_row_short{}".format(j) + node2 = "center{}".format((j + 1) * self.col_num - 1) + node3 = "right_row_long{}".format(j) + edges += new_edge(id1, node1, node2, "h", self.short_length) + edges += new_edge(id2, node2, node3, "h", self.long_length) + + return edges + + def specify_connections(self, net_params): + """Build out connections at each inner node. + + Connections describe what happens at the intersections. Here we link + lanes in straight lines, which means vehicles cannot turn at + intersections, they can only continue in a straight line. + """ + con_dict = {} + + def new_con(side, from_id, to_id, lane, signal_group): + return [{ + "from": side + from_id, + "to": side + to_id, + "fromLane": str(lane), + "toLane": str(lane), + "signal_group": signal_group + }] + + # build connections at each inner node + for i in range(self.row_num): + for j in range(self.col_num): + node_id = "{}_{}".format(i, j) + right_node_id = "{}_{}".format(i, j + 1) + top_node_id = "{}_{}".format(i + 1, j) + + conn = [] + for lane in range(self.vertical_lanes): + conn += new_con("bot", node_id, right_node_id, lane, 1) + conn += new_con("top", right_node_id, node_id, lane, 1) + for lane in range(self.horizontal_lanes): + conn += new_con("right", node_id, top_node_id, lane, 2) + conn += new_con("left", top_node_id, node_id, lane, 2) + + node_id = "center{}".format(i * self.col_num + j) + con_dict[node_id] = conn + + return con_dict + + # TODO necessary? + def specify_edge_starts(self): + """See parent class.""" + edgestarts = [] + for i in range(self.col_num + 1): + for j in range(self.row_num + 1): + index = "{}_{}".format(j, i) + if i != self.col_num: + edgestarts += [("left" + index, 0 + i * 50 + j * 5000), + ("right" + index, 10 + i * 50 + j * 5000)] + if j != self.row_num: + edgestarts += [("top" + index, 15 + i * 50 + j * 5000), + ("bot" + index, 20 + i * 50 + j * 5000)] + + return edgestarts + + # TODO necessary? + @staticmethod + def gen_custom_start_pos(cls, net_params, initial_config, num_vehicles): + """See parent class.""" + grid_array = net_params.additional_params["grid_array"] + row_num = grid_array["row_num"] + col_num = grid_array["col_num"] + cars_heading_left = grid_array["cars_left"] + cars_heading_right = grid_array["cars_right"] + cars_heading_top = grid_array["cars_top"] + cars_heading_bot = grid_array["cars_bot"] + + start_pos = [] + + x0 = 6 # position of the first car + dx = 10 # distance between each car + + start_lanes = [] + for i in range(col_num): + start_pos += [("right0_{}".format(i), x0 + k * dx) + for k in range(cars_heading_right)] + start_pos += [("left{}_{}".format(row_num, i), x0 + k * dx) + for k in range(cars_heading_left)] + horz_lanes = np.random.randint(low=0, high=net_params.additional_params["horizontal_lanes"], + size=cars_heading_left + cars_heading_right).tolist() + start_lanes += horz_lanes + + for i in range(row_num): + start_pos += [("top{}_{}".format(i, col_num), x0 + k * dx) + for k in range(cars_heading_top)] + start_pos += [("bot{}_0".format(i), x0 + k * dx) + for k in range(cars_heading_bot)] + vert_lanes = np.random.randint(low=0, high=net_params.additional_params["vertical_lanes"], + size=cars_heading_left + cars_heading_right).tolist() + start_lanes += vert_lanes + + return start_pos, start_lanes + + @property + def node_mapping(self): + """Map nodes to edges. + + Returns a list of pairs (node, connected edges) of all inner nodes + and for each of them, the 4 edges that leave this node. + + The nodes are listed in alphabetical order, and within that, edges are + listed in order: [bot, right, top, left]. + """ + mapping = {} + + for row in range(self.row_num): + for col in range(self.col_num): + node_id = "center{}".format(row * self.col_num + col) + + top_edge_id = "left{}_{}".format(row + 1, col) + bot_edge_id = "right{}_{}".format(row, col) + right_edge_id = "top{}_{}".format(row, col + 1) + left_edge_id = "bot{}_{}".format(row, col) + + mapping[node_id] = [left_edge_id, bot_edge_id, + right_edge_id, top_edge_id] + + return sorted(mapping.items(), key=lambda x: x[0]) diff --git a/flow/utils/aimsun/run.py b/flow/utils/aimsun/run.py index 4105082f24..adc9fa097a 100644 --- a/flow/utils/aimsun/run.py +++ b/flow/utils/aimsun/run.py @@ -126,7 +126,7 @@ def threaded_client(conn): # convert to integer data = int(data) - # if the simulation step is over, terminate the loop and let + # if the simulation step is over, terminate the ring and let # the step be executed if data == ac.SIMULATION_STEP: send_message(conn, in_format='i', values=(0,)) diff --git a/flow/utils/flow_warnings.py b/flow/utils/flow_warnings.py index 066f61ffeb..efeae5217b 100644 --- a/flow/utils/flow_warnings.py +++ b/flow/utils/flow_warnings.py @@ -1,9 +1,13 @@ """Warnings that may be printed by Flow (e.g. deprecation warnings).""" +import functools +import inspect import warnings +string_types = (type(b''), type(u'')) -def deprecation_warning(obj, dep_from, dep_to): + +def deprecated_attribute(obj, dep_from, dep_to): """Print a deprecation warning. Parameters @@ -18,3 +22,81 @@ def deprecation_warning(obj, dep_from, dep_to): warnings.warn( "The attribute {} in {} is deprecated, use {} instead.".format( dep_from, obj.__class__.__name__, dep_to)) + + +def deprecated(base, new_path): + """Print a deprecation warning. + + This is a decorator which can be used to mark functions as deprecated. It + will result in a warning being emitted when the function is used. + """ + # if isinstance(base, string_types): + + # The @deprecated is used with a 'reason'. + # + # .. code-block:: python + # + # @deprecated("please, use another function") + # def old_function(x, y): + # pass + + def decorator(func1): + + if inspect.isclass(func1): + fmt1 = "The class {base}.{name} is deprecated, use " \ + "{new_path} instead." + else: + fmt1 = "The function {base}.{name} is deprecated, use " \ + "{new_path} instead." + + @functools.wraps(func1) + def new_func1(*args, **kwargs): + warnings.simplefilter('always', PendingDeprecationWarning) + warnings.warn( + fmt1.format( + base=base, + name=func1.__name__, + new_path=new_path + ), + category=PendingDeprecationWarning, + stacklevel=2 + ) + warnings.simplefilter('default', PendingDeprecationWarning) + return func1(*args, **kwargs) + + return new_func1 + + return decorator + # + # elif inspect.isclass(reason) or inspect.isfunction(reason): + # + # # The @deprecated is used without any 'reason'. + # # + # # .. code-block:: python + # # + # # @deprecated + # # def old_function(x, y): + # # pass + # + # func2 = reason + # + # if inspect.isclass(func2): + # fmt2 = "Call to deprecated class {name}." + # else: + # fmt2 = "Call to deprecated function {name}." + # + # @functools.wraps(func2) + # def new_func2(*args, **kwargs): + # warnings.simplefilter('always', DeprecationWarning) + # warnings.warn( + # fmt2.format(name=func2.__name__), + # category=DeprecationWarning, + # stacklevel=2 + # ) + # warnings.simplefilter('default', DeprecationWarning) + # return func2(*args, **kwargs) + # + # return new_func2 + # + # else: + # raise TypeError(repr(type(reason))) diff --git a/flow/utils/registry.py b/flow/utils/registry.py index f586c3aff7..aff70bbc79 100644 --- a/flow/utils/registry.py +++ b/flow/utils/registry.py @@ -92,7 +92,7 @@ def create_env(*_): if params['env_name'] in single_agent_envs: env_loc = 'flow.envs' else: - env_loc = 'flow.multiagent_envs' + env_loc = 'flow.envs.multiagent' try: register( diff --git a/flow/utils/shflags b/flow/utils/shflags index 38495e1ddb..60e48b233c 100644 --- a/flow/utils/shflags +++ b/flow/utils/shflags @@ -511,7 +511,7 @@ _flags_getFlagInfo() { # See if the _flags_gFI_usName_ variable is a string as strings can be # empty... # Note: the DRY principle would say to have this function call itself for - # the next three lines, but doing so results in an infinite loop as an + # the next three lines, but doing so results in an infinite ring as an # invalid _flags_name_ will also not have the associated _type variable. # Because it doesn't (it will evaluate to an empty string) the logic will # try to find the _type variable of the _type variable, and so on. Not so diff --git a/flow/version.py b/flow/version.py index 487c623aa3..7c9845e29b 100644 --- a/flow/version.py +++ b/flow/version.py @@ -1,3 +1,3 @@ """Specifies the current version number of Flow.""" -__version__ = "0.4.1" +__version__ = "0.5.0.dev" diff --git a/flow/visualize/time_space_diagram.py b/flow/visualize/time_space_diagram.py index 91470dda1c..15ea6efb9d 100644 --- a/flow/visualize/time_space_diagram.py +++ b/flow/visualize/time_space_diagram.py @@ -26,8 +26,8 @@ # scenarios that can be plotted by this method ACCEPTABLE_SCENARIOS = [ - 'LoopScenario', - 'Figure8Scenario', + 'RingScenario', + 'FigureEightScenario', 'MergeScenario', ] @@ -120,9 +120,9 @@ def get_time_space_data(data, params): # switcher used to compute the positions based on the type of scenario switcher = { - 'LoopScenario': _ring_road, + 'RingScenario': _ring_road, 'MergeScenario': _merge, - 'Figure8Scenario': _figure_eight + 'FigureEightScenario': _figure_eight } # Collect a list of all the unique times. @@ -357,14 +357,14 @@ def _figure_eight(data, params, all_time): speed[ind, i] = spd # reorganize data for space-time plot - figure8_len = 6*ring_edgelen + 2*intersection + 2*junction + 10*inner + figure_eight_len = 6*ring_edgelen + 2*intersection + 2*junction + 10*inner intersection_loc = [edgestarts[':center_1'] + intersection / 2, edgestarts[':center_0'] + intersection / 2] - pos[pos < intersection_loc[0]] += figure8_len + pos[pos < intersection_loc[0]] += figure_eight_len pos[np.logical_and(pos > intersection_loc[0], pos < intersection_loc[1])] \ += - intersection_loc[1] pos[pos > intersection_loc[1]] = \ - - pos[pos > intersection_loc[1]] + figure8_len + intersection_loc[0] + - pos[pos > intersection_loc[1]] + figure_eight_len + intersection_loc[0] return pos, speed diff --git a/flow/visualize/visualizer_rllib.py b/flow/visualize/visualizer_rllib.py index b9d76838f4..c97b3df261 100644 --- a/flow/visualize/visualizer_rllib.py +++ b/flow/visualize/visualizer_rllib.py @@ -137,7 +137,7 @@ def visualizer_rllib(args): # if flow_params['env_name'] in single_agent_envs: # env_loc = 'flow.envs' # else: - # env_loc = 'flow.multiagent_envs' + # env_loc = 'flow.envs.multiagent' # Start the environment with the gui turned on and a path for the # emission file diff --git a/tests/data/rllib_data/multi_agent/params.json b/tests/data/rllib_data/multi_agent/params.json index d32656fa7a..981780b3ad 100644 --- a/tests/data/rllib_data/multi_agent/params.json +++ b/tests/data/rllib_data/multi_agent/params.json @@ -16,7 +16,7 @@ "entropy_coeff": 0.0, "env": "MultiWaveAttenuationPOEnv-v0", "env_config": { - "flow_params": "{\n \"env\": {\n \"additional_params\": {\n \"max_accel\": 1,\n \"max_decel\": 1,\n \"ring_length\": [\n 230,\n 230\n ],\n \"target_velocity\": 4\n },\n \"clip_actions\": true,\n \"evaluate\": false,\n \"horizon\": 3000,\n \"sims_per_step\": 1,\n \"warmup_steps\": 750\n },\n \"env_name\": \"MultiWaveAttenuationPOEnv\",\n \"exp_tag\": \"lord_of_numrings1\",\n \"initial\": {\n \"additional_params\": {},\n \"bunching\": 20.0,\n \"edges_distribution\": \"all\",\n \"lanes_distribution\": Infinity,\n \"min_gap\": 0,\n \"perturbation\": 0.0,\n \"shuffle\": false,\n \"spacing\": \"custom\",\n \"x0\": 0\n },\n \"net\": {\n \"additional_params\": {\n \"lanes\": 1,\n \"length\": 230,\n \"num_rings\": 1,\n \"resolution\": 40,\n \"speed_limit\": 30\n },\n \"inflows\": {\n \"_InFlows__flows\": []\n },\n \"osm_path\": null,\n \"template\": null\n },\n \"scenario\": \"MultiLoopScenario\",\n \"sim\": {\n \"color_vehicles\": true,\n \"emission_path\": null,\n \"lateral_resolution\": null,\n \"no_step_log\": true,\n \"num_clients\": 1,\n \"overtake_right\": false,\n \"port\": null,\n \"print_warnings\": true,\n \"pxpm\": 2,\n \"render\": false,\n \"restart_instance\": false,\n \"save_render\": false,\n \"seed\": null,\n \"show_radius\": false,\n \"sight_radius\": 25,\n \"sim_step\": 0.1,\n \"teleport_time\": -1\n },\n \"simulator\": \"traci\",\n \"veh\": [\n {\n \"acceleration_controller\": [\n \"IDMController\",\n {\n \"noise\": 0.2\n }\n ],\n \"car_following_params\": {\n \"controller_params\": {\n \"accel\": 2.6,\n \"carFollowModel\": \"IDM\",\n \"decel\": 4.5,\n \"impatience\": 0.5,\n \"maxSpeed\": 30,\n \"minGap\": 2.5,\n \"sigma\": 0.5,\n \"speedDev\": 0.1,\n \"speedFactor\": 1.0,\n \"tau\": 1.0\n },\n \"speed_mode\": 25\n },\n \"initial_speed\": 0,\n \"lane_change_controller\": [\n \"SimLaneChangeController\",\n {}\n ],\n \"lane_change_params\": {\n \"controller_params\": {\n \"laneChangeModel\": \"LC2013\",\n \"lcCooperative\": \"1.0\",\n \"lcKeepRight\": \"1.0\",\n \"lcSpeedGain\": \"1.0\",\n \"lcStrategic\": \"1.0\"\n },\n \"lane_change_mode\": 512\n },\n \"num_vehicles\": 21,\n \"routing_controller\": [\n \"ContinuousRouter\",\n {}\n ],\n \"veh_id\": \"human_0\"\n },\n {\n \"acceleration_controller\": [\n \"RLController\",\n {}\n ],\n \"car_following_params\": {\n \"controller_params\": {\n \"accel\": 2.6,\n \"carFollowModel\": \"IDM\",\n \"decel\": 4.5,\n \"impatience\": 0.5,\n \"maxSpeed\": 30,\n \"minGap\": 2.5,\n \"sigma\": 0.5,\n \"speedDev\": 0.1,\n \"speedFactor\": 1.0,\n \"tau\": 1.0\n },\n \"speed_mode\": 25\n },\n \"initial_speed\": 0,\n \"lane_change_controller\": [\n \"SimLaneChangeController\",\n {}\n ],\n \"lane_change_params\": {\n \"controller_params\": {\n \"laneChangeModel\": \"LC2013\",\n \"lcCooperative\": \"1.0\",\n \"lcKeepRight\": \"1.0\",\n \"lcSpeedGain\": \"1.0\",\n \"lcStrategic\": \"1.0\"\n },\n \"lane_change_mode\": 512\n },\n \"num_vehicles\": 1,\n \"routing_controller\": [\n \"ContinuousRouter\",\n {}\n ],\n \"veh_id\": \"rl_0\"\n }\n ]\n}", + "flow_params": "{\n \"env\": {\n \"additional_params\": {\n \"max_accel\": 1,\n \"max_decel\": 1,\n \"ring_length\": [\n 230,\n 230\n ],\n \"target_velocity\": 4\n },\n \"clip_actions\": true,\n \"evaluate\": false,\n \"horizon\": 3000,\n \"sims_per_step\": 1,\n \"warmup_steps\": 750\n },\n \"env_name\": \"MultiWaveAttenuationPOEnv\",\n \"exp_tag\": \"lord_of_numrings1\",\n \"initial\": {\n \"additional_params\": {},\n \"bunching\": 20.0,\n \"edges_distribution\": \"all\",\n \"lanes_distribution\": Infinity,\n \"min_gap\": 0,\n \"perturbation\": 0.0,\n \"shuffle\": false,\n \"spacing\": \"custom\",\n \"x0\": 0\n },\n \"net\": {\n \"additional_params\": {\n \"lanes\": 1,\n \"length\": 230,\n \"num_rings\": 1,\n \"resolution\": 40,\n \"speed_limit\": 30\n },\n \"inflows\": {\n \"_InFlows__flows\": []\n },\n \"osm_path\": null,\n \"template\": null\n },\n \"scenario\": \"MultiRingScenario\",\n \"sim\": {\n \"color_vehicles\": true,\n \"emission_path\": null,\n \"lateral_resolution\": null,\n \"no_step_log\": true,\n \"num_clients\": 1,\n \"overtake_right\": false,\n \"port\": null,\n \"print_warnings\": true,\n \"pxpm\": 2,\n \"render\": false,\n \"restart_instance\": false,\n \"save_render\": false,\n \"seed\": null,\n \"show_radius\": false,\n \"sight_radius\": 25,\n \"sim_step\": 0.1,\n \"teleport_time\": -1\n },\n \"simulator\": \"traci\",\n \"veh\": [\n {\n \"acceleration_controller\": [\n \"IDMController\",\n {\n \"noise\": 0.2\n }\n ],\n \"car_following_params\": {\n \"controller_params\": {\n \"accel\": 2.6,\n \"carFollowModel\": \"IDM\",\n \"decel\": 4.5,\n \"impatience\": 0.5,\n \"maxSpeed\": 30,\n \"minGap\": 2.5,\n \"sigma\": 0.5,\n \"speedDev\": 0.1,\n \"speedFactor\": 1.0,\n \"tau\": 1.0\n },\n \"speed_mode\": 25\n },\n \"initial_speed\": 0,\n \"lane_change_controller\": [\n \"SimLaneChangeController\",\n {}\n ],\n \"lane_change_params\": {\n \"controller_params\": {\n \"laneChangeModel\": \"LC2013\",\n \"lcCooperative\": \"1.0\",\n \"lcKeepRight\": \"1.0\",\n \"lcSpeedGain\": \"1.0\",\n \"lcStrategic\": \"1.0\"\n },\n \"lane_change_mode\": 512\n },\n \"num_vehicles\": 21,\n \"routing_controller\": [\n \"ContinuousRouter\",\n {}\n ],\n \"veh_id\": \"human_0\"\n },\n {\n \"acceleration_controller\": [\n \"RLController\",\n {}\n ],\n \"car_following_params\": {\n \"controller_params\": {\n \"accel\": 2.6,\n \"carFollowModel\": \"IDM\",\n \"decel\": 4.5,\n \"impatience\": 0.5,\n \"maxSpeed\": 30,\n \"minGap\": 2.5,\n \"sigma\": 0.5,\n \"speedDev\": 0.1,\n \"speedFactor\": 1.0,\n \"tau\": 1.0\n },\n \"speed_mode\": 25\n },\n \"initial_speed\": 0,\n \"lane_change_controller\": [\n \"SimLaneChangeController\",\n {}\n ],\n \"lane_change_params\": {\n \"controller_params\": {\n \"laneChangeModel\": \"LC2013\",\n \"lcCooperative\": \"1.0\",\n \"lcKeepRight\": \"1.0\",\n \"lcSpeedGain\": \"1.0\",\n \"lcStrategic\": \"1.0\"\n },\n \"lane_change_mode\": 512\n },\n \"num_vehicles\": 1,\n \"routing_controller\": [\n \"ContinuousRouter\",\n {}\n ],\n \"veh_id\": \"rl_0\"\n }\n ]\n}", "run": "PPO" }, "gamma": 0.999, diff --git a/tests/data/rllib_data/single_agent/params.json b/tests/data/rllib_data/single_agent/params.json index 26991e86a0..f2c6dfe589 100644 --- a/tests/data/rllib_data/single_agent/params.json +++ b/tests/data/rllib_data/single_agent/params.json @@ -16,7 +16,7 @@ "entropy_coeff": 0.0, "env": "AccelEnv-v0", "env_config": { - "flow_params": "{\n \"simulator\": \"traci\",\n \"env\": {\n \"additional_params\": {\n \"max_accel\": 3,\n \"max_decel\": 3,\n \"sort_vehicles\": false,\n \"target_velocity\": 20\n },\n \"clip_actions\": false,\n \"evaluate\": false,\n \"horizon\": 1500,\n \"sims_per_step\": 1,\n \"warmup_steps\": 0\n },\n \"env_name\": \"AccelEnv\",\n \"exp_tag\": \"to_visualize\",\n \"initial\": {\n \"additional_params\": {},\n \"bunching\": 0,\n \"edges_distribution\": \"all\",\n \"lanes_distribution\": Infinity,\n \"min_gap\": 0,\n \"perturbation\": 0.0,\n \"shuffle\": false,\n \"spacing\": \"uniform\",\n \"x0\": 0\n },\n \"net\": {\n \"additional_params\": {\n \"lanes\": 1,\n \"radius_ring\": 30,\n \"resolution\": 40,\n \"speed_limit\": 30\n },\n \"inflows\": {\n \"_InFlows__flows\": [],\n \"num_flows\": 0\n },\n \"template\": null,\n \"osm_path\": null\n },\n \"scenario\": \"Figure8Scenario\",\n \"sim\": {\n \"emission_path\": null,\n \"lateral_resolution\": null,\n \"no_step_log\": true,\n \"num_clients\": 1,\n \"overtake_right\": false,\n \"port\": null,\n \"print_warnings\": true,\n \"render\": false,\n \"restart_instance\": false,\n \"seed\": null,\n \"sim_step\": 0.1,\n \"teleport_time\": -1\n },\n \"veh\": [\n {\n \"acceleration_controller\": [\n \"IDMController\",\n {\n \"noise\": 0.2\n }\n ],\n \"initial_speed\": 0,\n \"lane_change_controller\": [\n \"SimLaneChangeController\",\n {}\n ],\n \"num_vehicles\": 13,\n \"routing_controller\": [\n \"ContinuousRouter\",\n {}\n ],\n \"car_following_params\": {\n \"speed_mode\": 1,\n \"controller_params\": {\n \"accel\": 2.6,\n \"carFollowModel\": \"IDM\",\n \"decel\": 4.5,\n \"impatience\": 0.5,\n \"maxSpeed\": 30,\n \"minGap\": 2.5,\n \"sigma\": 0.5,\n \"speedDev\": 0.1,\n \"speedFactor\": 1.0,\n \"tau\": 1.0\n }\n },\n \"lane_change_params\": {\n \"lane_change_mode\": 512,\n \"controller_params\": {\n \"laneChangeModel\": \"LC2013\",\n \"lcCooperative\": \"1.0\",\n \"lcKeepRight\": \"1.0\",\n \"lcSpeedGain\": \"1.0\",\n \"lcStrategic\": \"1.0\"\n }\n },\n \"veh_id\": \"human\"\n },\n {\n \"acceleration_controller\": [\n \"RLController\",\n {}\n ],\n \"initial_speed\": 0,\n \"lane_change_controller\": [\n \"SimLaneChangeController\",\n {}\n ],\n \"num_vehicles\": 1,\n \"routing_controller\": [\n \"ContinuousRouter\",\n {}\n ],\n \"car_following_params\": {\n \"speed_mode\": 1,\n \"controller_params\": {\n \"accel\": 2.6,\n \"carFollowModel\": \"IDM\",\n \"decel\": 4.5,\n \"impatience\": 0.5,\n \"maxSpeed\": 30,\n \"minGap\": 2.5,\n \"sigma\": 0.5,\n \"speedDev\": 0.1,\n \"speedFactor\": 1.0,\n \"tau\": 1.0\n }\n },\n \"lane_change_params\": {\n \"lane_change_mode\": 512,\n \"controller_params\": {\n \"laneChangeModel\": \"LC2013\",\n \"lcCooperative\": \"1.0\",\n \"lcKeepRight\": \"1.0\",\n \"lcSpeedGain\": \"1.0\",\n \"lcStrategic\": \"1.0\"\n }\n },\n \"veh_id\": \"rl\"\n }\n ]\n}", + "flow_params": "{\n \"simulator\": \"traci\",\n \"env\": {\n \"additional_params\": {\n \"max_accel\": 3,\n \"max_decel\": 3,\n \"sort_vehicles\": false,\n \"target_velocity\": 20\n },\n \"clip_actions\": false,\n \"evaluate\": false,\n \"horizon\": 1500,\n \"sims_per_step\": 1,\n \"warmup_steps\": 0\n },\n \"env_name\": \"AccelEnv\",\n \"exp_tag\": \"to_visualize\",\n \"initial\": {\n \"additional_params\": {},\n \"bunching\": 0,\n \"edges_distribution\": \"all\",\n \"lanes_distribution\": Infinity,\n \"min_gap\": 0,\n \"perturbation\": 0.0,\n \"shuffle\": false,\n \"spacing\": \"uniform\",\n \"x0\": 0\n },\n \"net\": {\n \"additional_params\": {\n \"lanes\": 1,\n \"radius_ring\": 30,\n \"resolution\": 40,\n \"speed_limit\": 30\n },\n \"inflows\": {\n \"_InFlows__flows\": [],\n \"num_flows\": 0\n },\n \"template\": null,\n \"osm_path\": null\n },\n \"scenario\": \"FigureEightScenario\",\n \"sim\": {\n \"emission_path\": null,\n \"lateral_resolution\": null,\n \"no_step_log\": true,\n \"num_clients\": 1,\n \"overtake_right\": false,\n \"port\": null,\n \"print_warnings\": true,\n \"render\": false,\n \"restart_instance\": false,\n \"seed\": null,\n \"sim_step\": 0.1,\n \"teleport_time\": -1\n },\n \"veh\": [\n {\n \"acceleration_controller\": [\n \"IDMController\",\n {\n \"noise\": 0.2\n }\n ],\n \"initial_speed\": 0,\n \"lane_change_controller\": [\n \"SimLaneChangeController\",\n {}\n ],\n \"num_vehicles\": 13,\n \"routing_controller\": [\n \"ContinuousRouter\",\n {}\n ],\n \"car_following_params\": {\n \"speed_mode\": 1,\n \"controller_params\": {\n \"accel\": 2.6,\n \"carFollowModel\": \"IDM\",\n \"decel\": 4.5,\n \"impatience\": 0.5,\n \"maxSpeed\": 30,\n \"minGap\": 2.5,\n \"sigma\": 0.5,\n \"speedDev\": 0.1,\n \"speedFactor\": 1.0,\n \"tau\": 1.0\n }\n },\n \"lane_change_params\": {\n \"lane_change_mode\": 512,\n \"controller_params\": {\n \"laneChangeModel\": \"LC2013\",\n \"lcCooperative\": \"1.0\",\n \"lcKeepRight\": \"1.0\",\n \"lcSpeedGain\": \"1.0\",\n \"lcStrategic\": \"1.0\"\n }\n },\n \"veh_id\": \"human\"\n },\n {\n \"acceleration_controller\": [\n \"RLController\",\n {}\n ],\n \"initial_speed\": 0,\n \"lane_change_controller\": [\n \"SimLaneChangeController\",\n {}\n ],\n \"num_vehicles\": 1,\n \"routing_controller\": [\n \"ContinuousRouter\",\n {}\n ],\n \"car_following_params\": {\n \"speed_mode\": 1,\n \"controller_params\": {\n \"accel\": 2.6,\n \"carFollowModel\": \"IDM\",\n \"decel\": 4.5,\n \"impatience\": 0.5,\n \"maxSpeed\": 30,\n \"minGap\": 2.5,\n \"sigma\": 0.5,\n \"speedDev\": 0.1,\n \"speedFactor\": 1.0,\n \"tau\": 1.0\n }\n },\n \"lane_change_params\": {\n \"lane_change_mode\": 512,\n \"controller_params\": {\n \"laneChangeModel\": \"LC2013\",\n \"lcCooperative\": \"1.0\",\n \"lcKeepRight\": \"1.0\",\n \"lcSpeedGain\": \"1.0\",\n \"lcStrategic\": \"1.0\"\n }\n },\n \"veh_id\": \"rl\"\n }\n ]\n}", "run": "PPO" }, "gamma": 0.999, diff --git a/tests/fast_tests/test_collisions.py b/tests/fast_tests/test_collisions.py index d094a87ac5..357e98df1a 100644 --- a/tests/fast_tests/test_collisions.py +++ b/tests/fast_tests/test_collisions.py @@ -7,7 +7,7 @@ from flow.controllers.car_following_models import SimCarFollowingController from flow.controllers.routing_controllers import GridRouter -from tests.setup_scripts import grid_mxn_exp_setup +from tests.setup_scripts import traffic_light_grid_mxn_exp_setup class TestCollisions(unittest.TestCase): @@ -50,7 +50,7 @@ def test_collide(self): net_params = NetParams(additional_params=additional_net_params) - env, _ = grid_mxn_exp_setup( + env, _ = traffic_light_grid_mxn_exp_setup( row_num=1, col_num=1, sim_params=sim_params, @@ -111,7 +111,7 @@ def test_collide_inflows(self): inflows=inflows, additional_params=additional_net_params) - env, _ = grid_mxn_exp_setup( + env, _ = traffic_light_grid_mxn_exp_setup( row_num=1, col_num=1, sim_params=sim_params, diff --git a/tests/fast_tests/test_environment_base_class.py b/tests/fast_tests/test_environment_base_class.py index ad76c4ca32..0496500d68 100644 --- a/tests/fast_tests/test_environment_base_class.py +++ b/tests/fast_tests/test_environment_base_class.py @@ -7,7 +7,7 @@ from flow.controllers.routing_controllers import ContinuousRouter from flow.controllers.car_following_models import IDMController from flow.controllers import RLController -from flow.envs.loop.loop_accel import ADDITIONAL_ENV_PARAMS +from flow.envs.ring.accel import ADDITIONAL_ENV_PARAMS from flow.utils.exceptions import FatalFlowError from flow.envs import Env, TestEnv @@ -103,7 +103,7 @@ def test_emission(self): class TestApplyingActionsWithSumo(unittest.TestCase): """ Tests the apply_acceleration, apply_lane_change, and choose_routes - functions in base_env.py + functions in base.py """ def setUp(self): diff --git a/tests/fast_tests/test_environments.py b/tests/fast_tests/test_environments.py index 36c3813053..762cc65707 100644 --- a/tests/fast_tests/test_environments.py +++ b/tests/fast_tests/test_environments.py @@ -7,18 +7,17 @@ from flow.core.params import VehicleParams from flow.core.params import NetParams, EnvParams, SumoParams, InFlows from flow.controllers import IDMController, RLController -from flow.scenarios import LoopScenario, MergeScenario, BottleneckScenario -from flow.scenarios import HighwayRampsScenario -from flow.scenarios.loop import ADDITIONAL_NET_PARAMS as LOOP_PARAMS +from flow.scenarios import RingScenario, MergeScenario, BottleneckScenario +from flow.scenarios.ring import ADDITIONAL_NET_PARAMS as Ring_PARAMS from flow.scenarios.merge import ADDITIONAL_NET_PARAMS as MERGE_PARAMS +from flow.scenarios import HighwayRampsScenario from flow.scenarios.highway_ramps import ADDITIONAL_NET_PARAMS as \ HIGHWAY_PARAMS from flow.envs import LaneChangeAccelEnv, LaneChangeAccelPOEnv, AccelEnv, \ - WaveAttenuationEnv, WaveAttenuationPOEnv, WaveAttenuationMergePOEnv, \ - TestEnv, DesiredVelocityEnv, BottleneckEnv, BottleNeckAccelEnv -from flow.envs.loop.wave_attenuation import v_eq_max_function -from flow.multiagent_envs import MultiAgentHighwayPOEnv - + WaveAttenuationEnv, WaveAttenuationPOEnv, MergePOEnv, \ + TestEnv, BottleneckDesiredVelocityEnv, BottleneckEnv, BottleneckAccelEnv +from flow.envs.ring.wave_attenuation import v_eq_max_function +from flow.envs.multiagent import MultiAgentHighwayPOEnv os.environ["TEST_FLAG"] = "True" @@ -33,10 +32,10 @@ def setUp(self): num_vehicles=1) self.sim_params = SumoParams() - self.scenario = LoopScenario( + self.scenario = RingScenario( name="test_merge", vehicles=vehicles, - net_params=NetParams(additional_params=LOOP_PARAMS.copy()), + net_params=NetParams(additional_params=Ring_PARAMS.copy()), ) self.env_params = EnvParams( additional_params={ @@ -122,10 +121,10 @@ def setUp(self): num_vehicles=1) self.sim_params = SumoParams() - self.scenario = LoopScenario( + self.scenario = RingScenario( name="test_merge", vehicles=vehicles, - net_params=NetParams(additional_params=LOOP_PARAMS.copy()), + net_params=NetParams(additional_params=Ring_PARAMS.copy()), ) self.env_params = EnvParams( additional_params={ @@ -206,10 +205,10 @@ def setUp(self): num_vehicles=1) self.sim_params = SumoParams() - self.scenario = LoopScenario( + self.scenario = RingScenario( name="test_merge", vehicles=vehicles, - net_params=NetParams(additional_params=LOOP_PARAMS.copy()), + net_params=NetParams(additional_params=Ring_PARAMS.copy()), ) self.env_params = EnvParams( additional_params={ @@ -339,10 +338,10 @@ def setUp(self): self.sim_params = SumoParams( restart_instance=True ) - self.scenario = LoopScenario( + self.scenario = RingScenario( name="test_merge", vehicles=vehicles, - net_params=NetParams(additional_params=LOOP_PARAMS.copy()), + net_params=NetParams(additional_params=Ring_PARAMS.copy()), ) params = { "max_accel": 1, @@ -461,11 +460,11 @@ def test_reset_no_same_length(self): ) # reset the network several times and check its length - self.assertEqual(env.k.scenario.length(), LOOP_PARAMS["length"]) + self.assertEqual(env.k.scenario.length(), Ring_PARAMS["length"]) env.reset() - self.assertEqual(env.k.scenario.length(), LOOP_PARAMS["length"]) + self.assertEqual(env.k.scenario.length(), Ring_PARAMS["length"]) env.reset() - self.assertEqual(env.k.scenario.length(), LOOP_PARAMS["length"]) + self.assertEqual(env.k.scenario.length(), Ring_PARAMS["length"]) class TestWaveAttenuationPOEnv(unittest.TestCase): @@ -478,10 +477,10 @@ def setUp(self): num_vehicles=1) self.sim_params = SumoParams() - self.scenario = LoopScenario( + self.scenario = RingScenario( name="test_merge", vehicles=vehicles, - net_params=NetParams(additional_params=LOOP_PARAMS.copy()), + net_params=NetParams(additional_params=Ring_PARAMS.copy()), ) self.env_params = EnvParams( additional_params={ @@ -611,7 +610,7 @@ def test_reward(self): ) -class TestWaveAttenuationMergePOEnv(unittest.TestCase): +class TestMergePOEnv(unittest.TestCase): def setUp(self): vehicles = VehicleParams() @@ -644,7 +643,7 @@ def test_additional_env_params(self): """Ensures that not returning the correct params leads to an error.""" self.assertTrue( test_additional_params( - env_class=WaveAttenuationMergePOEnv, + env_class=MergePOEnv, sim_params=self.sim_params, scenario=self.scenario, additional_params={ @@ -659,7 +658,7 @@ def test_additional_env_params(self): def test_observation_action_space(self): """Tests the observation and action spaces upon initialization.""" # create the environment - env = WaveAttenuationMergePOEnv( + env = MergePOEnv( sim_params=self.sim_params, scenario=self.scenario, env_params=self.env_params @@ -681,7 +680,7 @@ def test_observed(self): """Ensures that the observed ids are returning the correct vehicles.""" self.assertTrue( test_observed( - env_class=WaveAttenuationMergePOEnv, + env_class=MergePOEnv, sim_params=self.sim_params, scenario=self.scenario, env_params=self.env_params, @@ -697,10 +696,10 @@ class TestTestEnv(unittest.TestCase): def setUp(self): vehicles = VehicleParams() vehicles.add("test", num_vehicles=1) - net_params = NetParams(additional_params=LOOP_PARAMS) + net_params = NetParams(additional_params=Ring_PARAMS) env_params = EnvParams() sim_params = SumoParams() - scenario = LoopScenario("test_loop", + scenario = RingScenario("test_ring", vehicles=vehicles, net_params=net_params) self.env = TestEnv(env_params, sim_params, scenario) @@ -736,7 +735,7 @@ def reward_fn(*_): class TestBottleneckEnv(unittest.TestCase): - """Tests the BottleneckEnv environment in flow/envs/bottleneck_env.py""" + """Tests the BottleneckEnv environment in flow/envs/bottleneck.py""" def setUp(self): self.sim_params = SumoParams(sim_step=0.5, restart_instance=True) @@ -810,7 +809,7 @@ def test_observation_action_space(self): class TestBottleneckAccelEnv(unittest.TestCase): - """Tests BottleneckAccelEnv in flow/envs/bottleneck_env.py.""" + """Tests BottleneckAccelEnv in flow/envs/bottleneck.py.""" def setUp(self): self.sim_params = SumoParams(sim_step=0.5, restart_instance=True) @@ -838,7 +837,7 @@ def setUp(self): vehicles=vehicles, net_params=net_params) - self.env = BottleNeckAccelEnv( + self.env = BottleneckAccelEnv( env_params, self.sim_params, self.scenario) self.env.reset() @@ -850,7 +849,7 @@ def test_additional_env_params(self): """Ensures that not returning the correct params leads to an error.""" self.assertTrue( test_additional_params( - env_class=BottleNeckAccelEnv, + env_class=BottleneckAccelEnv, sim_params=self.sim_params, scenario=self.scenario, additional_params={ @@ -876,9 +875,10 @@ def test_observation_action_space(self): ) -class TestDesiredVelocityEnv(unittest.TestCase): +class TestBottleneckDesiredVelocityEnv(unittest.TestCase): - """Tests the DesiredVelocityEnv environment in flow/envs/bottleneck.py""" + """Tests the BottleneckDesiredVelocityEnv environment in + flow/envs/bottleneck.py""" def test_reset_inflows(self): """Tests that the inflow change within the expected range when calling @@ -930,7 +930,7 @@ def test_reset_inflows(self): vehicles=vehicles, net_params=net_params) - env = DesiredVelocityEnv(env_params, sim_params, scenario) + env = BottleneckDesiredVelocityEnv(env_params, sim_params, scenario) # reset the environment and get a new inflow rate env.reset() diff --git a/tests/fast_tests/test_examples.py b/tests/fast_tests/test_examples.py index 4651b523cb..be9dcc9bf2 100644 --- a/tests/fast_tests/test_examples.py +++ b/tests/fast_tests/test_examples.py @@ -4,9 +4,8 @@ import ray from ray.tune import run_experiments -from examples.rllib.cooperative_merge import setup_exps as coop_setup from examples.rllib.figure_eight import setup_exps as figure_eight_setup -from examples.rllib.green_wave import setup_exps as green_wave_setup +from examples.rllib.traffic_light_grid import setup_exps as traffic_light_grid_setup from examples.rllib.stabilizing_highway import setup_exps as highway_setup from examples.rllib.stabilizing_the_ring import setup_exps as ring_setup from examples.rllib.velocity_bottleneck import setup_exps as bottleneck_setup @@ -24,7 +23,7 @@ as multi_highway_setup from examples.stable_baselines.figure_eight import run_model as run_figure_eight -from examples.stable_baselines.green_wave import run_model as run_green_wave +from examples.stable_baselines.traffic_light_grid import run_model as run_traffic_light_grid from examples.stable_baselines.stabilizing_highway import run_model as run_stabilizing_highway from examples.stable_baselines.stabilizing_the_ring import run_model as run_stabilizing_ring from examples.stable_baselines.velocity_bottleneck import run_model as run_velocity_bottleneck @@ -34,10 +33,9 @@ from examples.sumo.bottlenecks import bottleneck_example from examples.sumo.density_exp import run_bottleneck from examples.sumo.figure_eight import figure_eight_example -from examples.sumo.grid import grid_example +from examples.sumo.traffic_light_grid import traffic_light_grid_example from examples.sumo.highway import highway_example from examples.sumo.highway_ramps import highway_ramps_example -from examples.sumo.loop_merge import loop_merge_example from examples.sumo.merge import merge_example from examples.sumo.minicity import minicity_example from examples.sumo.sugiyama import sugiyama_example @@ -70,14 +68,14 @@ def test_figure_eight(self): # run the experiment for a few time steps to ensure it doesn't fail exp.run(1, 5) - def test_grid(self): - """Verifies that examples/sumo/grid.py is working.""" + def test_traffic_light_grid(self): + """Verifies that examples/sumo/traffic_light_grid.py is working.""" # test the example in the absence of inflows - exp = grid_example(render=False, use_inflows=False) + exp = traffic_light_grid_example(render=False, use_inflows=False) exp.run(1, 5) # test the example in the presence of inflows - exp = grid_example(render=False, use_inflows=True) + exp = traffic_light_grid_example(render=False, use_inflows=True) exp.run(1, 5) def test_highway(self): @@ -112,14 +110,6 @@ def test_sugiyama(self): # run the experiment for a few time steps to ensure it doesn't fail exp.run(1, 5) - def test_loop_merge(self): - """Verify that examples/sumo/two_loops_merge_straight.py is working.""" - # import the experiment variable from the example - exp = loop_merge_example(render=False) - - # run the experiment for a few time steps to ensure it doesn't fail - exp.run(1, 5) - def test_bay_bridge(self): """Verifies that examples/sumo/bay_bridge.py is working.""" # import the experiment variable from the example @@ -167,8 +157,8 @@ class TestStableBaselineExamples(unittest.TestCase): This is done by running each experiment in that folder for five time-steps and confirming that it completes one rollout with two workers. """ - def test_run_green_wave(self): - run_green_wave(num_steps=5) + def test_run_traffic_light_grid(self): + run_traffic_light_grid(num_steps=5) def test_run_figure_eight(self): run_figure_eight(num_steps=5) @@ -194,22 +184,18 @@ def setUp(self): if not ray.is_initialized(): ray.init(num_cpus=1) - def test_coop_merge(self): - alg_run, env_name, config = coop_setup() - self.run_exp(alg_run, env_name, config) - def test_figure_eight(self): alg_run, env_name, config = figure_eight_setup() self.run_exp(alg_run, env_name, config) - def test_green_wave(self): + def test_traffic_light_grid(self): # test the example in the absence of inflows - alg_run, env_name, config = green_wave_setup(use_inflows=False) + alg_run, env_name, config = traffic_light_grid_setup(use_inflows=False) self.run_exp(alg_run, env_name, config) - def test_green_wave_inflows(self): + def test_traffic_light_grid_inflows(self): # test the example in the presence of inflows - alg_run, env_name, config = green_wave_setup(use_inflows=True) + alg_run, env_name, config = traffic_light_grid_setup(use_inflows=True) self.run_exp(alg_run, env_name, config) def test_stabilizing_highway(self): diff --git a/tests/fast_tests/test_files/fig8.json b/tests/fast_tests/test_files/fig8.json index fe0e2d431b..a8c84b3452 100644 --- a/tests/fast_tests/test_files/fig8.json +++ b/tests/fast_tests/test_files/fig8.json @@ -39,7 +39,7 @@ "osm_path": null, "template": null }, - "scenario": "Figure8Scenario", + "scenario": "FigureEightScenario", "sim": { "emission_path": null, "lateral_resolution": null, diff --git a/tests/fast_tests/test_files/merge.json b/tests/fast_tests/test_files/merge.json index 21bd801e8d..279735c9ab 100644 --- a/tests/fast_tests/test_files/merge.json +++ b/tests/fast_tests/test_files/merge.json @@ -12,7 +12,7 @@ "sims_per_step": 2, "warmup_steps": 0 }, - "env_name": "WaveAttenuationMergePOEnv", + "env_name": "MergePOEnv", "exp_tag": "merge_0", "initial": { "additional_params": {}, diff --git a/tests/fast_tests/test_files/loop_230.json b/tests/fast_tests/test_files/ring_230.json similarity index 99% rename from tests/fast_tests/test_files/loop_230.json rename to tests/fast_tests/test_files/ring_230.json index 78470b6ead..76a30381eb 100644 --- a/tests/fast_tests/test_files/loop_230.json +++ b/tests/fast_tests/test_files/ring_230.json @@ -41,7 +41,7 @@ "osm_path": null, "template": null }, - "scenario": "LoopScenario", + "scenario": "RingScenario", "sim": { "emission_path": null, "lateral_resolution": null, diff --git a/tests/fast_tests/test_files/loop_230_emission.csv b/tests/fast_tests/test_files/ring_230_emission.csv similarity index 100% rename from tests/fast_tests/test_files/loop_230_emission.csv rename to tests/fast_tests/test_files/ring_230_emission.csv diff --git a/tests/fast_tests/test_green_wave.py b/tests/fast_tests/test_green_wave.py index 74ed94fb44..22de10f378 100644 --- a/tests/fast_tests/test_green_wave.py +++ b/tests/fast_tests/test_green_wave.py @@ -2,13 +2,13 @@ from flow.core.experiment import Experiment -from tests.setup_scripts import grid_mxn_exp_setup +from tests.setup_scripts import traffic_light_grid_mxn_exp_setup class Test1x1Environment(unittest.TestCase): def setUp(self): - # create the environment and scenario classes for a grid network - self.env, _ = grid_mxn_exp_setup() + # create the environment and scenario classes for a traffic light grid network + self.env, _ = traffic_light_grid_mxn_exp_setup() self.env.reset() # instantiate an experiment class @@ -93,8 +93,8 @@ def test_get_distance_to_intersection(self): class Test2x2Environment(unittest.TestCase): def setUp(self): - # create the environment and scenario classes for a grid network - self.env, _ = grid_mxn_exp_setup(row_num=2, col_num=2) + # create the environment and scenario classes for a traffic light grid network + self.env, _ = traffic_light_grid_mxn_exp_setup(row_num=2, col_num=2) self.env.reset() # instantiate an experiment class diff --git a/tests/fast_tests/test_params.py b/tests/fast_tests/test_params.py index c7ecb3c321..de1b497dfb 100644 --- a/tests/fast_tests/test_params.py +++ b/tests/fast_tests/test_params.py @@ -2,7 +2,7 @@ from flow.core.params import EnvParams, SumoParams, SumoLaneChangeParams, \ SumoCarFollowingParams, VehicleParams, NetParams from flow.envs import Env -from flow.scenarios import LoopScenario +from flow.scenarios import RingScenario import os import numpy as np from gym.spaces import Box @@ -46,7 +46,7 @@ def test_clip_actions(self): "resolution": 40 } net_params = NetParams(additional_params=additional_net_params) - scenario = LoopScenario( + scenario = RingScenario( name="RingRoadTest", vehicles=vehicles, net_params=net_params) diff --git a/tests/fast_tests/test_scenario_base_class.py b/tests/fast_tests/test_scenario_base_class.py index cf580fe08b..30f6efc660 100644 --- a/tests/fast_tests/test_scenario_base_class.py +++ b/tests/fast_tests/test_scenario_base_class.py @@ -9,7 +9,7 @@ from flow.core.params import EnvParams from flow.core.params import SumoParams from flow.core.params import SumoCarFollowingParams -from flow.scenarios.loop import LoopScenario, ADDITIONAL_NET_PARAMS +from flow.scenarios.ring import RingScenario, ADDITIONAL_NET_PARAMS from flow.envs import TestEnv from flow.scenarios import Scenario @@ -23,7 +23,7 @@ os.environ["TEST_FLAG"] = "True" -class NoRouteNetwork(LoopScenario): +class NoRouteNetwork(RingScenario): """A network with no routes. Used to check for default route assignment. @@ -95,7 +95,7 @@ def test_get_edge(self): class TestEvenStartPos(unittest.TestCase): """ - Tests the function gen_even_start_pos in base_scenario.py. This function + Tests the function gen_even_start_pos in scenarios/base.py. This function can be used on any scenario subclass, and therefore may be tested on any of these classes. In order to perform this testing, replace the scenario in setUp() with the scenario to be tested. @@ -499,7 +499,7 @@ def test_even_start_pos_internal(self): class TestRandomStartPos(unittest.TestCase): """ - Tests the function gen_random_start_pos in base_scenario.py. + Tests the function gen_random_start_pos in scenarios/base.py. """ def setUp_gen_start_pos(self, initial_config=InitialConfig()): diff --git a/tests/fast_tests/test_scenarios.py b/tests/fast_tests/test_scenarios.py index 3b88118cba..2aa6eac1d7 100644 --- a/tests/fast_tests/test_scenarios.py +++ b/tests/fast_tests/test_scenarios.py @@ -2,12 +2,12 @@ import os from flow.core.params import VehicleParams from flow.core.params import NetParams -from flow.scenarios import BottleneckScenario, Figure8Scenario, \ - SimpleGridScenario, HighwayScenario, LoopScenario, MergeScenario, \ - TwoLoopsOneMergingScenario, MiniCityScenario, MultiLoopScenario +from flow.scenarios import BottleneckScenario, FigureEightScenario, \ + TrafficLightGridScenario, HighwayScenario, RingScenario, MergeScenario, \ + MiniCityScenario, MultiRingScenario __all__ = [ - "MultiLoopScenario", "MiniCityScenario" + "MultiRingScenario", "MiniCityScenario" ] os.environ["TEST_FLAG"] = "True" @@ -30,15 +30,15 @@ def test_additional_net_params(self): ) -class TestFigure8Scenario(unittest.TestCase): +class TestFigureEightScenario(unittest.TestCase): - """Tests Figure8Scenario in flow/scenarios/figure_eight.py.""" + """Tests FigureEightScenario in flow/scenarios/figure_eight.py.""" def test_additional_net_params(self): """Ensures that not returning the correct params leads to an error.""" self.assertTrue( test_additional_params( - scenario_class=Figure8Scenario, + scenario_class=FigureEightScenario, additional_params={ "radius_ring": 30, "lanes": 1, @@ -49,15 +49,15 @@ def test_additional_net_params(self): ) -class TestSimpleGridScenario(unittest.TestCase): +class TestTrafficLightGridScenario(unittest.TestCase): - """Tests SimpleGridScenario in flow/scenarios/grid.py.""" + """Tests TrafficLightGridScenario in flow/scenarios/traffic_light_grid.py.""" def test_additional_net_params(self): """Ensures that not returning the correct params leads to an error.""" self.assertTrue( test_additional_params( - scenario_class=SimpleGridScenario, + scenario_class=TrafficLightGridScenario, additional_params={ "grid_array": { "row_num": 3, @@ -100,15 +100,15 @@ def test_additional_net_params(self): ) -class TestLoopScenario(unittest.TestCase): +class TestRingScenario(unittest.TestCase): - """Tests LoopScenario in flow/scenarios/loop.py.""" + """Tests RingScenario in flow/scenarios/ring.py.""" def test_additional_net_params(self): """Ensures that not returning the correct params leads to an error.""" self.assertTrue( test_additional_params( - scenario_class=LoopScenario, + scenario_class=RingScenario, additional_params={ "length": 230, "lanes": 1, @@ -119,27 +119,6 @@ def test_additional_net_params(self): ) -class TestTwoLoopsOneMergingScenario(unittest.TestCase): - - """Tests TwoLoopsOneMergingScenario in flow/scenarios/loop_merge.py.""" - - def test_additional_net_params(self): - """Ensures that not returning the correct params leads to an error.""" - self.assertTrue( - test_additional_params( - scenario_class=TwoLoopsOneMergingScenario, - additional_params={ - "ring_radius": 50, - "lane_length": 75, - "inner_lanes": 3, - "outer_lanes": 2, - "speed_limit": 30, - "resolution": 40 - } - ) - ) - - class TestMergeScenario(unittest.TestCase): """Tests MergeScenario in flow/scenarios/merge.py.""" @@ -161,15 +140,15 @@ def test_additional_net_params(self): ) -class TestMultiLoopScenario(unittest.TestCase): +class TestMultiRingScenario(unittest.TestCase): - """Tests MultiLoopScenario in flow/scenarios/multi_loop.py.""" + """Tests MultiRingScenario in flow/scenarios/multi_ring.py.""" def test_additional_net_params(self): """Ensures that not returning the correct params leads to an error.""" self.assertTrue( test_additional_params( - scenario_class=MultiLoopScenario, + scenario_class=MultiRingScenario, additional_params={ "length": 230, "lanes": 1, diff --git a/tests/fast_tests/test_traffic_lights.py b/tests/fast_tests/test_traffic_lights.py index 5686938fc1..263ca55f2d 100644 --- a/tests/fast_tests/test_traffic_lights.py +++ b/tests/fast_tests/test_traffic_lights.py @@ -1,7 +1,7 @@ import unittest import os -from tests.setup_scripts import ring_road_exp_setup, grid_mxn_exp_setup +from tests.setup_scripts import ring_road_exp_setup, traffic_light_grid_mxn_exp_setup from flow.core.params import VehicleParams from flow.core.params import NetParams from flow.core.params import SumoCarFollowingParams @@ -153,7 +153,7 @@ def setUp(self): min_gap=2.5, tau=1.1), num_vehicles=16) - self.env, scenario = grid_mxn_exp_setup( + self.env, scenario = traffic_light_grid_mxn_exp_setup( row_num=1, col_num=3, vehicles=vehicles) def tearDown(self): @@ -228,7 +228,7 @@ def setUp(self): min_gap=2.5, tau=1.1), num_vehicles=16) - env, scenario = grid_mxn_exp_setup( + env, scenario = traffic_light_grid_mxn_exp_setup( row_num=1, col_num=3, vehicles=vehicles) self.exp = Experiment(env) @@ -285,7 +285,7 @@ def setUp(self): file="testindividuallights.xml", freq=100) - env, scenario = grid_mxn_exp_setup( + env, scenario = traffic_light_grid_mxn_exp_setup( row_num=1, col_num=4, tl_logic=tl_logic) self.exp = Experiment(env) diff --git a/tests/fast_tests/test_util.py b/tests/fast_tests/test_util.py index d152679f17..3caf8d4e31 100644 --- a/tests/fast_tests/test_util.py +++ b/tests/fast_tests/test_util.py @@ -10,7 +10,7 @@ from flow.core.params import SumoParams, EnvParams, NetParams, InitialConfig, \ InFlows, SumoCarFollowingParams from flow.core.util import emission_to_csv -from flow.utils.flow_warnings import deprecation_warning +from flow.utils.flow_warnings import deprecated_attribute from flow.utils.registry import make_create_env from flow.utils.rllib import FlowParamsEncoder, get_flow_params @@ -59,7 +59,7 @@ def test_emission_to_csv(self): class TestWarnings(unittest.TestCase): """Tests warning functions located in flow.utils.warnings""" - def test_deprecation_warning(self): + def test_deprecated_attribute(self): # dummy class class Foo(object): pass @@ -71,7 +71,7 @@ class Foo(object): # check the deprecation warning is printing what is expected self.assertWarnsRegex( UserWarning, "The attribute bar_deprecated in Foo is deprecated, " - "use bar_new instead.", deprecation_warning, Foo(), dep_from, + "use bar_new instead.", deprecated_attribute, Foo(), dep_from, dep_to) @@ -105,7 +105,7 @@ def test_make_create_env(self): flow_params = dict( exp_tag="figure_eight_0", env_name="AccelEnv", - scenario="Figure8Scenario", + scenario="FigureEightScenario", simulator='traci', sim=SumoParams( sim_step=0.1, @@ -217,7 +217,7 @@ def test_encoder_and_get_flow_params(self): flow_params = dict( exp_tag="merge_0", - env_name="WaveAttenuationMergePOEnv", + env_name="MergePOEnv", scenario="MergeScenario", sim=SumoParams( restart_instance=True, diff --git a/tests/fast_tests/test_visualizers.py b/tests/fast_tests/test_visualizers.py index b9860f9779..3117f37f7c 100644 --- a/tests/fast_tests/test_visualizers.py +++ b/tests/fast_tests/test_visualizers.py @@ -239,10 +239,10 @@ def test_time_space_diagram_merge(self): def test_time_space_diagram_ring_road(self): dir_path = os.path.dirname(os.path.realpath(__file__)) emission_data = tsd.import_data_from_emission( - os.path.join(dir_path, 'test_files/loop_230_emission.csv')) + os.path.join(dir_path, 'test_files/ring_230_emission.csv')) flow_params = tsd.get_flow_params( - os.path.join(dir_path, 'test_files/loop_230.json')) + os.path.join(dir_path, 'test_files/ring_230.json')) pos, speed, _ = tsd.get_time_space_data(emission_data, flow_params) expected_pos = np.array( diff --git a/tests/setup_scripts.py b/tests/setup_scripts.py index a3da841b9a..82ee29b8f3 100644 --- a/tests/setup_scripts.py +++ b/tests/setup_scripts.py @@ -14,12 +14,12 @@ SumoCarFollowingParams from flow.core.params import TrafficLightParams from flow.core.params import VehicleParams -from flow.envs.green_wave_env import GreenWaveTestEnv -from flow.envs.loop.loop_accel import AccelEnv -from flow.scenarios.figure_eight import Figure8Scenario -from flow.scenarios.grid import SimpleGridScenario +from flow.envs.traffic_light_grid import TrafficLightGridTestEnv +from flow.envs.ring.accel import AccelEnv +from flow.scenarios.figure_eight import FigureEightScenario +from flow.scenarios.traffic_light_grid import TrafficLightGridScenario from flow.scenarios.highway import HighwayScenario -from flow.scenarios.loop import LoopScenario +from flow.scenarios.ring import RingScenario def ring_road_exp_setup(sim_params=None, @@ -98,7 +98,7 @@ def ring_road_exp_setup(sim_params=None, traffic_lights = TrafficLightParams() # create the scenario - scenario = LoopScenario( + scenario = RingScenario( name="RingRoadTest", vehicles=vehicles, net_params=net_params, @@ -191,7 +191,7 @@ def figure_eight_exp_setup(sim_params=None, traffic_lights = TrafficLightParams() # create the scenario - scenario = Figure8Scenario( + scenario = FigureEightScenario( name="FigureEightTest", vehicles=vehicles, net_params=net_params, @@ -302,23 +302,23 @@ def highway_exp_setup(sim_params=None, return env, scenario -def grid_mxn_exp_setup(row_num=1, - col_num=1, - sim_params=None, - vehicles=None, - env_params=None, - net_params=None, - initial_config=None, - tl_logic=None): +def traffic_light_grid_mxn_exp_setup(row_num=1, + col_num=1, + sim_params=None, + vehicles=None, + env_params=None, + net_params=None, + initial_config=None, + tl_logic=None): """ - Create an environment and scenario pair for grid 1x1 test experiments. + Create an environment and scenario pair for traffic light grid 1x1 test experiments. Parameters ---------- row_num: int, optional - number of horizontal rows of edges in the grid network + number of horizontal rows of edges in the traffic light grid network col_num: int, optional - number of vertical columns of edges in the grid network + number of vertical columns of edges in the traffic light grid network sim_params : flow.core.params.SumoParams sumo-related configuration parameters, defaults to a time step of 1s and no sumo-imposed failsafe on human or rl vehicles @@ -330,8 +330,8 @@ def grid_mxn_exp_setup(row_num=1, environment-specific parameters, defaults to a environment with failsafes, where other parameters do not matter for non-rl runs net_params : flow.core.params.NetParams - network-specific configuration parameters, defaults to a 1x1 grid - with traffic lights on + network-specific configuration parameters, defaults to a 1x1 traffic + light grid with traffic lights on initial_config : flow.core.params.InitialConfig specifies starting positions of vehicles, defaults to evenly distributed vehicles across the length of the network @@ -410,7 +410,7 @@ def grid_mxn_exp_setup(row_num=1, spacing="custom", additional_params={"enter_speed": 30}) # create the scenario - scenario = SimpleGridScenario( + scenario = TrafficLightGridScenario( name="Grid1x1Test", vehicles=vehicles, net_params=net_params, @@ -418,7 +418,7 @@ def grid_mxn_exp_setup(row_num=1, traffic_lights=tl_logic) # create the environment - env = GreenWaveTestEnv( + env = TrafficLightGridTestEnv( env_params=env_params, sim_params=sim_params, scenario=scenario) # reset the environment @@ -523,7 +523,7 @@ def variable_lanes_exp_setup(sim_params=None, return env, scenario -class VariableLanesScenario(LoopScenario): +class VariableLanesScenario(RingScenario): """Instantiate a ring road with variable number of lanes per edge.""" def specify_edges(self, net_params): diff --git a/tests/slow_tests/test_benchmarks.py b/tests/slow_tests/test_benchmarks.py index a187e7a468..8c62763725 100644 --- a/tests/slow_tests/test_benchmarks.py +++ b/tests/slow_tests/test_benchmarks.py @@ -91,7 +91,7 @@ def ray_runner(self, num_runs, flow_params, version): def test_bottleneck0(self): """ Tests flow/benchmark/baselines/bottleneck0.py - env_name='DesiredVelocityEnv', + env_name='BottleneckDesiredVelocityEnv', """ # run the bottleneck to make sure it runs self.ray_runner(1, bottleneck0.flow_params, 0) @@ -147,7 +147,7 @@ def test_figure_eight2(self): def test_grid0(self): """ Tests flow/benchmark/baselines/grid0.py - env_name='PO_TrafficLightGridEnv', + env_name='TrafficLightGridPOEnv', """ # run the bottleneck to make sure it runs self.ray_runner(1, grid0.flow_params, 0) @@ -166,7 +166,7 @@ def test_grid1(self): def test_merge0(self): """ Tests flow/benchmark/baselines/merge{0,1,2}.py - env_name='WaveAttenuationMergePOEnv', + env_name='MergePOEnv', """ # run the bottleneck to make sure it runs self.ray_runner(1, merge0.flow_params, 0) diff --git a/tests/stress_tests/stress_test_start.py b/tests/stress_tests/stress_test_start.py index f60f28f17c..45a82c0fa2 100644 --- a/tests/stress_tests/stress_test_start.py +++ b/tests/stress_tests/stress_test_start.py @@ -3,8 +3,8 @@ from flow.core.params import SumoParams, EnvParams, \ InitialConfig, NetParams from flow.core.params import VehicleParams -from flow.envs.loop.loop_accel import AccelEnv, ADDITIONAL_ENV_PARAMS -from flow.scenarios.loop import LoopScenario, ADDITIONAL_NET_PARAMS +from flow.envs.ring.accel import AccelEnv, ADDITIONAL_ENV_PARAMS +from flow.scenarios.ring import RingScenario, ADDITIONAL_NET_PARAMS import ray @@ -27,7 +27,7 @@ def start(): initial_config = InitialConfig(bunching=20) - scenario = LoopScenario( + scenario = RingScenario( name="sugiyama", vehicles=vehicles, net_params=net_params, diff --git a/tutorials/tutorial01_sumo.ipynb b/tutorials/tutorial01_sumo.ipynb index cb30e18fdf..646f2eec39 100644 --- a/tutorials/tutorial01_sumo.ipynb +++ b/tutorials/tutorial01_sumo.ipynb @@ -14,7 +14,7 @@ "All simulations, both in the presence and absence of RL, require two components: a *scenario*, and an *environment*. Scenarios describe the features of the transportation network used in simulation. This includes the positions and properties of nodes and edges constituting the lanes and junctions, as well as properties of the vehicles, traffic lights, inflows, etc. in the network. Environments, on the other hand, initialize, reset, and advance simulations, and act the primary interface between the reinforcement learning algorithm and the scenario. Moreover, custom environments may be used to modify the dynamical features of an scenario.\n", "\n", "## 2. Setting up a Scenario\n", - "Flow contains a plethora of pre-designed scenarios used to replicate highways, intersections, and merges in both closed and open settings. All these scenarios are located in flow/scenarios. In order to recreate a ring road network, we begin by importing the scenario `LoopScenario`." + "Flow contains a plethora of pre-designed scenarios used to replicate highways, intersections, and merges in both closed and open settings. All these scenarios are located in flow/scenarios. In order to recreate a ring road network, we begin by importing the scenario `RingScenario`." ] }, { @@ -23,7 +23,7 @@ "metadata": {}, "outputs": [], "source": [ - "from flow.scenarios.loop import LoopScenario" + "from flow.scenarios.ring import RingScenario" ] }, { @@ -37,7 +37,7 @@ "* initial_config\n", "* traffic_lights\n", "\n", - "These parameters allow a single scenario to be recycled for a multitude of different network settings. For example, `LoopScenario` may be used to create ring roads of variable length with a variable number of lanes and vehicles.\n", + "These parameters allow a single scenario to be recycled for a multitude of different network settings. For example, `RingScenario` may be used to create ring roads of variable length with a variable number of lanes and vehicles.\n", "\n", "### 2.1 Name\n", "The `name` argument is a string variable depicting the name of the scenario. This has no effect on the type of network created." @@ -141,7 +141,7 @@ "metadata": {}, "outputs": [], "source": [ - "from flow.scenarios.loop import ADDITIONAL_NET_PARAMS\n", + "from flow.scenarios.ring import ADDITIONAL_NET_PARAMS\n", "\n", "print(ADDITIONAL_NET_PARAMS)" ] @@ -227,7 +227,7 @@ "metadata": {}, "outputs": [], "source": [ - "from flow.envs.loop.loop_accel import AccelEnv" + "from flow.envs.ring.accel import AccelEnv" ] }, { @@ -273,7 +273,7 @@ "metadata": {}, "outputs": [], "source": [ - "from flow.envs.loop.loop_accel import ADDITIONAL_ENV_PARAMS\n", + "from flow.envs.ring.accel import ADDITIONAL_ENV_PARAMS\n", "\n", "print(ADDITIONAL_ENV_PARAMS)" ] @@ -329,7 +329,7 @@ "outputs": [], "source": [ "# create the scenario object\n", - "scenario = LoopScenario(name=\"ring_example\",\n", + "scenario = RingScenario(name=\"ring_example\",\n", " vehicles=vehicles,\n", " net_params=net_params,\n", " initial_config=initial_config,\n", diff --git a/tutorials/tutorial02_aimsun.ipynb b/tutorials/tutorial02_aimsun.ipynb index 921c8f3e06..88a7cd459c 100644 --- a/tutorials/tutorial02_aimsun.ipynb +++ b/tutorials/tutorial02_aimsun.ipynb @@ -18,7 +18,7 @@ "All simulations, both in the presence and absence of RL, require two components: a *scenario*, and an *environment*. Scenarios describe the features of the transportation network used in simulation. This includes the positions and properties of nodes and edges constituting the lanes and junctions, as well as properties of the vehicles, traffic lights, inflows, etc. in the network. Environments, on the other hand, initialize, reset, and advance simulations, and act the primary interface between the reinforcement learning algorithm and the scenario. Moreover, custom environments may be used to modify the dynamical features of an scenario.\n", "\n", "## 2. Setting up a Scenario\n", - "Flow contains a plethora of pre-designed scenarios used to replicate highways, intersections, and merges in both closed and open settings. All these scenarios are located in flow/scenarios. In order to recreate a ring road network, we begin by importing the scenario `LoopScenario`." + "Flow contains a plethora of pre-designed scenarios used to replicate highways, intersections, and merges in both closed and open settings. All these scenarios are located in flow/scenarios. In order to recreate a ring road network, we begin by importing the scenario `RingScenario`." ] }, { @@ -27,7 +27,7 @@ "metadata": {}, "outputs": [], "source": [ - "from flow.scenarios.loop import LoopScenario" + "from flow.scenarios.ring import RingScenario" ] }, { @@ -41,7 +41,7 @@ "* initial_config\n", "* traffic_lights\n", "\n", - "These parameters allow a single scenario to be recycled for a multitude of different network settings. For example, `LoopScenario` may be used to create ring roads of variable length with a variable number of lanes and vehicles.\n", + "These parameters allow a single scenario to be recycled for a multitude of different network settings. For example, `RingScenario` may be used to create ring roads of variable length with a variable number of lanes and vehicles.\n", "\n", "### 2.1 Name\n", "The `name` argument is a string variable depicting the name of the scenario. This has no effect on the type of network created." @@ -130,7 +130,7 @@ "metadata": {}, "outputs": [], "source": [ - "from flow.scenarios.loop import ADDITIONAL_NET_PARAMS\n", + "from flow.scenarios.ring import ADDITIONAL_NET_PARAMS\n", "\n", "print(ADDITIONAL_NET_PARAMS)" ] @@ -235,7 +235,7 @@ "metadata": {}, "outputs": [], "source": [ - "from flow.envs.loop.loop_accel import AccelEnv" + "from flow.envs.ring.accel import AccelEnv" ] }, { @@ -281,7 +281,7 @@ "metadata": {}, "outputs": [], "source": [ - "from flow.envs.loop.loop_accel import ADDITIONAL_ENV_PARAMS\n", + "from flow.envs.ring.accel import ADDITIONAL_ENV_PARAMS\n", "\n", "print(ADDITIONAL_ENV_PARAMS)" ] @@ -338,7 +338,7 @@ "outputs": [], "source": [ "# create the scenario object\n", - "scenario = LoopScenario(name=\"ring_example\",\n", + "scenario = RingScenario(name=\"ring_example\",\n", " vehicles=vehicles,\n", " net_params=net_params,\n", " initial_config=initial_config,\n", diff --git a/tutorials/tutorial03_rllib.ipynb b/tutorials/tutorial03_rllib.ipynb index 7f9994b005..9022f786c7 100644 --- a/tutorials/tutorial03_rllib.ipynb +++ b/tutorials/tutorial03_rllib.ipynb @@ -14,7 +14,7 @@ "All simulations, both in the presence and absence of RL, require two components: a *scenario*, and an *environment*. Scenarios describe the features of the transportation network used in simulation. This includes the positions and properties of nodes and edges constituting the lanes and junctions, as well as properties of the vehicles, traffic lights, inflows, etc... in the network. Environments, on the other hand, initialize, reset, and advance simulations, and act as the primary interface between the reinforcement learning algorithm and the scenario. Moreover, custom environments may be used to modify the dynamical features of an scenario. Finally, in the RL case, it is in the *environment* that the state/action spaces and the reward function are defined. \n", "\n", "## 2. Setting up a Scenario\n", - "Flow contains a plethora of pre-designed scenarios used to replicate highways, intersections, and merges in both closed and open settings. All these scenarios are located in flow/scenarios. For this exercise, which involves a single lane ring road, we will use the scenario `LoopScenario`.\n", + "Flow contains a plethora of pre-designed scenarios used to replicate highways, intersections, and merges in both closed and open settings. All these scenarios are located in flow/scenarios. For this exercise, which involves a single lane ring road, we will use the scenario `RingScenario`.\n", "\n", "### 2.1 Setting up Scenario Parameters\n", "\n", @@ -54,7 +54,7 @@ "outputs": [], "source": [ "# ring road scenario class\n", - "scenario_name = \"LoopScenario\"" + "scenario_name = \"RingScenario\"" ] }, { @@ -77,7 +77,7 @@ "name = \"training_example\"\n", "\n", "# network-specific parameters\n", - "from flow.scenarios.loop import ADDITIONAL_NET_PARAMS\n", + "from flow.scenarios.ring import ADDITIONAL_NET_PARAMS\n", "net_params = NetParams(additional_params=ADDITIONAL_NET_PARAMS)\n", "\n", "# initial configuration to vehicles\n", diff --git a/tutorials/tutorial05_scenarios.ipynb b/tutorials/tutorial05_scenarios.ipynb index 2f612db376..f448e5f1b6 100644 --- a/tutorials/tutorial05_scenarios.ipynb +++ b/tutorials/tutorial05_scenarios.ipynb @@ -6,7 +6,7 @@ "source": [ "# Tutorial 05: Creating Custom Scenarios\n", "\n", - "This tutorial walks you through the process of generating custom scenarios. Scenarios define the network geometry of a task, as well as the constituents of the network, e.g. vehicles, traffic lights, etc... Various scenarios are available in Flow, depicting a diverse set of open and closed traffic networks such as ring roads, intersections/grids, straight highway merges, and more. \n", + "This tutorial walks you through the process of generating custom scenarios. Scenarios define the network geometry of a task, as well as the constituents of the network, e.g. vehicles, traffic lights, etc... Various scenarios are available in Flow, depicting a diverse set of open and closed traffic networks such as ring roads, intersections, traffic light grids, straight highway merges, and more. \n", "\n", "In this exercise, we will recreate the ring road network, seen in the figure below.\n", "\n", @@ -52,7 +52,7 @@ "\n", "All of the functions mentioned above paragraph take in as input `net_params`, and output a list of dictionary elements, with each element providing the attributes of the component to be specified.\n", "\n", - "This tutorial will cover the first three methods. For examples of `specify_types` and `specify_routes`, refer to source code located in `flow/scenarios/loop.py` and `flow/scenarios/bridge_toll.py`, respectively." + "This tutorial will cover the first three methods. For examples of `specify_types` and `specify_routes`, refer to source code located in `flow/scenarios/ring.py` and `flow/scenarios/bridge_toll.py`, respectively." ] }, { @@ -409,7 +409,7 @@ "metadata": {}, "outputs": [], "source": [ - "from flow.envs.loop.loop_accel import AccelEnv, ADDITIONAL_ENV_PARAMS\n", + "from flow.envs.ring.accel import AccelEnv, ADDITIONAL_ENV_PARAMS\n", "\n", "env_params = EnvParams(additional_params=ADDITIONAL_ENV_PARAMS)" ] diff --git a/tutorials/tutorial07_network_templates.ipynb b/tutorials/tutorial07_network_templates.ipynb index 7c995ca440..11bf2f14d8 100644 --- a/tutorials/tutorial07_network_templates.ipynb +++ b/tutorials/tutorial07_network_templates.ipynb @@ -174,7 +174,7 @@ "\u001b[0;31mFatalTraCIError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 16\u001b[0m \u001b[0;31m# run the simulation for 1000 steps\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 17\u001b[0m \u001b[0mexp\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mExperiment\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0menv\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0menv\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 18\u001b[0;31m \u001b[0m_\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mexp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrun\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m1000\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;32m~/Documents/flow/flow/core/experiment.py\u001b[0m in \u001b[0;36mrun\u001b[0;34m(self, num_runs, num_steps, rl_actions, convert_to_csv)\u001b[0m\n\u001b[1;32m 104\u001b[0m \u001b[0mstate\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0menv\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mreset\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 105\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mj\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mrange\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnum_steps\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 106\u001b[0;31m \u001b[0mstate\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mreward\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdone\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0m_\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0menv\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstep\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mrl_actions\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mstate\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 107\u001b[0m vel[j] = np.mean(\n\u001b[1;32m 108\u001b[0m self.env.k.vehicle.get_speed(self.env.k.vehicle.get_ids()))\n", - "\u001b[0;32m~/Documents/flow/flow/envs/base_env.py\u001b[0m in \u001b[0;36mstep\u001b[0;34m(self, rl_actions)\u001b[0m\n\u001b[1;32m 325\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 326\u001b[0m \u001b[0;31m# advance the simulation in the simulator by one step\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 327\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mk\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msimulation\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msimulation_step\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 328\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 329\u001b[0m \u001b[0;31m# store new observations in the vehicles and traffic lights class\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/Documents/flow/flow/envs/base.py\u001b[0m in \u001b[0;36mstep\u001b[0;34m(self, rl_actions)\u001b[0m\n\u001b[1;32m 325\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 326\u001b[0m \u001b[0;31m# advance the simulation in the simulator by one step\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 327\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mk\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msimulation\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msimulation_step\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 328\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 329\u001b[0m \u001b[0;31m# store new observations in the vehicles and traffic lights class\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m~/Documents/flow/flow/core/kernel/simulation/traci.py\u001b[0m in \u001b[0;36msimulation_step\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 54\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0msimulation_step\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 55\u001b[0m \u001b[0;34m\"\"\"See parent class.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 56\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mkernel_api\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msimulationStep\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 57\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 58\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mupdate\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mreset\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/usr/local/lib/python3.6/dist-packages/traci/connection.py\u001b[0m in \u001b[0;36msimulationStep\u001b[0;34m(self, step)\u001b[0m\n\u001b[1;32m 273\u001b[0m self._string += struct.pack(\"!BBi\", 1 +\n\u001b[1;32m 274\u001b[0m 1 + 4, tc.CMD_SIMSTEP, step)\n\u001b[0;32m--> 275\u001b[0;31m \u001b[0mresult\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_sendExact\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 276\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0msubscriptionResults\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_subscriptionMapping\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mvalues\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 277\u001b[0m \u001b[0msubscriptionResults\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mreset\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/usr/local/lib/python3.6/dist-packages/traci/connection.py\u001b[0m in \u001b[0;36m_sendExact\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 95\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_socket\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mclose\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 96\u001b[0m \u001b[0;32mdel\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_socket\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 97\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mFatalTraCIError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"connection closed by SUMO\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 98\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mcommand\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_queue\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 99\u001b[0m \u001b[0mprefix\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mresult\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mread\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"!BBB\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", diff --git a/tutorials/tutorial08_environments.ipynb b/tutorials/tutorial08_environments.ipynb index c8c82cd6da..6b7a2414db 100644 --- a/tutorials/tutorial08_environments.ipynb +++ b/tutorials/tutorial08_environments.ipynb @@ -287,7 +287,7 @@ "from flow.core.params import SumoParams, EnvParams, \\\n", " InitialConfig, NetParams\n", "from flow.core.params import VehicleParams\n", - "from flow.scenarios.loop import LoopScenario, ADDITIONAL_NET_PARAMS\n", + "from flow.scenarios.ring import RingScenario, ADDITIONAL_NET_PARAMS\n", "\n", "sumo_params = SumoParams(sim_step=0.1, render=True)\n", "\n", @@ -304,7 +304,7 @@ "\n", "initial_config = InitialConfig(bunching=20)\n", "\n", - "scenario = LoopScenario(name=\"sugiyama\",\n", + "scenario = RingScenario(name=\"sugiyama\",\n", " vehicles=vehicles,\n", " net_params=net_params,\n", " initial_config=initial_config)\n", @@ -360,7 +360,7 @@ "from rllab.baselines.linear_feature_baseline import LinearFeatureBaseline\n", "from rllab.policies.gaussian_gru_policy import GaussianGRUPolicy\n", "\n", - "from flow.scenarios.loop import LoopScenario\n", + "from flow.scenarios.ring import RingScenario\n", "from flow.controllers import RLController, IDMController, ContinuousRouter\n", "from flow.core.params import VehicleParams\n", "from flow.core.params import SumoParams, EnvParams, NetParams, InitialConfig\n", @@ -390,7 +390,7 @@ "\n", " initial_config = InitialConfig(bunching=20)\n", "\n", - " scenario = LoopScenario(name=\"sugiyama-training\",\n", + " scenario = RingScenario(name=\"sugiyama-training\",\n", " vehicles=vehicles,\n", " net_params=net_params,\n", " initial_config=initial_config)\n", diff --git a/tutorials/tutorial09_controllers.ipynb b/tutorials/tutorial09_controllers.ipynb index 5a3ecb9de6..eb09e7c3fd 100644 --- a/tutorials/tutorial09_controllers.ipynb +++ b/tutorials/tutorial09_controllers.ipynb @@ -23,7 +23,7 @@ "\n", "### 1.1 BaseController\n", "\n", - "Flow's `BaseController` class is an abstract class to use when implementing longitudinal controllers. It includes failsafe methods and the `get_action` method called by Flow's `core.base_env` module. `get_action` adds noise to actions and runs failsafes, if specified. `BaseController` does not implement `get_accel`; that method should be implemented in any controllers that are subclasses of `BaseController`. \n", + "Flow's `BaseController` class is an abstract class to use when implementing longitudinal controllers. It includes failsafe methods and the `get_action` method called by Flow's `core.base` module. `get_action` adds noise to actions and runs failsafes, if specified. `BaseController` does not implement `get_accel`; that method should be implemented in any controllers that are subclasses of `BaseController`. \n", "\n", "As such, any longitudinal controller must import `BaseController`. We also import NumPy in order to use some mathematical functions." ] diff --git a/tutorials/tutorial10_traffic_lights.ipynb b/tutorials/tutorial10_traffic_lights.ipynb index bd185dfcdb..8615643a78 100644 --- a/tutorials/tutorial10_traffic_lights.ipynb +++ b/tutorials/tutorial10_traffic_lights.ipynb @@ -15,11 +15,11 @@ "source": [ "This tutorial walks through how to add traffic lights to experiments. This tutorial will use the following files:\n", "\n", - "* Experiment script for RL version of traffic lights in grid: `examples/rllib/green_wave.py`\n", - "* Experiment script for non-RL version of traffic lights in grid: `examples/sumo/grid.py`\n", - "* Scenario: `grid.py` (class SimpleGridScenario)\n", - "* Environment for RL version of traffic lights in grid: `green_wave_env.py` (class TrafficLightGridEnv)\n", - "* Environment for non-RL version of traffic lights in grid: `loop_accel.py` (class AccelEnv)\n", + "* Experiment script for RL version of traffic lights in grid:\n", + "* Experiment script for non-RL version of traffic lights in grid:\n", + "* Scenario: `traffic_light_grid.py` (class TrafficLightGridScenario)\n", + "* Environment for RL version of traffic lights in grid (class TrafficLightGridEnv)\n", + "* Environment for non-RL version of traffic lights in grid (class AccelEnv)\n", "\n", "There are two main classes of traffic lights that Sumo supports: (1) actuated and (2) static traffic lights. This tutorial will cover both types. Moreover, in this tutorial, we'll discuss another type of traffic light. In total, we have 4 types of traffic lights in the Flow:\n", "\n", @@ -40,7 +40,7 @@ "outputs": [], "source": [ "from flow.core.params import NetParams\n", - "from flow.scenarios.grid import SimpleGridScenario\n", + "from flow.scenarios.traffic_light_grid import TrafficLightGridScenario\n", "from flow.core.params import TrafficLightParams\n", "from flow.core.params import SumoParams, EnvParams, InitialConfig, NetParams, \\\n", " InFlows, SumoCarFollowingParams\n", @@ -59,10 +59,10 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "There are a few unique additions to `additional_net_params` in the grid envirinments to be aware of. They are the following 2 items:\n", + "There are a few unique additions to `additional_net_params` in the traffic light grid envirinments to be aware of. They are the following 2 items:\n", "\n", "#### grid_array\n", - "`grid_array` passes information on the road network to the scenario, specifying the parameters you see below: `row_num`, `col_num`, `inner_length`, `short_length`, `long_length`, `cars_top`, `cars_bot`, `cars_left`, `cars_right`. This is required for any grid experiment.\n", + "`grid_array` passes information on the road network to the scenario, specifying the parameters you see below: `row_num`, `col_num`, `inner_length`, `short_length`, `long_length`, `cars_top`, `cars_bot`, `cars_left`, `cars_right`. This is required for any traffic light grid experiment.\n", "\n", "\n" ] @@ -74,7 +74,7 @@ "#### tl_logic\n", "`tl_logic` should be used for users who want to exert more control over individual traffic lights. `tl_logic` simply tells the env whether the traffic lights are controlled by RL or whether a default pattern or SUMO actuation is to be used. Use \"actuated\" if you want SUMO to control the traffic lights. \n", "\n", - "For this tutorial, we will assume the following parameters for the `grid_array`, which specifies a grid network with 2 rows and 3 columns. `traffic_lights` should be set to `True` for every experiment in this tutorial." + "For this tutorial, we will assume the following parameters for the `grid_array`, which specifies a traffic light grid network with 2 rows and 3 columns. `traffic_lights` should be set to `True` for every experiment in this tutorial." ] }, { @@ -198,7 +198,7 @@ "net_params = NetParams(no_internal_links=False,\n", " additional_params=additional_net_params)\n", "\n", - "scenario = SimpleGridScenario(name=\"grid\",\n", + "scenario = TrafficLightGridScenario(name=\"grid\",\n", " vehicles=VehicleParams(),\n", " net_params=net_params,\n", " initial_config=InitialConfig(),\n", diff --git a/tutorials/tutorial11_inflows.ipynb b/tutorials/tutorial11_inflows.ipynb index 2173d89b53..7d07348ac5 100644 --- a/tutorials/tutorial11_inflows.ipynb +++ b/tutorials/tutorial11_inflows.ipynb @@ -230,7 +230,7 @@ "outputs": [], "source": [ "from flow.core.params import SumoParams, EnvParams, InitialConfig\n", - "from flow.envs.loop.loop_accel import AccelEnv, ADDITIONAL_ENV_PARAMS\n", + "from flow.envs.ring.accel import AccelEnv, ADDITIONAL_ENV_PARAMS\n", "from flow.core.experiment import Experiment\n", "\n", "sumo_params = SumoParams(render=True,\n", diff --git a/tutorials/tutorial12_bottlenecks.ipynb b/tutorials/tutorial12_bottlenecks.ipynb index c1ee230af8..ed007b063e 100644 --- a/tutorials/tutorial12_bottlenecks.ipynb +++ b/tutorials/tutorial12_bottlenecks.ipynb @@ -52,7 +52,7 @@ "\n", "from flow.scenarios.bottleneck import BottleneckScenario\n", "from flow.controllers import SimLaneChangeController, ContinuousRouter\n", - "from flow.envs.bottleneck_env import BottleneckEnv\n", + "from flow.envs.bottleneck import BottleneckEnv\n", "from flow.core.experiment import Experiment\n", "\n", "import logging\n", @@ -225,7 +225,7 @@ "metadata": {}, "source": [ "## Adding a tollbooth\n", - "On the segment indicated in Fig. 4, we have set up a series of rules that allow users to mimic the effect of a tollbooth. If *disable_tb=False*, cars that approach the toll-booth have their color changed to blue to indicate that they are in the toll-booth region. Their lane changing is disabled. As they come to the front of the toll-booth, we sample from a gaussian to determine how long they should be held at the toll booth. The holding process is imitated by a red light that remains red for the duration of the holding time. As indicated in the figure, the outer lanes are fast-trak lanes; their mean holding time is set to be lower than the holding time of the other toll-booth lanes. For the exact values of the holding parameters, please refer to the *BottleneckEnv* class in *flow/envs/bottleneck_env.py*\n", + "On the segment indicated in Fig. 4, we have set up a series of rules that allow users to mimic the effect of a tollbooth. If *disable_tb=False*, cars that approach the toll-booth have their color changed to blue to indicate that they are in the toll-booth region. Their lane changing is disabled. As they come to the front of the toll-booth, we sample from a gaussian to determine how long they should be held at the toll booth. The holding process is imitated by a red light that remains red for the duration of the holding time. As indicated in the figure, the outer lanes are fast-trak lanes; their mean holding time is set to be lower than the holding time of the other toll-booth lanes. For the exact values of the holding parameters, please refer to the *BottleneckEnv* class in *flow/envs/bottleneck.py*\n", "\n", "\n", "\n", @@ -330,7 +330,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "For a reference on all of the different parameters that can be used to modify the ALINEA algorithm, please refer to the documentation in *BottleneckEnv* in *flow/envs/bottleneck_env.py*" + "For a reference on all of the different parameters that can be used to modify the ALINEA algorithm, please refer to the documentation in *BottleneckEnv* in *flow/envs/bottleneck.py*" ] }, {