Acquiring data from Python to CSV
prajwalvinod opened this issue · 9 comments
I am trying to use the framework that is shown here to simulate a traffic network via TraCI. I use the command to get the output data in csv format as well as running the network using traci. But I am faced with two issues:
- Although the gui opens up, the network does not run, as in the gui stays blank.
- Even if on occasion it does run, there is no output csv file in the path specified.
I follow the initialization process as shown in the environment code file. And I am not sure what the issue is exactly and why it is not working.
Hi, what is the example that you are trying to run? The output csv file is generated at the end of the simulation
I am not exactly running any of the simulations from the files. I basically used the framework for creating the environment and the traffic lights and then run a simulation for my own SUMO network that I created for some thesis work.
can you share your network file?
The following is what my net file looks like.
<?xml version="1.0" encoding="UTF-8"?>
<!-- generated on 2023-04-03 17:45:35 by Eclipse SUMO netconvert Version 1.14.1
<configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://sumo.dlr.de/xsd/netconvertConfiguration.xsd">
<input>
<node-files value="hegyi_nodes.nod.xml"/>
<edge-files value="hegyi_edges.edg.xml"/>
<connection-files value="hegyi_con.con.xml"/>
<tllogic-files value="hegyi_tll.tll.xml"/>
</input>
<output>
<output-file value="hegyi_network.net.xml"/>
</output>
</configuration>
-->
<net version="1.9" junctionCornerDetail="5" limitTurnSpeed="5.50" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://sumo.dlr.de/xsd/net_file.xsd">
<location netOffset="0.00,0.00" convBoundary="0.00,0.00,6000.00,258.82" origBoundary="0.00,0.00,6000.00,258.82" projParameter="!"/>
<edge id=":J1_0" function="internal">
<lane id=":J1_0_0" index="0" speed="27.78" length="0.10" shape="1000.00,-4.80 1000.00,-4.80"/>
<lane id=":J1_0_1" index="1" speed="27.78" length="0.10" shape="1000.00,-1.60 1000.00,-1.60"/>
</edge>
<edge id=":J2_0" function="internal">
<lane id=":J2_0_0" index="0" speed="27.78" length="0.10" shape="2000.00,-4.80 2000.00,-4.80"/>
<lane id=":J2_0_1" index="1" speed="27.78" length="0.10" shape="2000.00,-1.60 2000.00,-1.60"/>
</edge>
<edge id=":J3_0" function="internal">
<lane id=":J3_0_0" index="0" speed="27.78" length="0.10" shape="3000.00,-4.80 3000.00,-4.80"/>
<lane id=":J3_0_1" index="1" speed="27.78" length="0.10" shape="3000.00,-1.60 3000.00,-1.60"/>
</edge>
<edge id=":J4_0" function="internal">
<lane id=":J4_0_0" index="0" speed="27.78" length="20.35" shape="3983.65,-4.80 4004.00,-4.80"/>
<lane id=":J4_0_1" index="1" speed="27.78" length="20.35" shape="3983.65,-1.60 4004.00,-1.60"/>
</edge>
<edge id=":J4_2" function="internal">
<lane id=":J4_2_0" index="0" speed="27.78" length="20.26" shape="3984.28,2.56 3988.00,1.36 3992.20,-0.01 3997.38,-1.13 4004.00,-1.60"/>
</edge>
<edge id=":J5_0" function="internal">
<lane id=":J5_0_0" index="0" speed="27.78" length="0.10" shape="5000.00,-4.80 5000.00,-4.80"/>
<lane id=":J5_0_1" index="1" speed="27.78" length="0.10" shape="5000.00,-1.60 5000.00,-1.60"/>
</edge>
<edge id=":J8_0" function="internal">
<lane id=":J8_0_0" index="0" speed="27.78" length="0.10" shape="3984.19,2.58 3984.19,2.58"/>
</edge>
<edge id="E0" from="J0" to="J1" priority="-1" length="1000.00">
<lane id="E0_0" index="0" speed="27.78" length="1000.00" shape="0.00,-4.80 1000.00,-4.80"/>
<lane id="E0_1" index="1" speed="27.78" length="1000.00" shape="0.00,-1.60 1000.00,-1.60"/>
</edge>
<edge id="E1" from="J1" to="J2" priority="-1" length="1000.00">
<lane id="E1_0" index="0" speed="27.78" length="1000.00" shape="1000.00,-4.80 2000.00,-4.80"/>
<lane id="E1_1" index="1" speed="27.78" length="1000.00" shape="1000.00,-1.60 2000.00,-1.60"/>
</edge>
<edge id="E2" from="J2" to="J3" priority="-1" length="1000.00">
<lane id="E2_0" index="0" speed="27.78" length="1000.00" shape="2000.00,-4.80 3000.00,-4.80"/>
<lane id="E2_1" index="1" speed="27.78" length="1000.00" shape="2000.00,-1.60 3000.00,-1.60"/>
</edge>
<edge id="E3" from="J3" to="J4" priority="-1" length="1000.00">
<lane id="E3_0" index="0" speed="27.78" length="1000.00" shape="3000.00,-4.80 3983.65,-4.80"/>
<lane id="E3_1" index="1" speed="27.78" length="1000.00" shape="3000.00,-1.60 3983.65,-1.60"/>
</edge>
<edge id="E4" from="J4" to="J5" priority="-1" length="1000.00">
<lane id="E4_0" index="0" speed="27.78" length="1000.00" acceleration="1" shape="4004.00,-4.80 5000.00,-4.80"/>
<lane id="E4_1" index="1" speed="27.78" length="1000.00" shape="4004.00,-1.60 5000.00,-1.60"/>
</edge>
<edge id="E5" from="J5" to="J6" priority="-1" length="1000.00">
<lane id="E5_0" index="0" speed="27.78" length="1000.00" shape="5000.00,-4.80 6000.00,-4.80"/>
<lane id="E5_1" index="1" speed="27.78" length="1000.00" shape="5000.00,-1.60 6000.00,-1.60"/>
</edge>
<edge id="E6" from="J7" to="J8" priority="-1" length="984.06">
<lane id="E6_0" index="0" speed="27.78" length="984.06" shape="3033.66,257.27 3984.19,2.58"/>
</edge>
<edge id="E7" from="J8" to="J4" priority="-1" length="15.94">
<lane id="E7_0" index="0" speed="27.78" length="15.94" shape="3984.19,2.58 3984.28,2.56"/>
</edge>
<tlLogic id="J8" type="static" programID="hegyi_tll" offset="0">
<phase duration="35" state="r"/>
<phase duration="5" state="y"/>
<phase duration="20" state="G"/>
</tlLogic>
<junction id="J0" type="dead_end" x="0.00" y="0.00" incLanes="" intLanes="" shape="0.00,0.00 0.00,-6.40"/>
<junction id="J1" type="unregulated" x="1000.00" y="0.00" incLanes="E0_0 E0_1" intLanes=":J1_0_0 :J1_0_1" shape="1000.00,0.00 1000.00,-6.40 1000.00,0.00"/>
<junction id="J2" type="unregulated" x="2000.00" y="0.00" incLanes="E1_0 E1_1" intLanes=":J2_0_0 :J2_0_1" shape="2000.00,0.00 2000.00,-6.40 2000.00,0.00"/>
<junction id="J3" type="unregulated" x="3000.00" y="0.00" incLanes="E2_0 E2_1" intLanes=":J3_0_0 :J3_0_1" shape="3000.00,0.00 3000.00,-6.40 3000.00,0.00"/>
<junction id="J4" type="zipper" x="4000.00" y="0.00" incLanes="E3_0 E3_1 E7_0" intLanes=":J4_0_0 :J4_0_1 :J4_2_0" shape="4004.00,0.00 4004.00,-6.40 3983.65,-6.40 3983.65,0.00 3983.78,1.04 3984.61,4.13">
<request index="0" response="000" foes="000" cont="0"/>
<request index="1" response="100" foes="100" cont="0"/>
<request index="2" response="010" foes="010" cont="0"/>
</junction>
<junction id="J5" type="unregulated" x="5000.00" y="0.00" incLanes="E4_0 E4_1" intLanes=":J5_0_0 :J5_0_1" shape="5000.00,0.00 5000.00,-6.40 5000.00,0.00"/>
<junction id="J6" type="dead_end" x="6000.00" y="0.00" incLanes="E5_0 E5_1" intLanes="" shape="6000.00,-6.40 6000.00,0.00"/>
<junction id="J7" type="dead_end" x="3034.07" y="258.82" incLanes="" intLanes="" shape="3034.07,258.82 3033.24,255.73"/>
<junction id="J8" type="traffic_light" x="3984.60" y="4.13" incLanes="E6_0" intLanes=":J8_0_0" shape="3984.60,4.13 3983.77,1.04 3984.60,4.13">
<request index="0" response="0" foes="0" cont="0"/>
</junction>
<connection from="E0" to="E1" fromLane="0" toLane="0" via=":J1_0_0" dir="s" state="M"/>
<connection from="E0" to="E1" fromLane="1" toLane="1" via=":J1_0_1" dir="s" state="M"/>
<connection from="E1" to="E2" fromLane="0" toLane="0" via=":J2_0_0" dir="s" state="M"/>
<connection from="E1" to="E2" fromLane="1" toLane="1" via=":J2_0_1" dir="s" state="M"/>
<connection from="E2" to="E3" fromLane="0" toLane="0" via=":J3_0_0" dir="s" state="M"/>
<connection from="E2" to="E3" fromLane="1" toLane="1" via=":J3_0_1" dir="s" state="M"/>
<connection from="E3" to="E4" fromLane="0" toLane="0" via=":J4_0_0" dir="s" state="M"/>
<connection from="E3" to="E4" fromLane="1" toLane="1" via=":J4_0_1" dir="s" state="Z"/>
<connection from="E4" to="E5" fromLane="0" toLane="0" via=":J5_0_0" dir="s" state="M"/>
<connection from="E4" to="E5" fromLane="1" toLane="1" via=":J5_0_1" dir="s" state="M"/>
<connection from="E6" to="E7" fromLane="0" toLane="0" via=":J8_0_0" tl="J8" linkIndex="0" dir="s" state="O"/>
<connection from="E7" to="E4" fromLane="0" toLane="1" via=":J4_2_0" dir="s" state="Z"/>
<connection from=":J1_0" to="E1" fromLane="0" toLane="0" dir="s" state="M"/>
<connection from=":J1_0" to="E1" fromLane="1" toLane="1" dir="s" state="M"/>
<connection from=":J2_0" to="E2" fromLane="0" toLane="0" dir="s" state="M"/>
<connection from=":J2_0" to="E2" fromLane="1" toLane="1" dir="s" state="M"/>
<connection from=":J3_0" to="E3" fromLane="0" toLane="0" dir="s" state="M"/>
<connection from=":J3_0" to="E3" fromLane="1" toLane="1" dir="s" state="M"/>
<connection from=":J4_0" to="E4" fromLane="0" toLane="0" dir="s" state="M"/>
<connection from=":J4_0" to="E4" fromLane="1" toLane="1" dir="s" state="M"/>
<connection from=":J4_2" to="E4" fromLane="0" toLane="1" dir="s" state="M"/>
<connection from=":J5_0" to="E5" fromLane="0" toLane="0" dir="s" state="M"/>
<connection from=":J5_0" to="E5" fromLane="1" toLane="1" dir="s" state="M"/>
<connection from=":J8_0" to="E7" fromLane="0" toLane="0" dir="s" state="M"/>
</net>
I also wanted to ask about additional files from sumo and if they can be included in the code for the environment. If yes, how can that be done ? Is it by initializing the config file or doing it separately ?
You can add files by using the "additional_sumo_cmd" argument (https://github.com/LucasAlegre/sumo-rl/blob/main/sumo_rl/environment/env.py#L105), and passing additional files with "-a filename"
Thanks for that.
- That solved the issue with additional files.
- But I still am not able to generate a csv output file.
- Does it make a difference that I generated my network using netconvert whereas your networks are generated using netedit ?
- When I run an open loop simulation for the environment from python no matter how I modify the code it does not seem to change the traffic light phase. What could be the possible cause ?
- Is it possible to generate traffic data per time step in the simulation, i.e., for example every second of the simulation ?
Sorry for the long inquiry. I have divided the questions into as small pieces as possible and numbered them to make it easier to read and answer.
I would also like to ask that if my traffic light logic looks like this:
<additional>
<tlLogic id="J8" programID="hegyi_tll" offset="0" type="static">
<phase duration="31" state="r"/>
<phase duration="4" state="y"/>
<phase duration="20" state="g"/>
</tlLogic>
</additional>
How would I define the phases in the Python traffic light description as you have given in the repository ?
That is in this:
class TrafficSignal:
"""This class represents a Traffic Signal controlling an intersection.
It is responsible for retrieving information and changing the traffic phase using the Traci API.
IMPORTANT: It assumes that the traffic phases defined in the .net file are of the form:
[green_phase, yellow_phase, green_phase, yellow_phase, ...]
Currently it is not supporting all-red phases (but should be easy to implement it).
# Observation Space
The default observation for each traffic signal agent is a vector:
obs = [phase_one_hot, min_green, lane_1_density,...,lane_n_density, lane_1_queue,...,lane_n_queue]
- ```phase_one_hot``` is a one-hot encoded vector indicating the current active green phase
- ```min_green``` is a binary variable indicating whether min_green seconds have already passed in the current phase
- ```lane_i_density``` is the number of vehicles in incoming lane i dividided by the total capacity of the lane
- ```lane_i_queue``` is the number of queued (speed below 0.1 m/s) vehicles in incoming lane i divided by the total capacity of the lane
You can change the observation space by implementing a custom observation class. See :py:class:`sumo_rl.environment.observations.ObservationFunction`.
# Action Space
Action space is discrete, corresponding to which green phase is going to be open for the next delta_time seconds.
# Reward Function
The default reward function is 'diff-waiting-time'. You can change the reward function by implementing a custom reward function and passing to the constructor of :py:class:`sumo_rl.environment.env.SumoEnvironment`.
"""
# Default min gap of SUMO (see https://sumo.dlr.de/docs/Simulation/Safety.html). Should this be parameterized?
MIN_GAP = 2.5
def __init__(
self,
env,
ts_id: str,
delta_time: int,
yellow_time: int,
min_green: int,
max_green: int,
begin_time: int,
reward_fn: Union[str, Callable],
sumo,
):
"""Initializes a TrafficSignal object.
Args:
env (SumoEnvironment): The environment this traffic signal belongs to.
ts_id (str): The id of the traffic signal.
delta_time (int): The time in seconds between actions.
yellow_time (int): The time in seconds of the yellow phase.
min_green (int): The minimum time in seconds of the green phase.
max_green (int): The maximum time in seconds of the green phase.
begin_time (int): The time in seconds when the traffic signal starts operating.
reward_fn (Union[str, Callable]): The reward function. Can be a string with the name of the reward function or a callable function.
sumo (Sumo): The Sumo instance.
"""
self.id = ts_id
self.env = env
self.delta_time = delta_time
self.yellow_time = yellow_time
self.min_green = min_green
self.max_green = max_green
self.green_phase = 0
self.is_yellow = False
self.time_since_last_phase_change = 0
self.next_action_time = begin_time
self.last_measure = 0.0
self.last_reward = None
self.reward_fn = reward_fn
self.sumo = sumo
if type(self.reward_fn) is str:
if self.reward_fn in TrafficSignal.reward_fns.keys():
self.reward_fn = TrafficSignal.reward_fns[self.reward_fn]
else:
raise NotImplementedError(f"Reward function {self.reward_fn} not implemented")
self.observation_fn = self.env.observation_class(self)
self._build_phases()
self.lanes = list(
dict.fromkeys(self.sumo.trafficlight.getControlledLanes(self.id))
) # Remove duplicates and keep order
self.out_lanes = [link[0][1] for link in self.sumo.trafficlight.getControlledLinks(self.id) if link]
self.out_lanes = list(set(self.out_lanes))
self.lanes_length = {lane: self.sumo.lane.getLength(lane) for lane in self.lanes + self.out_lanes}
self.observation_space = self.observation_fn.observation_space()
self.action_space = spaces.Discrete(self.num_green_phases)
def _build_phases(self):
phases = self.sumo.trafficlight.getAllProgramLogics(self.id)[0].phases
if self.env.fixed_ts:
self.num_green_phases = len(phases) // 2 # Number of green phases == number of phases (green+yellow) divided by 2
return
self.green_phases = []
self.yellow_dict = {}
for phase in phases:
state = phase.state
if "y" not in state and (state.count("r") + state.count("s") != len(state)):
self.green_phases.append(self.sumo.trafficlight.Phase(60, state))
self.num_green_phases = len(self.green_phases)
self.all_phases = self.green_phases.copy()
for i, p1 in enumerate(self.green_phases):
for j, p2 in enumerate(self.green_phases):
if i == j:
continue
yellow_state = ""
for s in range(len(p1.state)):
if (p1.state[s] == "G" or p1.state[s] == "g") and (p2.state[s] == "r" or p2.state[s] == "s"):
yellow_state += "y"
else:
yellow_state += p1.state[s]
self.yellow_dict[(i, j)] = len(self.all_phases)
self.all_phases.append(self.sumo.trafficlight.Phase(self.yellow_time, yellow_state))
programs = self.sumo.trafficlight.getAllProgramLogics(self.id)
logic = programs[0]
logic.type = 0
logic.phases = self.all_phases
self.sumo.trafficlight.setProgramLogic(self.id, logic)
self.sumo.trafficlight.setRedYellowGreenState(self.id, self.all_phases[0].state)
@property
def time_to_act(self):
"""Returns True if the traffic signal should act in the current step."""
return self.next_action_time == self.env.sim_step
def update(self):
"""Updates the traffic signal state.
If the traffic signal should act, it will set the next green phase and update the next action time.
"""
self.time_since_last_phase_change += 1
if self.is_yellow and self.time_since_last_phase_change == self.yellow_time:
# self.sumo.trafficlight.setPhase(self.id, self.green_phase)
self.sumo.trafficlight.setRedYellowGreenState(self.id, self.all_phases[self.green_phase].state)
self.is_yellow = False
def set_next_phase(self, new_phase: int):
"""Sets what will be the next green phase and sets yellow phase if the next phase is different than the current.
Args:
new_phase (int): Number between [0 ... num_green_phases]
"""
new_phase = int(new_phase)
if self.green_phase == new_phase or self.time_since_last_phase_change < self.yellow_time + self.min_green:
# self.sumo.trafficlight.setPhase(self.id, self.green_phase)
self.sumo.trafficlight.setRedYellowGreenState(self.id, self.all_phases[self.green_phase].state)
self.next_action_time = self.env.sim_step + self.delta_time
else:
# self.sumo.trafficlight.setPhase(self.id, self.yellow_dict[(self.green_phase, new_phase)]) # turns yellow
self.sumo.trafficlight.setRedYellowGreenState(
self.id, self.all_phases[self.yellow_dict[(self.green_phase, new_phase)]].state
)
self.green_phase = new_phase
self.next_action_time = self.env.sim_step + self.delta_time
self.is_yellow = True
self.time_since_last_phase_change = 0
def compute_observation(self):
"""Computes the observation of the traffic signal."""
return self.observation_fn()
def compute_reward(self):
"""Computes the reward of the traffic signal."""
self.last_reward = self.reward_fn(self)
return self.last_reward
def _pressure_reward(self):
return self.get_pressure()
def _average_speed_reward(self):
return self.get_average_speed()
def _queue_reward(self):
return -self.get_total_queued()
def _diff_waiting_time_reward(self):
ts_wait = sum(self.get_accumulated_waiting_time_per_lane()) / 100.0
reward = self.last_measure - ts_wait
self.last_measure = ts_wait
return reward
def _observation_fn_default(self):
phase_id = [1 if self.green_phase == i else 0 for i in range(self.num_green_phases)] # one-hot encoding
min_green = [0 if self.time_since_last_phase_change < self.min_green + self.yellow_time else 1]
density = self.get_lanes_density()
queue = self.get_lanes_queue()
observation = np.array(phase_id + min_green + density + queue, dtype=np.float32)
return observation
def get_accumulated_waiting_time_per_lane(self) -> List[float]:
"""Returns the accumulated waiting time per lane.
Returns:
List[float]: List of accumulated waiting time of each intersection lane.
"""
wait_time_per_lane = []
for lane in self.lanes:
veh_list = self.sumo.lane.getLastStepVehicleIDs(lane)
wait_time = 0.0
for veh in veh_list:
veh_lane = self.sumo.vehicle.getLaneID(veh)
acc = self.sumo.vehicle.getAccumulatedWaitingTime(veh)
if veh not in self.env.vehicles:
self.env.vehicles[veh] = {veh_lane: acc}
else:
self.env.vehicles[veh][veh_lane] = acc - sum(
[self.env.vehicles[veh][lane] for lane in self.env.vehicles[veh].keys() if lane != veh_lane]
)
wait_time += self.env.vehicles[veh][veh_lane]
wait_time_per_lane.append(wait_time)
return wait_time_per_lane
def get_average_speed(self) -> float:
"""Returns the average speed normalized by the maximum allowed speed of the vehicles in the intersection.
Obs: If there are no vehicles in the intersection, it returns 1.0.
"""
avg_speed = 0.0
vehs = self._get_veh_list()
if len(vehs) == 0:
return 1.0
for v in vehs:
avg_speed += self.sumo.vehicle.getSpeed(v) / self.sumo.vehicle.getAllowedSpeed(v)
return avg_speed / len(vehs)
def get_pressure(self):
"""Returns the pressure (#veh leaving - #veh approaching) of the intersection."""
return sum(self.sumo.lane.getLastStepVehicleNumber(lane) for lane in self.out_lanes) - sum(
self.sumo.lane.getLastStepVehicleNumber(lane) for lane in self.lanes
)
def get_out_lanes_density(self) -> List[float]:
"""Returns the density of the vehicles in the outgoing lanes of the intersection."""
lanes_density = [
self.sumo.lane.getLastStepVehicleNumber(lane)
/ (self.lanes_length[lane] / (self.MIN_GAP + self.sumo.lane.getLastStepLength(lane)))
for lane in self.out_lanes
]
return [min(1, density) for density in lanes_density]
def get_lanes_density(self) -> List[float]:
"""Returns the density [0,1] of the vehicles in the incoming lanes of the intersection.
Obs: The density is computed as the number of vehicles divided by the number of vehicles that could fit in the lane.
"""
lanes_density = [
self.sumo.lane.getLastStepVehicleNumber(lane)
/ (self.lanes_length[lane] / (self.MIN_GAP + self.sumo.lane.getLastStepLength(lane)))
for lane in self.lanes
]
return [min(1, density) for density in lanes_density]
def get_lanes_queue(self) -> List[float]:
"""Returns the queue [0,1] of the vehicles in the incoming lanes of the intersection.
Obs: The queue is computed as the number of vehicles halting divided by the number of vehicles that could fit in the lane.
"""
lanes_queue = [
self.sumo.lane.getLastStepHaltingNumber(lane)
/ (self.lanes_length[lane] / (self.MIN_GAP + self.sumo.lane.getLastStepLength(lane)))
for lane in self.lanes
]
return [min(1, queue) for queue in lanes_queue]
def get_total_queued(self) -> int:
"""Returns the total number of vehicles halting in the intersection."""
return sum(self.sumo.lane.getLastStepHaltingNumber(lane) for lane in self.lanes)
def _get_veh_list(self):
veh_list = []
for lane in self.lanes:
veh_list += self.sumo.lane.getLastStepVehicleIDs(lane)
return veh_list
@classmethod
def register_reward_fn(cls, fn: Callable):
"""Registers a reward function.
Args:
fn (Callable): The reward function to register.
"""
if fn.__name__ in cls.reward_fns.keys():
raise KeyError(f"Reward function {fn.__name__} already exists")
cls.reward_fns[fn.__name__] = fn
reward_fns = {
"diff-waiting-time": _diff_waiting_time_reward,
"average-speed": _average_speed_reward,
"queue": _queue_reward,
"pressure": _pressure_reward,
}
I would also like to know why there is a [0] in the following lines of code:
phases = self.sumo.trafficlight.getAllProgramLogics(self.id)[0].phases
logic = programs[0]
self.out_lanes = [link[0][1] for link in self.sumo.trafficlight.getControlledLinks(self.id) if link]
Also in the above traffic signal definition in which line do I input the name of my traffic signal, i.e., "J8" ?
Hoping for a reply.
Thanks.
Solved.