1
0
mirror of https://github.com/Ahp06/SUMO_Emissions.git synced 2024-11-25 04:56:30 +00:00

Merge pull request #6 from Ahp06/sumo-runner

Sumo runner
This commit is contained in:
Axel HUYNH-PHUC 2019-01-20 09:31:20 +01:00 committed by GitHub
commit 72e8e6d3ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 307 additions and 229 deletions

View File

@ -4,15 +4,15 @@ Created on 17 oct. 2018
@author: Axel Huynh-Phuc, Thibaud Gasser
"""
from typing import Iterable
import traci
from model import Area, Vehicle
"""
This module defines all possible actions on the simulation
"""
import traci
from typing import Iterable
from model import Area, Vehicle
def compute_edge_weight(edge_id):
"""

View File

@ -4,6 +4,10 @@ Created on 17 oct. 2018
@author: Axel Huynh-Phuc, Thibaud Gasser
"""
"""
This module defines the global configuration for the simulation
"""
import datetime
import json
import logging
@ -12,10 +16,6 @@ import sys
from model import Emission
"""
This module defines the global configuration for the simulation
"""
class Config:
"""
@ -38,7 +38,7 @@ class Config:
:param config_file: The path to your configuration file
:return:
"""
with open(config_file, 'r') as f:
with open(f'files/configs/{config_file}.json', 'r') as f:
data = json.load(f)
for option in data:
@ -102,10 +102,10 @@ class Config:
now = datetime.datetime.now()
current_date = now.strftime("%Y_%m_%d_%H_%M_%S")
if not os.path.exists('logs'):
if not os.path.exists('files/logs'):
os.makedirs('logs')
log_filename = f'logs/sumo_logs_{current_date}_{self.config_filename}.log'
log_filename = f'files/logs/sumo_logs_{current_date}_{self.config_filename}.log'
logger = logging.getLogger("sumo_logger")
logger.setLevel(logging.INFO)

130
sumo_project/data.py Normal file
View File

@ -0,0 +1,130 @@
"""
Created on 17 oct. 2018
@author: Axel Huynh-Phuc, Thibaud Gasser
"""
"""
This module is used for loading simulation data
"""
import argparse
import csv
import datetime
import itertools
import json
import os
import pprint
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 model import Area, Vehicle, Lane, TrafficLight, Phase, Logic, Emission
class Data:
def __init__(self, map_bounds, config : Config):
"""
Data constructor
"""
self.map_bounds = map_bounds
self.config = config
def init_grid(self):
"""
Initialize the grid of the loaded map from the configuration
:param self.map_bounds: The map bounds
:param areas_number: The number of areas
:param window_size: The size of the acquisition window
:return: A list of areas
"""
self.grid = list()
areas_number = self.config.areas_number
window_size = self.config.window_size
width = self.map_bounds[1][0] / areas_number
height = self.map_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))
name = 'Area ({},{})'.format(i, j)
area = Area(ar_bounds, name, window_size)
self.grid.append(area)
return self.grid
def get_all_lanes(self) -> List[Lane]:
"""
Recover and creates a list of Lane objects
:return: The lanes list
"""
lanes = []
for lane_id in traci.lane.getIDList():
polygon_lane = LineString(traci.lane.getShape(lane_id))
initial_max_speed = traci.lane.getMaxSpeed(lane_id)
lanes.append(Lane(lane_id, polygon_lane, initial_max_speed))
return lanes
def parse_phase(self, phase_repr):
"""
Because the SUMO object Phase does not contain accessors,
we parse the string representation to retrieve data members.
:param phase_repr: The Phase string representation
:return: An new Phase instance
"""
duration = search('duration: {:f}', phase_repr)
min_duration = search('minDuration: {:f}', phase_repr)
max_duration = search('maxDuration: {:f}', phase_repr)
phase_def = search('phaseDef: {}\n', phase_repr)
if phase_def is None:
phase_def = ''
else:
phase_def = phase_def[0]
return Phase(duration[0], min_duration[0], max_duration[0], phase_def)
def add_data_to_areas(self):
"""
Adds all recovered data to different areas
:param areas: The list of areas
:return:
"""
lanes = self.get_all_lanes()
for area in self.grid:
for lane in lanes: # add lanes
if area.rectangle.intersects(lane.polygon):
area.add_lane(lane)
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
phases = []
for phase in traci.trafficlight.Logic.getPhases(l): # add phases to logics
phases.append(self.parse_phase(phase.__repr__()))
logics.append(Logic(l, phases))
area.add_tl(TrafficLight(tl_id, logics))
def save(self, dump_name):
"""
Save simulation data into a json file
:param dump_name: The name of your data dump
:return:
"""
dump_dir = 'files/dump'
if not os.path.exists(dump_dir):
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:
f.write(s)

View File

@ -4,6 +4,10 @@ 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
@ -11,96 +15,17 @@ import itertools
import os
import sys
import time
import traci
from typing import List
import actions
import traci
from config import Config
from model import Area, Vehicle, Lane, TrafficLight, Phase, Logic, Emission
import jsonpickle
from parse import search
from shapely.geometry import LineString
"""
This module defines the entry point of the application
"""
def init_grid(simulation_bounds, areas_number, window_size):
"""
Initialize the grid of the loaded map from the configuration
:param simulation_bounds: The map bounds
:param areas_number: The number of areas
:param window_size: The size of the acquisition window
:return: A list of areas
"""
grid = list()
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))
name = 'Area ({},{})'.format(i, j)
area = Area(ar_bounds, name, window_size)
grid.append(area)
traci.polygon.add(area.name, ar_bounds, (255, 0, 0)) # Add polygon for UI
return grid
def get_all_lanes() -> List[Lane]:
"""
Recover and creates a list of Lane objects
:return: The lanes list
"""
lanes = []
for lane_id in traci.lane.getIDList():
polygon_lane = LineString(traci.lane.getShape(lane_id))
initial_max_speed = traci.lane.getMaxSpeed(lane_id)
lanes.append(Lane(lane_id, polygon_lane, initial_max_speed))
return lanes
def parse_phase(phase_repr):
"""
Because the SUMO object Phase does not contain accessors,
we parse the string representation to retrieve data members.
:param phase_repr: The Phase string representation
:return: An new Phase instance
"""
duration = search('duration: {:f}', phase_repr)
min_duration = search('minDuration: {:f}', phase_repr)
max_duration = search('maxDuration: {:f}', phase_repr)
phase_def = search('phaseDef: {}\n', phase_repr)
if phase_def is None:
phase_def = ''
else:
phase_def = phase_def[0]
return Phase(duration[0], min_duration[0], max_duration[0], phase_def)
def add_data_to_areas(areas: List[Area]):
"""
Adds all recovered data to different areas
:param areas: The list of areas
:return:
"""
lanes = get_all_lanes()
for area in areas:
for lane in lanes: # add lanes
if area.rectangle.intersects(lane.polygon):
area.add_lane(lane)
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
phases = []
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))
import actions
from config import Config
from data import Data
from model import Area, Vehicle, Lane, TrafficLight, Phase, Logic, Emission
def compute_vehicle_emissions(veh_id):
@ -195,13 +120,13 @@ def export_data_to_csv(config, grid):
:param grid: The list of areas
:return:
"""
csv_dir = 'csv'
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'csv/{now}.csv', 'w') as f:
with open(f'files/csv/{now}.csv', 'w') as f:
writer = csv.writer(f)
# Write CSV headers
writer.writerow(itertools.chain(('Step',), (a.name for a in grid)))
@ -210,125 +135,3 @@ def export_data_to_csv(config, grid):
em_for_step = (f'{a.emissions_by_step[step].value():.3f}' for a in grid)
writer.writerow(itertools.chain((step,), em_for_step))
def run(config, logger, csv_export):
"""
Run the simulation with the configuration chosen
:param config: The simulation configuration
:param logger: The simulation logger
:return:
"""
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.areas_number, config.window_size)
add_data_to_areas(grid)
loading_time = round(time.perf_counter() - start, 2)
logger.info(f'Data loaded ({loading_time}s)')
logger.info('Simulation started...')
step = 0
while step < config.n_steps:
traci.simulationStep()
vehicles = get_all_vehicles()
get_emissions(grid, vehicles, step, config, logger)
step += 1
print(f'step = {step}/{config.n_steps}', end='\r')
finally:
traci.close(False)
if csv_export:
export_data_to_csv(config, grid)
logger.info(f'Exported data into the csv folder')
simulation_time = round(time.perf_counter() - start, 2)
logger.info(f'End of the simulation ({simulation_time}s)')
# 1 step is equal to one second simulated
logger.info(f'Real-time factor : {config.n_steps / simulation_time}')
total_emissions = Emission()
for area in 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()
if not (ref is None): # If a reference value exist (add yours into config.py)
global_diff = (ref.value() - total_emissions.value()) / ref.value()
logger.info(f'Global reduction percentage of emissions = {global_diff * 100} %')
logger.info(f'-> CO2 emissions = {get_reduction_percentage(ref.co2, total_emissions.co2)} %')
logger.info(f'-> CO emissions = {get_reduction_percentage(ref.co, total_emissions.co)} %')
logger.info(f'-> Nox emissions = {get_reduction_percentage(ref.nox, total_emissions.nox)} %')
logger.info(f'-> HC emissions = {get_reduction_percentage(ref.hc, total_emissions.hc)} %')
logger.info(f'-> PMx emissions = {get_reduction_percentage(ref.pmx, total_emissions.pmx)} %')
def add_options(parser):
"""
Add command line options
:param parser: The command line parser
:return:
"""
parser.add_argument("-f", "--configfile", type=str, default='configs/default_config.json', required=False,
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("-steps", "--steps", type=int, default=200, required=False,
help='Choose the simulated time (in seconds)')
parser.add_argument("-ref", "--ref", action="store_true",
help='Launch a reference simulation (without acting on areas)')
parser.add_argument("-gui", "--gui", action="store_true",
help="Show UI")
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
:param args: Command line options
:return:
"""
parser = argparse.ArgumentParser(description="")
add_options(parser)
args = parser.parse_args(args)
config = Config()
config.import_config_file(args.configfile) # By default the configfile is default_config.json
config.init_traci()
logger = config.init_logger(save_logs=args.save)
csv_export = False
if args.ref:
config.without_actions_mode = True
logger.info(f'Reference simulation')
if args.steps:
config.n_steps = args.steps
if args.gui:
config._SUMOCMD = "sumo-gui"
if args.csv:
csv_export = True
config.check_config()
logger.info(f'Loaded configuration file : {args.configfile}')
logger.info(f'Simulated time : {args.steps}s')
run(config, logger, csv_export)
if __name__ == '__main__':
main(sys.argv[1:])

View File

@ -1,13 +1,13 @@
{
"_SUMOCMD": "sumo",
"_SUMOCFG": "simulations/mulhouse_simulation/osm.sumocfg",
"_SUMOCFG": "files/simulations/mulhouse_simulation/osm.sumocfg",
"areas_number": 10,
"emissions_threshold": 500000,
"n_steps": 200,
"window_size":100,
"without_actions_mode": false,
"without_actions_mode": true,
"limit_speed_mode": true,
"speed_rf": 0.1,

View File

Before

Width:  |  Height:  |  Size: 489 KiB

After

Width:  |  Height:  |  Size: 489 KiB

View File

@ -4,17 +4,17 @@ Created on 17 oct. 2018
@author: Axel Huynh-Phuc, Thibaud Gasser
"""
"""
This module defines the business model of our application
"""
import collections
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
"""
This module defines the business model of our application
"""
class Lane:
@ -150,7 +150,7 @@ class Emission:
class Area:
"""
The Area class defines a grid area of the map
The Area class defines a grid area of the simulation map
"""
def __init__(self, coords, name, window_size):

145
sumo_project/runner.py Normal file
View File

@ -0,0 +1,145 @@
'''
Created on 19 janv. 2019
@author: Admin
'''
import argparse
import os
import sys
import time
import traci
import jsonpickle
from config import Config
from data import Data
import emissions
from model import Emission
def add_options(parser):
"""
Add command line options
:param parser: The command line parser
:return:
"""
parser.add_argument("-new_dump", "--new_dump", metavar=('config_file', 'dump_name'), nargs=2, type=str,
required=False, help='Load an create a new data dump with the configuration file chosen')
parser.add_argument("-run", "--run", type=str, required=False,
help='Run a simulation with the dump chosen')
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(config_file, dump_name):
config = Config()
config.import_config_file(config_file)
config.check_config()
config.init_traci()
sumo_binary = os.path.join(os.environ['SUMO_HOME'], 'bin', 'sumo')
sumo_cmd = [sumo_binary, "-c", config._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(), config)
data.init_grid()
data.add_data_to_areas()
data.save(dump_name)
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 run(data : Data, config : Config, logger):
try:
traci.start(config.sumo_cmd)
for area in data.grid:
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:
create_dump(args.new_dump[0], args.new_dump[1])
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())
config = data.config
logger = config.init_logger(save_logs=args.save)
logger.info(f'Running simulation dump {args.run}...')
start = time.perf_counter()
run(data, config, logger)
if args.csv:
emissions.export_data_to_csv(config, data.grid)
logger.info(f'Exported data into the csv folder')
simulation_time = round(time.perf_counter() - start, 2)
logger.info(f'End of the simulation ({simulation_time}s)')
# 1 step is equal to one second simulated
logger.info(f'Real-time factor : {config.n_steps / simulation_time}')
total_emissions = Emission()
for area in 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()
if not (ref is None): # If a reference value exist (add yours into config.py)
global_diff = (ref.value() - total_emissions.value()) / ref.value()
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:])