""" RLLIB SUMO Utils - SUMO Connector Author: Lara CODECA lara.codeca@gmail.com See: https://github.com/lcodeca/rllibsumoutils https://github.com/lcodeca/rllibsumodocker for further details. """ import logging import os import sys # Attach $SUMO_HOME/tools to the path to import SUMO libraries if "SUMO_HOME" in os.environ: sys.path.append(os.path.join(os.environ["SUMO_HOME"], "tools")) else: raise Exception("Please declare environment variable 'SUMO_HOME'") ############################################################################### logging.basicConfig() logger = logging.getLogger(__name__) ############################################################################### DEFAULT_CONFIG = { # SUMO Connector. Default: "libsumo". # Possible strings: "libsumo" or "traci" "sumo_connector": "libsumo", # Enable the GUI, works only with traci "sumo_gui": False, # SUMO configuration file. Required. String. "sumo_cfg": None, # Overides . # Required when using multiple environments at the same time. String. "sumo_output": "", # Additional parameter for the SUMO command line. # It cannot contain --output-prefix. List of strings. "sumo_params": None, # Enable TraCI trace file. Boolean. "trace_file": False, # SUMO Simulation ending time, in seconds. Float. "end_of_sim": None, # SUMO update frequency in number of traci.simulationStep() calls. Integer. "update_freq": 1, # SUMO tripinfo file as defined in the sumo configuration file in # . # Required for gathering metrics only. String. "tripinfo_keyword": None, # SUMO tripinfo XML Schema file. # Required for gathering metrics only. String. "tripinfo_xml_schema": None, # Logging legel. Should be one of DEBUG, INFO, WARN, or ERROR. "log_level": "WARN", # Anything. User defined. "misc": None, } ############################################################################### class SUMOConnector(object): """Handler of a SUMO simulation.""" def __init__(self, config): """ Initialize SUMO and sets the beginning of the simulation. Param: config: Dict. See DEFAULT_CONFIG. """ self._config = config # logging level = logging.getLevelName(config["log_level"]) logger.setLevel(level) # libsumo vs TraCI selection if config["sumo_connector"] == "libsumo": import libsumo as traci elif config["sumo_connector"] == "traci": import traci else: raise Exception( "ERROR: '{}' is not a valid option for 'sumo_connector'. " "The possible connectors are 'traci' or 'libsumo'.".format( config["sumo_connector"] ) ) # TraCI Handler and SUMO simulation logger.debug("Starting SUMOConnector in process %d.", os.getpid()) self._sumo_label = "{}".format(os.getpid()) self._sumo_output_prefix = "{}{}".format( config["sumo_output"], self._sumo_label ) self._sumo_parameters = ["sumo", "-c", config["sumo_cfg"]] if config["sumo_gui"] and config["sumo_connector"] == "traci": self._sumo_parameters[0] = "sumo-gui" self._sumo_parameters.extend(["--start", "--quit-on-end"]) if config["sumo_params"] is not None: self._sumo_parameters.extend(config["sumo_params"]) self._sumo_parameters.extend(["--output-prefix", self._sumo_output_prefix]) logger.debug("SUMO command line: %s", str(self._sumo_parameters)) if config["trace_file"]: traci.start( self._sumo_parameters, traceFile="{}.tracefile.log".format(self._sumo_output_prefix), ) else: traci.start(self._sumo_parameters) self.traci_handler = traci # From now on, the call must always be to self.traci_handler self._is_ongoing = True self._start_time = self.traci_handler.simulation.getTime() self._sumo_steps = 0 self._manually_stopped = False # Initialize simulation self._initialize_simulation() # Initialize metrics self._initialize_metrics() def __del__(self): logger.debug("Deleting SUMOConnector in process %d.", os.getpid()) try: self.end_simulation() except KeyError: logger.warning("Simulation %s already closed.", self._sumo_label) ########################################################################### def _initialize_simulation(self): """Specific simulation initialization.""" raise NotImplementedError def _initialize_metrics(self): """Specific metrics initialization""" raise NotImplementedError def _default_step_action(self, agents): """Specific code to be executed in every simulation step""" raise NotImplementedError ########################################################################### def _stopping_condition(self, current_step_counter, until_end): """Computes the stopping condition.""" if self._manually_stopped: return True if self.traci_handler.simulation.getMinExpectedNumber() <= 0: # No entities left in the simulation. return True if self._config["end_of_sim"] is not None: if self.traci_handler.simulation.getTime() > self._config["end_of_sim"]: # the simulatio reach the predefined (from parameters) end return True if current_step_counter == self._config["update_freq"] and not until_end: return True return False def step(self, until_end=False, agents=None): """ Runs a "learning" step and returns if the simulation has finished. This function in meant to be called by the RLLIB Environment. Params: until_end: Bool. If True, run the sumo simulation until the end. agents: Set(String). It passes the agents to the _default_step_action function. Return: Bool. True iff the simulation is still ongoing. """ if agents is None: agents = set() # Execute SUMO steps until the learning needs to happen current_step_counter = 0 logger.debug( "=================================================================" ) while not self._stopping_condition(current_step_counter, until_end): logger.debug( "[%s] Current step counter: %d, Update frequency: %d", str(until_end), current_step_counter, self._config["update_freq"], ) self.traci_handler.simulationStep() self._sumo_steps += 1 current_step_counter += 1 self._default_step_action(agents) logger.debug( "=================================================================" ) # If the simulation has finished if self.is_ongoing_sim(): return True logger.debug("The SUMO simulation is done.") return False def fast_forward(self, time): """ Move the simulation forward (without doing anything else) until the given time. Param: time: Float, simulation time in seconds. """ logger.debug( "Fast-forward from time %.2f", self.traci_handler.simulation.getTime() ) self.traci_handler.simulationStep(float(time)) logger.debug( "Fast-forward to time %.2f", self.traci_handler.simulation.getTime() ) ########################################################################### def get_sumo_steps(self): """Returns the total number of traci.simulationStep() calls.""" return self._sumo_steps def is_ongoing_sim(self): """Return True iff the SUMO simulation is still ongoing.""" if self._manually_stopped: return False if self.traci_handler.simulation.getMinExpectedNumber() <= 0: # No entities left in the simulation. return False if self._config["end_of_sim"] is not None: if self.traci_handler.simulation.getTime() > self._config["end_of_sim"]: # the simulatio reach the predefined (from parameters) end return False return True def get_current_time(self): """ Returns the current simulation time, or None if the simulation is not ongoing. """ if self.is_ongoing_sim(): return self.traci_handler.simulation.getTime() return None def end_simulation(self): """Forces the simulation to stop.""" if self.is_ongoing_sim(): logger.info("Closing TraCI %s", self._sumo_label) self._manually_stopped = True self.traci_handler.close() else: logger.warning("TraCI %s is already closed.", self._sumo_label) ###########################################################################