From ee6ef7a010b28907903058b2ecefd24e2e0c5e50 Mon Sep 17 00:00:00 2001 From: Ahp06 Date: Tue, 22 Jan 2019 11:56:44 +0100 Subject: [PATCH 1/5] Added RunProcess class and added multiprocessing run --- sumo_project/config.py | 31 --- sumo_project/data.py | 7 +- sumo_project/emissions.py | 65 +---- .../{default_config.json => config2.json} | 6 +- sumo_project/main.py | 126 +++++++++ sumo_project/runner.py | 257 +++++++----------- 6 files changed, 251 insertions(+), 241 deletions(-) rename sumo_project/files/configs/{default_config.json => config2.json} (73%) create mode 100644 sumo_project/main.py diff --git a/sumo_project/config.py b/sumo_project/config.py index 87ff052..0b4f97f 100644 --- a/sumo_project/config.py +++ b/sumo_project/config.py @@ -69,7 +69,6 @@ class Config: :return: All properties chosen by the user """ return ( - f'grid : {self.areas_number}x{self.areas_number}\n' f'step number = {self.n_steps}\n' f'window size = {self.window_size}\n' f'weight routing mode = {self.weight_routing_mode}\n' @@ -89,36 +88,6 @@ class Config: sumo_binary = os.path.join(os.environ['SUMO_HOME'], 'bin', self._SUMOCMD) self.sumo_cmd = [sumo_binary, "-c", self._SUMOCFG] - def init_logger(self, dump_name, save_logs=False): - """ - Init the application logger - :param dump_name: The name of the data dump to use - :param save_logs: If save_logs is True, it will save the logs into the logs directory - :return: - """ - now = datetime.datetime.now() - current_date = now.strftime("%Y_%m_%d_%H_%M_%S") - - if not os.path.exists('files/logs'): - os.makedirs('logs') - - log_filename = f'files/logs/{dump_name}_{current_date}.log' - - logger = logging.getLogger(f'sumo_logger_{dump_name}') - 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_ref_emissions(self): """ :return: Return the sum of all emissions (in mg) from the simulation of reference diff --git a/sumo_project/data.py b/sumo_project/data.py index eda25c0..006c869 100644 --- a/sumo_project/data.py +++ b/sumo_project/data.py @@ -30,10 +30,11 @@ from model import Area, Vehicle, Lane, TrafficLight, Phase, Logic, Emission class Data: - def __init__(self, map_bounds, areas_number,simulation_dir): + def __init__(self, dump_name, map_bounds, areas_number,simulation_dir): """ Data constructor """ + self.dump_name = dump_name self.map_bounds = map_bounds self.areas_number = areas_number self.dir = simulation_dir @@ -109,7 +110,7 @@ class Data: logics.append(Logic(l, phases)) area.add_tl(TrafficLight(tl_id, logics)) - def save(self, dump_name): + def save(self): """ Save simulation data into a json file :param dump_name: The name of your data dump @@ -120,6 +121,6 @@ class Data: os.mkdir(dump_dir) s = json.dumps(json.loads(jsonpickle.encode(self)), indent=4) # for pretty JSON - with open(f'{dump_dir}/{dump_name}.json', 'w') as f: + with open(f'{dump_dir}/{self.dump_name}.json', 'w') as f: f.write(s) diff --git a/sumo_project/emissions.py b/sumo_project/emissions.py index c341f0b..7af85ab 100644 --- a/sumo_project/emissions.py +++ b/sumo_project/emissions.py @@ -4,28 +4,16 @@ Created on 17 oct. 2018 @author: Axel Huynh-Phuc, Thibaud Gasser """ -""" -This module defines the entry point of the application -""" - -import argparse -import csv -import datetime -import itertools -import os -import sys -import time import traci from typing import List -import jsonpickle -from parse import search from shapely.geometry import LineString import actions from config import Config from data import Data from model import Area, Vehicle, Lane, TrafficLight, Phase, Logic, Emission +from runner import RunProcess def compute_vehicle_emissions(veh_id): @@ -57,7 +45,7 @@ def get_all_vehicles() -> List[Vehicle]: return vehicles -def get_emissions(grid: List[Area], vehicles: List[Vehicle], current_step, config, logger): +def get_emissions(p : RunProcess, vehicles: List[Vehicle], current_step): """ For each area retrieves the acquired emissions in the window, and acts according to the configuration chosen by the user @@ -68,7 +56,7 @@ def get_emissions(grid: List[Area], vehicles: List[Vehicle], current_step, confi :param logger: The simulation logger :return: """ - for area in grid: + for area in p.data.grid: total_emissions = Emission() for vehicle in vehicles: if vehicle.pos in area: @@ -76,24 +64,24 @@ def get_emissions(grid: List[Area], vehicles: List[Vehicle], current_step, confi # Adding of the total of emissions pollutant at the current step into memory area.emissions_by_step.append(total_emissions) - + # If the sum of pollutant emissions (in mg) exceeds the threshold - if area.sum_emissions_into_window(current_step) >= config.emissions_threshold: + if area.sum_emissions_into_window(current_step) >= p.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, config.speed_rf) - 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 p.config.limit_speed_mode and not area.limited_speed: + p.logger.info(f'Action - Decreased max speed into {area.name} by {p.config.speed_rf * 100}%') + actions.limit_speed_into_area(area, p.config.speed_rf) + if p.config.adjust_traffic_light_mode and not area.tls_adjusted: + p.logger.info( + f'Action - Decreased traffic lights duration by {p.config.trafficLights_duration_rf * 100}%') + actions.adjust_traffic_light_phase_duration(area, p.config.trafficLights_duration_rf) - if config.lock_area_mode and not area.locked: + if p.config.lock_area_mode and not area.locked: if actions.count_vehicles_in_area(area): - logger.info(f'Action - {area.name} blocked') + p.logger.info(f'Action - {area.name} blocked') actions.lock_area(area) - if config.weight_routing_mode and not area.weight_adjusted: + if p.config.weight_routing_mode and not area.weight_adjusted: actions.adjust_edges_weights(area) traci.polygon.setFilled(area.name, True) @@ -112,26 +100,3 @@ def get_reduction_percentage(ref, total): """ return (ref - total) / ref * 100 - -def export_data_to_csv(config, conf, grid, dump_name): - """ - Export all Emission objects as a CSV file into the csv directory - :param config: The simulation configuration - :param grid: The list of areas - :return: - """ - csv_dir = 'files/csv' - if not os.path.exists(csv_dir): - os.mkdir(csv_dir) - - now = datetime.datetime.now().strftime("%Y_%m_%d_%H_%M_%S") - - with open(f'files/csv/{dump_name}_{conf}_{now}.csv', 'w') as f: - writer = csv.writer(f) - # Write CSV headers - writer.writerow(itertools.chain(('Step',), (a.name for a in grid))) - # Write all areas emission value for each step - for step in range(config.n_steps): - em_for_step = (f'{a.emissions_by_step[step].value():.3f}' for a in grid) - writer.writerow(itertools.chain((step,), em_for_step)) - diff --git a/sumo_project/files/configs/default_config.json b/sumo_project/files/configs/config2.json similarity index 73% rename from sumo_project/files/configs/default_config.json rename to sumo_project/files/configs/config2.json index ded4d80..3c4fad1 100644 --- a/sumo_project/files/configs/default_config.json +++ b/sumo_project/files/configs/config2.json @@ -5,13 +5,13 @@ "n_steps": 200, "window_size":100, - "without_actions_mode": true, + "without_actions_mode": false, "limit_speed_mode": true, - "speed_rf": 0.1, + "speed_rf": 1.1, "adjust_traffic_light_mode": true, - "trafficLights_duration_rf": 0.2, + "trafficLights_duration_rf": 0.3, "weight_routing_mode": false, diff --git a/sumo_project/main.py b/sumo_project/main.py new file mode 100644 index 0000000..5a1dc31 --- /dev/null +++ b/sumo_project/main.py @@ -0,0 +1,126 @@ +''' +Created on 19 janv. 2019 + +@author: Axel Huynh-Phuc +''' + +import sys +import os +import argparse +import traci +import time +import jsonpickle + +from data import Data +from config import Config +from runner import RunProcess + + +""" +Init the Traci API +""" +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'") + + +def add_options(parser): + """ + Add command line options + :param parser: The command line parser + :return: + """ + + # TODO: Faire que -areas & -simulation_dir soit requis si -new_dump + # Faire que -c soit requis si -run + + parser.add_argument("-new_dump", "--new_dump", type=str, + required=False, help='Load and create a new data dump with the configuration file chosen') + parser.add_argument("-areas", "--areas", type=int, required=False, + help='Will create a grid with "areas x areas" areas') + parser.add_argument("-simulation_dir", "--simulation_dir", type=str, required=False, + help='Choose the simulation directory') + + parser.add_argument("-run", "--run", type=str, + help='Run a simulation with the dump chosen') + parser.add_argument("-c", "--c", nargs='+', type=str, + help='Choose your configuration file from your working directory') + parser.add_argument("-save", "--save", action="store_true", + help='Save the logs into the logs folder') + parser.add_argument("-csv", "--csv", action="store_true", + help="Export all data emissions into a CSV file") + + +def create_dump(dump_name, simulation_dir, areas_number): + """ + Create a new dump with config file and dump_name chosen + :param dump_name: The name of the data dump + :param simulation_dir: The simulation directory + :param areas_number: The number of areas in grid + :return: + """ + + sumo_binary = os.path.join(os.environ['SUMO_HOME'], 'bin', 'sumo') + sumo_cmd = [sumo_binary, "-c", f'files/simulations/{simulation_dir}/osm.sumocfg'] + + traci.start(sumo_cmd) + if not os.path.isfile(f'files/dump/{dump_name}.json'): + start = time.perf_counter() + data = Data(dump_name, traci.simulation.getNetBoundary(), areas_number, simulation_dir) + data.init_grid() + data.add_data_to_areas() + data.save() + + loading_time = round(time.perf_counter() - start, 2) + print(f'Data loaded ({loading_time}s)') + print(f'Dump {dump_name} created') + else: + print(f'Dump with name {dump_name} already exist') + + traci.close(False) + +def main(args): + """ + The entry point of the application + :param args: Command line options + :return: + """ + parser = argparse.ArgumentParser() + add_options(parser) + args = parser.parse_args(args) + + if args.new_dump is not None: + if (args.simulation_dir is not None) and (args.areas is not None): + create_dump(args.new_dump, args.simulation_dir, args.areas) + + if args.run is not None: + dump_path = f'files/dump/{args.run}.json' + if os.path.isfile(dump_path): + with open(dump_path, 'r') as f: + data = jsonpickle.decode(f.read()) + + process = [] + + if args.c is not None: + + # Init all process + for conf in args.c: + + config = Config() + config.import_config_file(conf) + config.init_traci(data.dir) + config.check_config() + + p = RunProcess(data, config,args.save,args.csv) + p.init_logger() + process.append(p) + + p.logger.info(f'Running simulation dump "{args.run}" with the config "{conf}" ...') + p.start() + p.join() + + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/sumo_project/runner.py b/sumo_project/runner.py index eea0723..c8af223 100644 --- a/sumo_project/runner.py +++ b/sumo_project/runner.py @@ -1,181 +1,130 @@ -''' -Created on 19 janv. 2019 - -@author: Axel Huynh-Phuc -''' - import argparse import os import sys import time import traci +import logging +import itertools +import csv import jsonpickle +import multiprocessing +import datetime from config import Config from data import Data import emissions from model import Emission -""" -Init the Traci API -""" -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'") +class RunProcess(multiprocessing.Process): + + def __init__(self, data : Data, config : Config, save_logs, csv_export): + multiprocessing.Process.__init__(self) + self.data = data + self.config = config + self.save_logs = save_logs + self.csv_export = csv_export -def add_options(parser): - """ - Add command line options - :param parser: The command line parser - :return: - """ - - # TODO: Faire que -areas & -simulation_dir soit requis si -new_dump - # Faire que -c soit requis si -run - - parser.add_argument("-new_dump", "--new_dump", type=str, - required=False, help='Load and create a new data dump with the configuration file chosen') - parser.add_argument("-areas", "--areas", type=int, required=False, - help='Will create a grid with "areas x areas" areas') - parser.add_argument("-simulation_dir", "--simulation_dir", type=str, required=False, - help='Choose the simulation directory') - - parser.add_argument("-run", "--run", type=str, - help='Run a simulation with the dump chosen') - parser.add_argument("-c", "--c", type=str, - help='Choose your configuration file from your working directory') - parser.add_argument("-save", "--save", action="store_true", - help='Save the logs into the logs folder') - parser.add_argument("-csv", "--csv", action="store_true", - help="Export all data emissions into a CSV file") + def init_logger(self): + now = datetime.datetime.now() + current_date = now.strftime("%Y_%m_%d_%H_%M_%S") - -def create_dump(dump_name, simulation_dir, areas_number): - """ - Create a new dump with config file and dump_name chosen - :param dump_name: The name of the data dump - :param simulation_dir: The simulation directory - :param areas_number: The number of areas in grid - :return: - """ - - sumo_binary = os.path.join(os.environ['SUMO_HOME'], 'bin', 'sumo') - sumo_cmd = [sumo_binary, "-c", f'files/simulations/{simulation_dir}/osm.sumocfg'] - - traci.start(sumo_cmd) - if not os.path.isfile(f'files/dump/{dump_name}.json'): - start = time.perf_counter() - data = Data(traci.simulation.getNetBoundary(), areas_number, simulation_dir) - data.init_grid() - data.add_data_to_areas() - data.save(dump_name) + if not os.path.exists('files/logs'): + os.makedirs('logs') + + # log_filename = f'files/logs/{logger_name}_{current_date}.log' + log_filename = f'files/logs/{current_date}.log' + + conf_name = self.config.config_filename.replace('.json', '') + self.logger = logging.getLogger(f'{self.data.dir}_{conf_name}') + self.logger.setLevel(logging.INFO) + formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") + + if self.save_logs: + file_handler = logging.FileHandler(log_filename) + file_handler.setFormatter(formatter) + self.logger.addHandler(file_handler) + + handler = logging.StreamHandler() + handler.setFormatter(formatter) + self.logger.addHandler(handler) - loading_time = round(time.perf_counter() - start, 2) - print(f'Data loaded ({loading_time}s)') - print(f'Dump {dump_name} created') - else: - print(f'Dump with name {dump_name} already exist') + def export_data_to_csv(self): + """ + Export all Emission objects as a CSV file into the csv directory + """ + csv_dir = 'files/csv' + if not os.path.exists(csv_dir): + os.mkdir(csv_dir) + + now = datetime.datetime.now().strftime("%Y_%m_%d_%H_%M_%S") + conf_name = self.config.config_filename.replace('.json', '') + + with open(f'files/csv/{self.data.dump_name}_{conf_name}_{now}.csv', 'w') as f: + writer = csv.writer(f) + # Write CSV headers + writer.writerow(itertools.chain(('Step',), (a.name for a in self.data.grid))) + # Write all areas emission value for each step + for step in range(self.config.n_steps): + em_for_step = (f'{a.emissions_by_step[step].value():.3f}' for a in self.data.grid) + writer.writerow(itertools.chain((step,), em_for_step)) - traci.close(False) - - -def run(data : Data, config : Config, logger): - """ - Run a data set - :param data: The data instance - :param config: The config instance - :param logger: The logger instance - """ - try: - traci.start(config.sumo_cmd) - - for area in data.grid: # Set acquisition window size - area.set_window_size(config.window_size) - traci.polygon.add(area.name, area.rectangle.exterior.coords, (255, 0, 0)) # Add polygon for UI - - logger.info(f'Loaded simulation file : {config._SUMOCFG}') - logger.info('Loading data for the simulation') - start = time.perf_counter() - - logger.info('Simulation started...') - step = 0 - while step < config.n_steps: - traci.simulationStep() - - vehicles = emissions.get_all_vehicles() - emissions.get_emissions(data.grid, vehicles, step, config, logger) - step += 1 - - print(f'step = {step}/{config.n_steps}', end='\r') - - finally: - traci.close(False) - - -def main(args): - """ - The entry point of the application - :param args: Command line options - :return: - """ - parser = argparse.ArgumentParser() - add_options(parser) - args = parser.parse_args(args) - - if args.new_dump is not None: - if (args.simulation_dir is not None) and (args.areas is not None): - create_dump(args.new_dump, args.simulation_dir, args.areas) - - if args.run is not None: - dump_path = f'files/dump/{args.run}.json' - if os.path.isfile(dump_path): - with open(dump_path, 'r') as f: - data = jsonpickle.decode(f.read()) + def run(self): + """ + Run a data set + """ + try: + self.init_logger() - config = Config() - if args.c is not None: - config.import_config_file(args.c) - config.init_traci(data.dir) - logger = config.init_logger(dump_name=args.run, save_logs=args.save) - config.check_config() + traci.start(self.config.sumo_cmd) + + for area in self.data.grid: # Set acquisition window size + area.set_window_size(self.config.window_size) + traci.polygon.add(area.name, area.rectangle.exterior.coords, (255, 0, 0)) # Add polygon for UI + + self.logger.info(f'Loaded simulation file : {self.config._SUMOCFG}') + self.logger.info('Loading data for the simulation') - logger.info(f'Running simulation dump {args.run}...') start = time.perf_counter() - - run(data, config, logger) - - simulation_time = round(time.perf_counter() - start, 2) - logger.info(f'End of the simulation ({simulation_time}s)') - - if args.csv: - emissions.export_data_to_csv(config, data.grid, dump_name=args.run) - logger.info(f'Exported data into the csv folder') - - # 1 step is equal to one second simulated - logger.info(f'Real-time factor : {config.n_steps / simulation_time}') + self.logger.info('Simulation started...') + step = 0 + while step < self.config.n_steps: + traci.simulationStep() + + vehicles = emissions.get_all_vehicles() + emissions.get_emissions(self, vehicles, step) + step += 1 + + print(f'step = {step}/{self.config.n_steps}', end='\r') + + finally: + traci.close(False) total_emissions = Emission() - for area in data.grid: + for area in self.data.grid: total_emissions += area.sum_all_emissions() - - logger.info(f'Total emissions = {total_emissions.value()} mg') - - if not config.without_actions_mode: # If it's not a simulation without actions - ref = config.get_ref_emissions() + + self.logger.info(f'Total emissions = {total_emissions.value()} mg') + + if not self.config.without_actions_mode: # If it's not a simulation without actions + ref = self.config.get_ref_emissions() if not (ref is None): # If a reference value exist (add yours into config.py) global_diff = (ref.value() - total_emissions.value()) / ref.value() + self.logger.info(f'Global reduction percentage of emissions = {global_diff * 100} %') + self.logger.info(f'-> CO2 emissions = {emissions.get_reduction_percentage(ref.co2, total_emissions.co2)} %') + self.logger.info(f'-> CO emissions = {emissions.get_reduction_percentage(ref.co, total_emissions.co)} %') + self.logger.info(f'-> Nox emissions = {emissions.get_reduction_percentage(ref.nox, total_emissions.nox)} %') + self.logger.info(f'-> HC emissions = {emissions.get_reduction_percentage(ref.hc, total_emissions.hc)} %') + self.logger.info(f'-> PMx emissions = {emissions.get_reduction_percentage(ref.pmx, total_emissions.pmx)} %') + + simulation_time = round(time.perf_counter() - start, 2) + self.logger.info(f'End of the simulation ({simulation_time}s)') + # 1 step is equal to one second simulated + self.logger.info(f'Real-time factor : {self.config.n_steps / simulation_time}') - logger.info(f'Global reduction percentage of emissions = {global_diff * 100} %') - logger.info(f'-> CO2 emissions = {emissions.get_reduction_percentage(ref.co2, total_emissions.co2)} %') - logger.info(f'-> CO emissions = {emissions.get_reduction_percentage(ref.co, total_emissions.co)} %') - logger.info(f'-> Nox emissions = {emissions.get_reduction_percentage(ref.nox, total_emissions.nox)} %') - logger.info(f'-> HC emissions = {emissions.get_reduction_percentage(ref.hc, total_emissions.hc)} %') - logger.info(f'-> PMx emissions = {emissions.get_reduction_percentage(ref.pmx, total_emissions.pmx)} %') - - -if __name__ == '__main__': - main(sys.argv[1:]) + if self.csv_export: + self.export_data_to_csv() + self.logger.info(f'Exported data into the csv folder') + + \ No newline at end of file From b33e96ba60c95c57a7869ee3e7547e03a1e6c1fa Mon Sep 17 00:00:00 2001 From: Ahp06 Date: Tue, 22 Jan 2019 12:55:06 +0100 Subject: [PATCH 2/5] Fixed bugs about logging with multiprocessing --- sumo_project/emissions.py | 1 - sumo_project/main.py | 126 ------------------------------------- sumo_project/runner.py | 127 ++++++++++++++++++++++++++++++++++++-- 3 files changed, 121 insertions(+), 133 deletions(-) delete mode 100644 sumo_project/main.py diff --git a/sumo_project/emissions.py b/sumo_project/emissions.py index 7af85ab..cb2d7b6 100644 --- a/sumo_project/emissions.py +++ b/sumo_project/emissions.py @@ -15,7 +15,6 @@ from data import Data from model import Area, Vehicle, Lane, TrafficLight, Phase, Logic, Emission from runner import RunProcess - def compute_vehicle_emissions(veh_id): """ Recover the emissions of different pollutants from a vehicle and create an Emission instance diff --git a/sumo_project/main.py b/sumo_project/main.py deleted file mode 100644 index 5a1dc31..0000000 --- a/sumo_project/main.py +++ /dev/null @@ -1,126 +0,0 @@ -''' -Created on 19 janv. 2019 - -@author: Axel Huynh-Phuc -''' - -import sys -import os -import argparse -import traci -import time -import jsonpickle - -from data import Data -from config import Config -from runner import RunProcess - - -""" -Init the Traci API -""" -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'") - - -def add_options(parser): - """ - Add command line options - :param parser: The command line parser - :return: - """ - - # TODO: Faire que -areas & -simulation_dir soit requis si -new_dump - # Faire que -c soit requis si -run - - parser.add_argument("-new_dump", "--new_dump", type=str, - required=False, help='Load and create a new data dump with the configuration file chosen') - parser.add_argument("-areas", "--areas", type=int, required=False, - help='Will create a grid with "areas x areas" areas') - parser.add_argument("-simulation_dir", "--simulation_dir", type=str, required=False, - help='Choose the simulation directory') - - parser.add_argument("-run", "--run", type=str, - help='Run a simulation with the dump chosen') - parser.add_argument("-c", "--c", nargs='+', type=str, - help='Choose your configuration file from your working directory') - parser.add_argument("-save", "--save", action="store_true", - help='Save the logs into the logs folder') - parser.add_argument("-csv", "--csv", action="store_true", - help="Export all data emissions into a CSV file") - - -def create_dump(dump_name, simulation_dir, areas_number): - """ - Create a new dump with config file and dump_name chosen - :param dump_name: The name of the data dump - :param simulation_dir: The simulation directory - :param areas_number: The number of areas in grid - :return: - """ - - sumo_binary = os.path.join(os.environ['SUMO_HOME'], 'bin', 'sumo') - sumo_cmd = [sumo_binary, "-c", f'files/simulations/{simulation_dir}/osm.sumocfg'] - - traci.start(sumo_cmd) - if not os.path.isfile(f'files/dump/{dump_name}.json'): - start = time.perf_counter() - data = Data(dump_name, traci.simulation.getNetBoundary(), areas_number, simulation_dir) - data.init_grid() - data.add_data_to_areas() - data.save() - - loading_time = round(time.perf_counter() - start, 2) - print(f'Data loaded ({loading_time}s)') - print(f'Dump {dump_name} created') - else: - print(f'Dump with name {dump_name} already exist') - - traci.close(False) - -def main(args): - """ - The entry point of the application - :param args: Command line options - :return: - """ - parser = argparse.ArgumentParser() - add_options(parser) - args = parser.parse_args(args) - - if args.new_dump is not None: - if (args.simulation_dir is not None) and (args.areas is not None): - create_dump(args.new_dump, args.simulation_dir, args.areas) - - if args.run is not None: - dump_path = f'files/dump/{args.run}.json' - if os.path.isfile(dump_path): - with open(dump_path, 'r') as f: - data = jsonpickle.decode(f.read()) - - process = [] - - if args.c is not None: - - # Init all process - for conf in args.c: - - config = Config() - config.import_config_file(conf) - config.init_traci(data.dir) - config.check_config() - - p = RunProcess(data, config,args.save,args.csv) - p.init_logger() - process.append(p) - - p.logger.info(f'Running simulation dump "{args.run}" with the config "{conf}" ...') - p.start() - p.join() - - -if __name__ == '__main__': - main(sys.argv[1:]) diff --git a/sumo_project/runner.py b/sumo_project/runner.py index c8af223..a99cef8 100644 --- a/sumo_project/runner.py +++ b/sumo_project/runner.py @@ -1,15 +1,25 @@ +''' +Created on 19 janv. 2019 + +@author: Axel Huynh-Phuc +''' + +""" +Init the Traci API +""" + import argparse +import csv +import datetime +import itertools +import logging +import multiprocessing import os import sys import time import traci -import logging -import itertools -import csv import jsonpickle -import multiprocessing -import datetime from config import Config from data import Data @@ -17,6 +27,12 @@ import emissions from model import Emission +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'") + class RunProcess(multiprocessing.Process): def __init__(self, data : Data, config : Config, save_logs, csv_export): @@ -76,6 +92,7 @@ class RunProcess(multiprocessing.Process): """ try: self.init_logger() + self.logger.info(f'Running simulation dump "{self.data.dump_name}" with the config "{self.config.config_filename}" ...') traci.start(self.config.sumo_cmd) @@ -126,5 +143,103 @@ class RunProcess(multiprocessing.Process): if self.csv_export: self.export_data_to_csv() self.logger.info(f'Exported data into the csv folder') + + +def add_options(parser): + """ + Add command line options + :param parser: The command line parser + :return: + """ + + # TODO: Faire que -areas & -simulation_dir soit requis si -new_dump + # Faire que -c soit requis si -run + + parser.add_argument("-new_dump", "--new_dump", type=str, + required=False, help='Load and create a new data dump with the configuration file chosen') + parser.add_argument("-areas", "--areas", type=int, required=False, + help='Will create a grid with "areas x areas" areas') + parser.add_argument("-simulation_dir", "--simulation_dir", type=str, required=False, + help='Choose the simulation directory') + + parser.add_argument("-run", "--run", type=str, + help='Run a simulation with the dump chosen') + parser.add_argument("-c", "--c", nargs='+', type=str, + help='Choose your configuration file from your working directory') + parser.add_argument("-save", "--save", action="store_true", + help='Save the logs into the logs folder') + parser.add_argument("-csv", "--csv", action="store_true", + help="Export all data emissions into a CSV file") + + +def create_dump(dump_name, simulation_dir, areas_number): + """ + Create a new dump with config file and dump_name chosen + :param dump_name: The name of the data dump + :param simulation_dir: The simulation directory + :param areas_number: The number of areas in grid + :return: + """ + + sumo_binary = os.path.join(os.environ['SUMO_HOME'], 'bin', 'sumo') + sumo_cmd = [sumo_binary, "-c", f'files/simulations/{simulation_dir}/osm.sumocfg'] + + traci.start(sumo_cmd) + if not os.path.isfile(f'files/dump/{dump_name}.json'): + start = time.perf_counter() + data = Data(dump_name, traci.simulation.getNetBoundary(), areas_number, simulation_dir) + data.init_grid() + data.add_data_to_areas() + data.save() + + loading_time = round(time.perf_counter() - start, 2) + print(f'Data loaded ({loading_time}s)') + print(f'Dump {dump_name} created') + else: + print(f'Dump with name {dump_name} already exist') + + traci.close(False) + +def main(args): + """ + The entry point of the application + :param args: Command line options + :return: + """ + parser = argparse.ArgumentParser() + add_options(parser) + args = parser.parse_args(args) + + if args.new_dump is not None: + if (args.simulation_dir is not None) and (args.areas is not None): + create_dump(args.new_dump, args.simulation_dir, args.areas) + + if args.run is not None: + dump_path = f'files/dump/{args.run}.json' + if os.path.isfile(dump_path): + with open(dump_path, 'r') as f: + data = jsonpickle.decode(f.read()) + + process = [] + + if args.c is not None: - \ No newline at end of file + # Init all process + for conf in args.c: + + config = Config() + config.import_config_file(conf) + config.init_traci(data.dir) + config.check_config() + + p = RunProcess(data, config,args.save,args.csv) + p.init_logger() + process.append(p) + + p.start() + + for p in process : p.join() + + +if __name__ == '__main__': + main(sys.argv[1:]) From f00e933fe69dc95697e412f0236e0007ce1ad7e1 Mon Sep 17 00:00:00 2001 From: Ahp06 Date: Tue, 22 Jan 2019 13:19:46 +0100 Subject: [PATCH 3/5] Reformat code --- sumo_project/config.py | 6 +++- sumo_project/runner.py | 75 +++++++++++++++++++----------------------- 2 files changed, 39 insertions(+), 42 deletions(-) diff --git a/sumo_project/config.py b/sumo_project/config.py index 0b4f97f..17f6663 100644 --- a/sumo_project/config.py +++ b/sumo_project/config.py @@ -15,6 +15,7 @@ import os import sys from model import Emission +from data import Data class Config: @@ -27,10 +28,13 @@ class Config: ref200 = Emission(co2=42816869.05436445, co=1128465.0343051048, nox=18389.648337283958, hc=6154.330914019103, pmx=885.0829265236318) - def __init__(self): + def __init__(self,config_file, data : Data): """ Default constructor """ + self.import_config_file(config_file) + self.init_traci(data.dir) + self.check_config() def import_config_file(self, config_file): """ diff --git a/sumo_project/runner.py b/sumo_project/runner.py index a99cef8..048945f 100644 --- a/sumo_project/runner.py +++ b/sumo_project/runner.py @@ -49,10 +49,9 @@ class RunProcess(multiprocessing.Process): if not os.path.exists('files/logs'): os.makedirs('logs') - # log_filename = f'files/logs/{logger_name}_{current_date}.log' - log_filename = f'files/logs/{current_date}.log' - conf_name = self.config.config_filename.replace('.json', '') + log_filename = f'files/logs/{self.data.dump_name}_{conf_name}_{current_date}.log' + self.logger = logging.getLogger(f'{self.data.dir}_{conf_name}') self.logger.setLevel(logging.INFO) formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") @@ -144,34 +143,6 @@ class RunProcess(multiprocessing.Process): self.export_data_to_csv() self.logger.info(f'Exported data into the csv folder') - -def add_options(parser): - """ - Add command line options - :param parser: The command line parser - :return: - """ - - # TODO: Faire que -areas & -simulation_dir soit requis si -new_dump - # Faire que -c soit requis si -run - - parser.add_argument("-new_dump", "--new_dump", type=str, - required=False, help='Load and create a new data dump with the configuration file chosen') - parser.add_argument("-areas", "--areas", type=int, required=False, - help='Will create a grid with "areas x areas" areas') - parser.add_argument("-simulation_dir", "--simulation_dir", type=str, required=False, - help='Choose the simulation directory') - - parser.add_argument("-run", "--run", type=str, - help='Run a simulation with the dump chosen') - parser.add_argument("-c", "--c", nargs='+', type=str, - help='Choose your configuration file from your working directory') - parser.add_argument("-save", "--save", action="store_true", - help='Save the logs into the logs folder') - parser.add_argument("-csv", "--csv", action="store_true", - help="Export all data emissions into a CSV file") - - def create_dump(dump_name, simulation_dir, areas_number): """ Create a new dump with config file and dump_name chosen @@ -198,8 +169,34 @@ def create_dump(dump_name, simulation_dir, areas_number): else: print(f'Dump with name {dump_name} already exist') - traci.close(False) + traci.close(False) + +def add_options(parser): + """ + Add command line options + :param parser: The command line parser + :return: + """ + # TODO: Faire que -areas & -simulation_dir soit requis si -new_dump + # Faire que -c soit requis si -run + + parser.add_argument("-new_dump", "--new_dump", type=str, + required=False, help='Load and create a new data dump with the configuration file chosen') + parser.add_argument("-areas", "--areas", type=int, required=False, + help='Will create a grid with "areas x areas" areas') + parser.add_argument("-simulation_dir", "--simulation_dir", type=str, required=False, + help='Choose the simulation directory') + + parser.add_argument("-run", "--run", type=str, + help='Run a simulation with the dump chosen') + parser.add_argument("-c", "--c", nargs='+', type=str, + help='Choose your configuration file from your working directory') + parser.add_argument("-save", "--save", action="store_true", + help='Save the logs into the logs folder') + parser.add_argument("-csv", "--csv", action="store_true", + help="Export all data emissions into a CSV file") + def main(args): """ The entry point of the application @@ -224,16 +221,12 @@ def main(args): if args.c is not None: - # Init all process - for conf in args.c: - - config = Config() - config.import_config_file(conf) - config.init_traci(data.dir) - config.check_config() - + # Init + for conf in args.c: + config = Config(conf,data) + p = RunProcess(data, config,args.save,args.csv) - p.init_logger() + #p.init_logger() process.append(p) p.start() From 6571b09d83d2b0ed18b5815a15556fb0cbf3f1b9 Mon Sep 17 00:00:00 2001 From: Ahp06 Date: Tue, 22 Jan 2019 15:01:40 +0100 Subject: [PATCH 4/5] Changed some doc method --- sumo_project/runner.py | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/sumo_project/runner.py b/sumo_project/runner.py index 048945f..d070d18 100644 --- a/sumo_project/runner.py +++ b/sumo_project/runner.py @@ -5,7 +5,7 @@ Created on 19 janv. 2019 ''' """ -Init the Traci API +This module defines the entry point of the application """ import argparse @@ -26,7 +26,9 @@ from data import Data import emissions from model import Emission - +""" +Init the Traci API +""" if 'SUMO_HOME' in os.environ: tools = os.path.join(os.environ['SUMO_HOME'], 'tools') sys.path.append(tools) @@ -36,6 +38,13 @@ else: class RunProcess(multiprocessing.Process): def __init__(self, data : Data, config : Config, save_logs, csv_export): + """ + RunProcess constructor + :param data: The data instance + :param config: The config instance + :param save_logs: If save_logs == True, it will save the logs into the logs directory + :param csv_export: If csv_export == True, it will export all emissions data into a csv file + """ multiprocessing.Process.__init__(self) self.data = data self.config = config @@ -43,6 +52,9 @@ class RunProcess(multiprocessing.Process): self.csv_export = csv_export def init_logger(self): + """ + Init logger properties + """ now = datetime.datetime.now() current_date = now.strftime("%Y_%m_%d_%H_%M_%S") @@ -142,7 +154,7 @@ class RunProcess(multiprocessing.Process): if self.csv_export: self.export_data_to_csv() self.logger.info(f'Exported data into the csv folder') - + def create_dump(dump_name, simulation_dir, areas_number): """ Create a new dump with config file and dump_name chosen @@ -220,17 +232,13 @@ def main(args): process = [] if args.c is not None: - - # Init - for conf in args.c: - config = Config(conf,data) - - p = RunProcess(data, config,args.save,args.csv) - #p.init_logger() - process.append(p) + for conf in args.c: # Initialize all process + config = Config(conf,data) + p = RunProcess(data, config,args.save,args.csv) + process.append(p) p.start() - + for p in process : p.join() From 882d1bf515a1ee527c713a438a56d19004788d5c Mon Sep 17 00:00:00 2001 From: Ahp06 Date: Tue, 22 Jan 2019 15:45:24 +0100 Subject: [PATCH 5/5] Added imgs for doc --- sumo_project/files/imgs/runner_example.PNG | Bin 3294 -> 0 bytes sumo_project/files/imgs/runner_help.PNG | Bin 24182 -> 25842 bytes sumo_project/files/imgs/runner_new_dump.PNG | Bin 0 -> 3977 bytes sumo_project/files/imgs/runner_run_ex.PNG | Bin 0 -> 3507 bytes sumo_project/runner.py | 6 +++--- 5 files changed, 3 insertions(+), 3 deletions(-) delete mode 100644 sumo_project/files/imgs/runner_example.PNG create mode 100644 sumo_project/files/imgs/runner_new_dump.PNG create mode 100644 sumo_project/files/imgs/runner_run_ex.PNG diff --git a/sumo_project/files/imgs/runner_example.PNG b/sumo_project/files/imgs/runner_example.PNG deleted file mode 100644 index 4e571e411aa7b8a6f3bf84f529a6b15a320bea7d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3294 zcmd5;`(F}<7N@n9S;^8y`ADj*%sF$0<|B~G%txus(i|zSylO>iN+zI4o0<6}m3)h< zG&2RHB42=Wz6w*8FG>aS1t|d{A_{@m?r--mxc7&1zUTYXIiK^+IOFHJ)zHik003D`ay4SC0 z0KoXh7g;BW1Y-bzEoko($IfApQhrw4)ZuS$O)O3Yg=CulvPafYVf};Uc1!KU6Td91 zaNiUuj%{zq3O!DWJ+uogw^Zdqm`Z=W0|0q18`+};XFMc3;(I{!vHkoP_NG@gLJ zOVdtbJ(?7kRl?3mQy@ErpNh;PK75cx6 ztYy*AyJ#t6Ko3_I`7GNQ#q5`4y|vRH`YzLcrV&}tcFrRuOR2ezPbA)q_HH!Z{Y!(K+4*NuA^++&k?MZ>PUilq&87hxvyY#}XtB`Mg-sHgK{I*#E+5=F}A} zI+6ck%fnTd;y{U@V7?+i zUQG3#8x*969 zWNAS(8;?lPGVIjskyLsRCjrlNLTwpP0ZTMd`g#Yo6)UW5V{38h3b;!Owo${du&#dh zdeCdbpTZzJf6VR($Joknehi~g>i8cb zlAX6}+*=t@DK5k%M{G`7669RB8;t{<{czz1{W8iS!4Ds>6scQ@uTAILHZGOMSV9OM zwUX)i7Q4MVi{{8jl3$Bav(C-PrlkvoR>Znzhn_*CEd5qM8`)({8 zdN$ciiX@Rx)flF(MENsr`e!52kgsL&FpjG5TucY9A}GtC88nFqf=2tzuuHg4 zf^Bo-hnRY3-t(9+)};iJWG_)nr}w9ST*x*@m#eXF-1{f0btbR)TuDh)hX`;fwa{G1 z-WC+Gz7RFj*XwjH1wVA{U!^=7W@C_ltD-8>i#KA9c+D!K@zZ5EX~#*_c5Fx%9~aH( z{j{Sdj-lCH9~>LQ7>m&yC03WWw2aRodBd?Xrihw4AnE9s6EI&g{pI3Uj`yDhnWK*g zy6oB#sZuhgUNA4()M--dabRtt1drhxwKcmEC%v(WKnprLP`KJI(>;;!7=LIX)F~-% z-JjffIYEg@>kIP%Ipy*HGc8}qW%5S(i_a14i?zNWjAosg~t03Eb z_WKdFPE9Yd(gzp4lqWZXQ31`)0ng9PAoi)L8Kd}*RNjKE#LxEtv23f2C=i(GNHoU_ z85l!~-guv-x0|z11;eqFC1s!9`Vtn9Wc4n|mIegYCDlp2qrr(G5bALEcy6MiS3a!$ z*_ivcbZa#EC^QdoJDzADi`JkU2%~XUWK!dN1SdBv4Vha?`#OTc|4MmTk*IB;A?dDjbZOE_SDy?k z3Qz9IiTa4O?zwp_WxpZWJpJ7m94E6mLaX`l_g(DY$c^wH(7$)5LY$_^>+PCWWXFC~ zh(HSgV6)NW>T%_}mq8!zUAsc(Of5awGacAH=|lqgKadri$n+dh6NTX;TVG1^KoS^i zh9z!jIklIE;2of)EqSJ_wU3RR=yQwEdo_S?p9i)_28&_;TV_9VkCtKSkR0Mol_$xnNXo>V0 zq|LEwut-&e&(osAZQZTxX^ZmM%qV_=f)!CYio?PprS4h;xdwaLc*IblnO{qRW;|lV zQG*b;3CGg&imRVT9Oe$T(K;JLo_Cqlr&T81Zh!|6vAX9?R^&zUM3EtuP(?P_9}2l{8+4hRKVzhtC!*u6k6+ z^Zsd$BeiOx-mt*D1>8f8yV34w+yL~%;kgYxF|YE6&SX=Xx}494cC?&qrP`ca-Ypf+ zqa{k8l`X)1f#fz>#FfQo{TlruF307D*N<4M4%d#pDX`gbR={bgnFoXj_J1UBFt2l? zV_JBd>w4<(J5es8{gj!3r3NZGm_LJ(HMc! zom{uVr=j0_+pmBPlcr3QSjH$g-wq3p;TfnkoEG6Ailfc{px^EigJR!}^i^bk+h6s| z6Qmz_gqOZklVla|D;jZl7rvxlbP42Ail$y5PN-0nq(Pcv7+Pq`pn-Y}$eRulEVcDx z=k4lpC-DUi_SHB8-T}1R@ysY!aXKVXCN6UoI`qur#G9}YH~H?(K;BgvJi{4NJoJRK zBS-kje>yP6M;eKjwU_JawAnp6z2F{vk(<9H_dxZ08mnAfra4)snd12 zx+{bWcC3`U7Tniu($5|O3X-t)8u}@WxL%kYle_O_5Yqbg*stDyku5x}^LQ(8Uim$$ zsYux7q|B@PUB#6D!1iB31;NHdl{);zHmU9v%wc=_`jrQbcs4}0mkO1+`q+% QU&8eE@H;_0{@Wk_2BAuvE&u=k diff --git a/sumo_project/files/imgs/runner_help.PNG b/sumo_project/files/imgs/runner_help.PNG index 3c7b3d6e64587740f423bbcd4bd016dee2875925..a876a59344450cec0be46fb6171be3d95d22375a 100644 GIT binary patch literal 25842 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 literal 24182 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 diff --git a/sumo_project/files/imgs/runner_new_dump.PNG b/sumo_project/files/imgs/runner_new_dump.PNG new file mode 100644 index 0000000000000000000000000000000000000000..614674bb701f63b67e8624f98de841677be19ca9 GIT binary patch literal 3977 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 B_fG%- literal 0 HcmV?d00001 diff --git a/sumo_project/files/imgs/runner_run_ex.PNG b/sumo_project/files/imgs/runner_run_ex.PNG new file mode 100644 index 0000000000000000000000000000000000000000..f19826714ad1e3e249a310ce95fa250ae3a17f95 GIT binary patch literal 3507 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 literal 0 HcmV?d00001 diff --git a/sumo_project/runner.py b/sumo_project/runner.py index d070d18..256fedf 100644 --- a/sumo_project/runner.py +++ b/sumo_project/runner.py @@ -201,9 +201,9 @@ def add_options(parser): help='Choose the simulation directory') parser.add_argument("-run", "--run", type=str, - help='Run a simulation with the dump chosen') - parser.add_argument("-c", "--c", nargs='+', type=str, - help='Choose your configuration file from your working directory') + help='Run a simulation process with the dump chosen') + parser.add_argument("-c", "--c", metavar =('config1','config2'), nargs='+', type=str, + help='Choose your(s) configuration file(s) from your working directory') parser.add_argument("-save", "--save", action="store_true", help='Save the logs into the logs folder') parser.add_argument("-csv", "--csv", action="store_true",