diff --git a/sumo_project/actions.py b/sumo_project/actions.py index 69705db..484d7f5 100644 --- a/sumo_project/actions.py +++ b/sumo_project/actions.py @@ -3,13 +3,14 @@ Created on 17 oct. 2018 @author: Axel Huynh-Phuc, Thibaud Gasser """ +import traci +from traci._trafficlight import Logic from typing import Iterable -import traci from shapely.geometry.linestring import LineString from model import Area, Vehicle -from traci._trafficlight import Logic + def compute_edge_weight(edge_id): return (traci.edge.getCOEmission(edge_id) @@ -22,7 +23,7 @@ def adjust_edges_weights(): for edge_id in traci.edge.getIDList(): weight = compute_edge_weight(edge_id) # by default edges weight = length/mean speed traci.edge.setEffort(edge_id, weight) - + for veh_id in traci.vehicle.getIDList(): traci.vehicle.rerouteEffort(veh_id) @@ -58,19 +59,20 @@ def lock_area(area): def reverse_actions(area): #Reset max speed to original - if not area.limited_speed: + if area.limited_speed: area.limited_speed = False - for lane in area.lanes: - traci.lane.setMaxSpeed(lane.lane_id, lane.initial_max_speed / 3.6) + for lane in area._lanes: + traci.lane.setMaxSpeed(lane.lane_id, lane.initial_max_speed) #Reset traffic lights initial duration - if not area.tls_adjusted: + if area.tls_adjusted: area.tls_adjusted = False - for initial_logic in tl._logics: - traci.trafficlights.setCompleteRedYellowGreenDefinition(tl.tl_id, initial_logic) + for tl in area._tls: + for initial_logic in tl._logics: + traci.trafficlights.setCompleteRedYellowGreenDefinition(tl.tl_id, initial_logic._logic) #Unlock the area - if not area.locked: + if area.locked: area.locked = False for lane in area._lanes: traci.lane.setAllowed(lane.lane_id, '') #empty means all classes are allowed diff --git a/sumo_project/config.py b/sumo_project/config.py index decf4d5..e139ffb 100644 --- a/sumo_project/config.py +++ b/sumo_project/config.py @@ -2,116 +2,111 @@ Global configuration for the simulation """ +import datetime +import json +import logging import os import sys -import datetime -import logging -############################################################################### -############################# SIMULATION FILE ################################# -############################################################################### -if 'SUMO_HOME' in os.environ: - tools = os.path.join(os.environ['SUMO_HOME'], 'tools') - sys.path.append(tools) -else: - sys.exit("please declare environment variable 'SUMO_HOME'") - -_SUMOCMD = 'sumo' # use 'sumo-gui' cmd for UI -_SUMOCFG = "mulhouse_simulation/osm.sumocfg" -sumo_binary = os.path.join(os.environ['SUMO_HOME'], 'bin', _SUMOCMD) -sumo_cmd = [sumo_binary, "-c", _SUMOCFG] - -############################################################################### -################################## LOGS ####################################### -############################################################################### - -now = datetime.datetime.now() -current_date = now.strftime("%Y_%m_%d_%H_%M_%S") -LOG_FILENAME = f'sumo_logs_{current_date}.log' - -# create logger -logger = logging.getLogger("sumo_logger") -logger.setLevel(logging.INFO) -# create console handler and set level to info -handler = logging.FileHandler(LOG_FILENAME) -# create formatter -formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") -# add formatter to handler -handler.setFormatter(formatter) -# add handler to logger -logger.addHandler(handler) - -############################################################################### -########################## SIMULATION CONFIGURATION ########################### -############################################################################### - -CELLS_NUMBER = 10 -EMISSIONS_THRESHOLD = 500000 -n_steps = 200 - -############################################################################### -########################## ACTIONS CONFIGURATION ############################## -############################################################################### - -#Set this mode to True if you want running a basic simulation without actions -without_actions_mode = False - -#Limit the speed into areas when the threshold is exceeded -speed_rf = 0.1 -limit_speed_mode = True - -#Decrease all traffic lights duration into the area when the threshold is exceeded -trafficLights_duration_rf = 0.2 -adjust_traffic_light_mode = True - -#Vehicles are routed according to the less polluted route (HEAVY) -weight_routing_mode = False - -#Lock the area when the threshold is exceeded (NOT FIXED) -lock_area_mode = False - -#Weight routing mode cannot be combinated with other actions -if weight_routing_mode: - limit_speed_mode = False - adjust_traffic_light_mode = False +class Config: + + # Total of emissions of all pollutants in mg for n steps of simulation without acting on areas + # These constants are simulation dependant, you must change them according to your simulation + total_emissions100 = 13615949.148296086 + total_emissions200 = 43970763.15084738 + total_emissions300 = 87382632.0821697 + + def __init__(self): + '''Default constructor''' + + def import_config_file(self,config_file): + with open(config_file,'r') as f: + data = json.load(f) + + self._SUMOCMD = data["_SUMOCMD"] + self._SUMOCFG = data["_SUMOCFG"] + + self.areas_number = data["areas_number"] + self.emissions_threshold = data["emissions_threshold"] + self.n_steps = data["n_steps"] + self.window_size = data["window_size"] + + self.without_actions_mode = data["without_actions_mode"] + self.limit_speed_mode = data["limit_speed_mode"] + self.speed_rf = data["speed_rf"] + self.adjust_traffic_light_mode = data["adjust_traffic_light_mode"] + self.trafficLights_duration_rf = data["trafficLights_duration_rf"] + self.weight_routing_mode = data["weight_routing_mode"] + self.lock_area_mode = data["lock_area_mode"] + + self.check_config() + + def check_config(self): + #Weight routing mode cannot be combinated with other actions + if self.weight_routing_mode: + self.limit_speed_mode = False + self.adjust_traffic_light_mode = False + self.lock_area_mode = False + + #If without_actions_mode is choosen + if self.without_actions_mode: + self.limit_speed_mode = False + self.adjust_traffic_light_mode = False + self.weight_routing_mode = False + self.lock_area_mode = False + + + def __repr__(self) -> str: + return (str(f'grid : {self.areas_number}x{self.areas_number}\n') + + str(f'step number = {self.n_steps}\n') + + str(f'window size = {self.window_size}\n') + + str(f'weight routing mode = {self.weight_routing_mode}\n') + + str(f'lock area mode = {self.lock_area_mode}\n') + + str(f'limit speed mode = {self.limit_speed_mode}, RF = {self.speed_rf*100}%\n') + + str(f'adjust traffic light mode = {self.adjust_traffic_light_mode} , RF = {self.trafficLights_duration_rf*100}%\n')) + + + def init_traci(self): + if 'SUMO_HOME' in os.environ: + tools = os.path.join(os.environ['SUMO_HOME'], 'tools') + sys.path.append(tools) + else: + sys.exit("please declare environment variable 'SUMO_HOME'") + + sumo_binary = os.path.join(os.environ['SUMO_HOME'], 'bin', self._SUMOCMD) + self.sumo_cmd = [sumo_binary, "-c", self._SUMOCFG] + + def init_logger(self, save_logs = False): + now = datetime.datetime.now() + current_date = now.strftime("%Y_%m_%d_%H_%M_%S") + + if not os.path.exists('logs'): + os.makedirs('logs') + + log_filename = f'logs/sumo_logs_{current_date}.log' + + logger = logging.getLogger("sumo_logger") + logger.setLevel(logging.INFO) + formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") + + if save_logs : + file_handler = logging.FileHandler(log_filename) + file_handler.setFormatter(formatter) + logger.addHandler(file_handler) + + handler = logging.StreamHandler() + handler.setFormatter(formatter) + logger.addHandler(handler) + + return logger + + def get_basics_emissions(self): + if self.n_steps == 100: + return self.total_emissions100 + if self.n_steps == 200: + return self.total_emissions200 + if self.n_steps == 300: + return self.total_emissions300 -#If without_actions_mode is choosen -if without_actions_mode: - limit_speed_mode = False - adjust_traffic_light_mode = False - weight_routing_mode = False - lock_area_mode = False - -############################################################################### -########################## SIMULATION REFERENCES ############################## -############################################################################### - -# Total of emissions of all pollutants in mg for n steps of simulation without locking areas -total_emissions200 = 43970763.15084749 -total_emissions300 = 87382632.08217141 -total_emissions400 = 140757491.8489904 -total_emissions500 = 202817535.43856794 - -############################################################################### -########################## CONFIGURATION METHODS ############################## -############################################################################### - -def get_basics_emissions(): - if n_steps == 200: - return total_emissions200 - if n_steps == 300: - return total_emissions300 - if n_steps == 400: - return total_emissions400 - if n_steps == 500: - return total_emissions500 - -def show_config(): - return (str(f'Grid : {CELLS_NUMBER}x{CELLS_NUMBER}\n') - + str(f'step number = {n_steps}\n') - + str(f'weight routing mode = {weight_routing_mode}\n') - + str(f'lock area mode = {lock_area_mode}\n') - + str(f'limit speed mode = {limit_speed_mode}, RF = {speed_rf*100}%\n') - + str(f'adjust traffic light mode = {adjust_traffic_light_mode} , RF = {trafficLights_duration_rf*100}%\n')) diff --git a/sumo_project/configs/default_config.json b/sumo_project/configs/default_config.json new file mode 100644 index 0000000..4bf6bf2 --- /dev/null +++ b/sumo_project/configs/default_config.json @@ -0,0 +1,22 @@ +{ + "_SUMOCMD": "sumo", + "_SUMOCFG": "simulations/mulhouse_simulation/osm.sumocfg", + + "areas_number": 10, + "emissions_threshold": 500000, + "n_steps": 200, + "window_size":100, + + "without_actions_mode": true, + + "limit_speed_mode": false, + "speed_rf": 0.1, + + "adjust_traffic_light_mode": false, + "trafficLights_duration_rf": 0.2, + + "weight_routing_mode": false, + + "lock_area_mode": false + +} \ No newline at end of file diff --git a/sumo_project/emissions.py b/sumo_project/emissions.py index 21d4cb3..dcb5792 100644 --- a/sumo_project/emissions.py +++ b/sumo_project/emissions.py @@ -1,25 +1,24 @@ +import argparse +import sys +import time +from traci import trafficlight +import traci from typing import List -import traci -import time - -from shapely.geometry import LineString from parse import search +from shapely.geometry import LineString import actions -import config -import sys +from config import Config from model import Area, Vehicle, Lane , TrafficLight , Phase , Logic -from traci import trafficlight -logger = config.logger -def init_grid(simulation_bounds, cells_number): +def init_grid(simulation_bounds, areas_number): grid = list() - width = simulation_bounds[1][0] / cells_number - height = simulation_bounds[1][1] / cells_number - for i in range(cells_number): - for j in range(cells_number): + width = simulation_bounds[1][0] / areas_number + height = simulation_bounds[1][1] / areas_number + for i in range(areas_number): + for j in range(areas_number): # bounds coordinates for the area : (xmin, ymin, xmax, ymax) ar_bounds = ((i * width, j * height), (i * width, (j + 1) * height), ((i + 1) * width, (j + 1) * height), ((i + 1) * width, j * height)) @@ -29,6 +28,7 @@ def init_grid(simulation_bounds, cells_number): traci.polygon.add(area.name, ar_bounds, (0, 255, 0)) return grid + def get_all_lanes() -> List[Lane]: lanes = [] for lane_id in traci.lane.getIDList(): @@ -37,6 +37,7 @@ def get_all_lanes() -> List[Lane]: lanes.append(Lane(lane_id, polygon_lane, initial_max_speed)) return lanes + def parse_phase(phase_repr): duration = search('duration: {:f}', phase_repr) minDuration = search('minDuration: {:f}', phase_repr) @@ -48,6 +49,7 @@ def parse_phase(phase_repr): return Phase(duration[0], minDuration[0], maxDuration[0], phaseDef) + def add_data_to_areas(areas: List[Area]): lanes = get_all_lanes() for area in areas: @@ -57,12 +59,13 @@ def add_data_to_areas(areas: List[Area]): for tl_id in traci.trafficlight.getIDList(): # add traffic lights if lane.lane_id in traci.trafficlight.getControlledLanes(tl_id): logics = [] - for l in traci.trafficlight.getCompleteRedYellowGreenDefinition(tl_id): #add logics + for l in traci.trafficlight.getCompleteRedYellowGreenDefinition(tl_id): # add logics phases = [] - for phase in traci.trafficlight.Logic.getPhases(l): #add phases to logics + for phase in traci.trafficlight.Logic.getPhases(l): # add phases to logics phases.append(parse_phase(phase.__repr__())) - logics.append(Logic(l,phases)) - area.add_tl(TrafficLight(tl_id,logics)) + logics.append(Logic(l, phases)) + area.add_tl(TrafficLight(tl_id, logics)) + def compute_vehicle_emissions(veh_id): return (traci.vehicle.getCOEmission(veh_id) @@ -81,74 +84,107 @@ def get_all_vehicles() -> List[Vehicle]: vehicles.append(vehicle) return vehicles -def get_emissions(grid: List[Area], vehicles: List[Vehicle]): + +def get_emissions(grid: List[Area], vehicles: List[Vehicle], current_step, config, logger): for area in grid: + vehicle_emissions = 0 for vehicle in vehicles: if vehicle.pos in area: - area.emissions += vehicle.emissions - if area.emissions > config.EMISSIONS_THRESHOLD: - + vehicle_emissions += vehicle.emissions + + area.emissions_by_step.append(vehicle_emissions) + + if area.sum_emissions_into_window(current_step, config.window_size) >= config.emissions_threshold: + if config.limit_speed_mode and not area.limited_speed: logger.info(f'Action - Decreased max speed into {area.name} by {config.speed_rf*100}%') actions.limit_speed_into_area(area, vehicles, config.speed_rf) - traci.polygon.setColor(area.name, (255, 0, 0)) - traci.polygon.setFilled(area.name, True) if config.adjust_traffic_light_mode and not area.tls_adjusted: logger.info(f'Action - Decreased traffic lights duration by {config.trafficLights_duration_rf*100}%') actions.adjust_traffic_light_phase_duration(area, config.trafficLights_duration_rf) - + if config.lock_area_mode and not area.locked: if actions.count_vehicles_in_area(area): logger.info(f'Action - {area.name} blocked') actions.lock_area(area) + + traci.polygon.setColor(area.name, (255, 0, 0)) + traci.polygon.setFilled(area.name, True) + + else: + actions.reverse_actions(area) -def main(): + +def run(config, logger): grid = list() try: traci.start(config.sumo_cmd) - + logger.info(f'Loaded simulation file : {config._SUMOCFG}') logger.info('Loading data for the simulation') start = time.perf_counter() - grid = init_grid(traci.simulation.getNetBoundary(), config.CELLS_NUMBER) + grid = init_grid(traci.simulation.getNetBoundary(), config.areas_number) add_data_to_areas(grid) - loading_time = round(time.perf_counter() - start,2) + loading_time = round(time.perf_counter() - start, 2) logger.info(f'Data loaded ({loading_time}s)') logger.info('Start of the simulation') step = 0 - while step < config.n_steps : #traci.simulation.getMinExpectedNumber() > 0: + while step < config.n_steps : # traci.simulation.getMinExpectedNumber() > 0: traci.simulationStep() vehicles = get_all_vehicles() - get_emissions(grid, vehicles) + get_emissions(grid, vehicles, step, config, logger) if config.weight_routing_mode: - logger.info('Action - Lane weights adjusted') actions.adjust_edges_weights() step += 1 finally: traci.close(False) - simulation_time = round(time.perf_counter() - start,2) + simulation_time = round(time.perf_counter() - start, 2) logger.info(f'End of the simulation ({simulation_time}s)') total_emissions = 0 for area in grid: - total_emissions += area.emissions - + total_emissions += area.sum_all_emissions() + logger.info(f'Total emissions = {total_emissions} mg') - if not config.without_actions_mode: + if not config.without_actions_mode : ref = config.get_basics_emissions() - diff_with_actions = (ref - total_emissions)/ref - logger.info(f'Reduction percentage of emissions = {diff_with_actions*100} %') - - logger.info('With the configuration : \n' + str(config.show_config())) - logger.info('Logs END') + if not (ref is None): + diff_with_actions = (ref - total_emissions) / ref + logger.info(f'Reduction percentage of emissions = {diff_with_actions*100} %') + + +def main(args): + parser = argparse.ArgumentParser(description="") + parser.add_argument("-f", "--configfile", type=str, default='configs/default_config.json', required=False) + parser.add_argument("-save", "--save", action="store_true") + parser.add_argument("-ref", "--ref", action="store_true") + args = parser.parse_args(args) + + # > py ./emissions.py -f configs/config1.json -save + # will load the configuration file "config1.json" and save logs into the logs directory + + # > py ./emissions.py -f configs/config1.json -save -ref & py ./emissions.py -f configs/config1.json -save + # same as above but also launches a reference simulation by using -ref option + + config = Config() + config.import_config_file(args.configfile) + config.init_traci() + logger = config.init_logger(save_logs=args.save) + if args.ref: + config.without_actions_mode = True + config.check_config() + logger.info(f'Reference simulation') + logger.info(f'Loaded configuration file : {args.configfile}') + + run(config, logger) - + if __name__ == '__main__': - main() + main(sys.argv[1:]) diff --git a/sumo_project/imgs/simulation_example.PNG b/sumo_project/imgs/simulation_example.PNG new file mode 100644 index 0000000..71535bc Binary files /dev/null and b/sumo_project/imgs/simulation_example.PNG differ diff --git a/sumo_project/model.py b/sumo_project/model.py index 888cef7..e910840 100644 --- a/sumo_project/model.py +++ b/sumo_project/model.py @@ -1,9 +1,9 @@ +from traci._trafficlight import Logic as SUMO_Logic from typing import Tuple, Set from shapely.geometry import Point, LineString from shapely.geometry import Polygon from shapely.geometry.base import BaseGeometry -from traci._trafficlight import Logic as SUMO_Logic class Lane: @@ -51,7 +51,7 @@ class Area: self.tls_adjusted = False self.rectangle = Polygon(coords) self.name = name - self.emissions = 0.0 + self.emissions_by_step = [] self._lanes: Set[Lane] = set() self._tls: Set[TrafficLight] = set() @@ -76,6 +76,19 @@ class Area: def remove_lane(self, lane: Lane): self._lanes.remove(lane) + + def sum_all_emissions(self): + sum = 0 + for emission in self.emissions_by_step: + sum += emission + return sum + + def sum_emissions_into_window(self, current_step, window_size): + sum = 0 + q = current_step // window_size #Returns the integral part of the quotient + for i in range(q*window_size, current_step): + sum += self.emissions_by_step[i] + return sum @classmethod def from_bounds(cls, xmin, ymin, xmax, ymax): diff --git a/sumo_project/mulhouse_simulation/build.bat b/sumo_project/simulations/mulhouse_simulation/build.bat similarity index 100% rename from sumo_project/mulhouse_simulation/build.bat rename to sumo_project/simulations/mulhouse_simulation/build.bat diff --git a/sumo_project/mulhouse_simulation/osm.net.xml b/sumo_project/simulations/mulhouse_simulation/osm.net.xml similarity index 100% rename from sumo_project/mulhouse_simulation/osm.net.xml rename to sumo_project/simulations/mulhouse_simulation/osm.net.xml diff --git a/sumo_project/mulhouse_simulation/osm.netccfg b/sumo_project/simulations/mulhouse_simulation/osm.netccfg similarity index 100% rename from sumo_project/mulhouse_simulation/osm.netccfg rename to sumo_project/simulations/mulhouse_simulation/osm.netccfg diff --git a/sumo_project/mulhouse_simulation/osm.passenger.rou.alt.xml b/sumo_project/simulations/mulhouse_simulation/osm.passenger.rou.alt.xml similarity index 100% rename from sumo_project/mulhouse_simulation/osm.passenger.rou.alt.xml rename to sumo_project/simulations/mulhouse_simulation/osm.passenger.rou.alt.xml diff --git a/sumo_project/mulhouse_simulation/osm.passenger.rou.xml b/sumo_project/simulations/mulhouse_simulation/osm.passenger.rou.xml similarity index 100% rename from sumo_project/mulhouse_simulation/osm.passenger.rou.xml rename to sumo_project/simulations/mulhouse_simulation/osm.passenger.rou.xml diff --git a/sumo_project/mulhouse_simulation/osm.passenger.trips.xml b/sumo_project/simulations/mulhouse_simulation/osm.passenger.trips.xml similarity index 100% rename from sumo_project/mulhouse_simulation/osm.passenger.trips.xml rename to sumo_project/simulations/mulhouse_simulation/osm.passenger.trips.xml diff --git a/sumo_project/mulhouse_simulation/osm.poly.xml b/sumo_project/simulations/mulhouse_simulation/osm.poly.xml similarity index 100% rename from sumo_project/mulhouse_simulation/osm.poly.xml rename to sumo_project/simulations/mulhouse_simulation/osm.poly.xml diff --git a/sumo_project/mulhouse_simulation/osm.polycfg b/sumo_project/simulations/mulhouse_simulation/osm.polycfg similarity index 100% rename from sumo_project/mulhouse_simulation/osm.polycfg rename to sumo_project/simulations/mulhouse_simulation/osm.polycfg diff --git a/sumo_project/mulhouse_simulation/osm.sumocfg b/sumo_project/simulations/mulhouse_simulation/osm.sumocfg similarity index 100% rename from sumo_project/mulhouse_simulation/osm.sumocfg rename to sumo_project/simulations/mulhouse_simulation/osm.sumocfg diff --git a/sumo_project/mulhouse_simulation/osm.view.xml b/sumo_project/simulations/mulhouse_simulation/osm.view.xml similarity index 100% rename from sumo_project/mulhouse_simulation/osm.view.xml rename to sumo_project/simulations/mulhouse_simulation/osm.view.xml diff --git a/sumo_project/mulhouse_simulation/osm_bbox.osm.xml b/sumo_project/simulations/mulhouse_simulation/osm_bbox.osm.xml similarity index 100% rename from sumo_project/mulhouse_simulation/osm_bbox.osm.xml rename to sumo_project/simulations/mulhouse_simulation/osm_bbox.osm.xml diff --git a/sumo_project/mulhouse_simulation/run.bat b/sumo_project/simulations/mulhouse_simulation/run.bat similarity index 100% rename from sumo_project/mulhouse_simulation/run.bat rename to sumo_project/simulations/mulhouse_simulation/run.bat