ray/rllib/contrib/sumo/connector.py
Balaji Veeramani 7f1bacc7dc
[CI] Format Python code with Black (#21975)
See #21316 and #21311 for the motivation behind these changes.
2022-01-29 18:41:57 -08:00

261 lines
9.2 KiB
Python

""" 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 <output-prefix value=".."/>.
# 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
# <tripinfo-output value=".."/>.
# 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)
###########################################################################