[
Date Prev][
Date Next][
Thread Prev][
Thread Next][
Date Index][
Thread Index]
[
List Home]
[sumo-user] Issue regarding re-routing after onboarding person with TaxiService
|
I have completed this much code and attached the file her, in this code taxi only onboard the passenger from the current edge if possible or person is available on the next edge, I want that taxi should onboard passenger from upcoming edge until it's capacity is full. After onboarding, taxi should go with the probabilistic route as given in the function decide_next_idle_junction_for_taxi(). Taxi is allowed to take maximum 2 diversion from the shortest paths which will be given by the function of get_all_shortest_routes(). If the upcoming edge which taxi is taking is not the matching the upcoming edge from any of the possible shortest route then it will considered as diversion. For example: taxi is having shortest possible route as 1, 2, 3, 4, taxi is currently on edge 1, and as per function it is going on the route 1, 5, then from 5th edge it goes to 3 as per function and then 4 so final route will be 1, 5, 3, 4 so here taxi has taken one diversion. If taxi has completed 2 diversion then let taxi decide it's own route as per dispatchTaxi() .
I am not able to do the rerouting after onboarding the passenger, it is removing the taxis from the simulation, what's the issue with this? How can I solve this?
Thanks,
Jay
from collections import defaultdict
import traci
import sumolib
import numpy as np
import random
import networkx as nx
net = sumolib.net.readNet("grid.net.xml")
# Get special edges (e.g., bus lanes, metro tracks)
edge_ids = [e.getID() for e in net.getEdges() if not e.isSpecial()]
ped_id_counter = 0
od_matrix = {}
DAMPING_FACTOR_ZETA = 0.5
SCALE_DISTANCE = 100.0
# GRID_SIZE = 250 # Size of each grid cell
# spatial_grid = {} # Dictionary to store passengers in grid cells
train_stops = {
"E2": {
"down": {"id": "E0_stop_down", "lane": "E1E2", "pos": 460},
"up": {"id": "E0_stop_up", "lane": "E3E2", "pos": 460}
},
"E4": {
"down": {"id": "E4_stop_down", "lane": "E3E4", "pos": 460},
"up": {"id": "E4_stop_up", "lane": "E5E4", "pos": 460}
},
"E7": {
"down": {"id": "E7_stop_down", "lane": "E6E7", "pos": 460},
"up": {"id": "E7_stop_up", "lane": "E8E7", "pos": 460}
},
"C4": {
"down": {"id": "C4_stop_down", "lane": "B4C4", "pos": 460},
"up": {"id": "C4_stop_up", "lane": "D4C4", "pos": 460}
},
"H4": {
"down": {"id": "E7_stop_down", "lane": "G4H4", "pos": 460},
"up": {"id": "E7_stop_up", "lane": "I4H4", "pos": 460}
}
}
def get_lambda(current_minute):
"""Return pedestrian spawn rate based on scaled day cycle (120 minutes total)"""
if 0 <= current_minute < 56:
return 6 # Night
elif 56 <= current_minute < 86:
return 15 # Off-peak
elif 86 <= current_minute < 116:
return 30 # Peak
elif 116 <= current_minute < 160:
return 13 # Off-peak
elif 160 <= current_minute < 206:
return 30 # Peak
elif 206 <= current_minute <= 240:
return 13 # Off-peak
else:
return 15 # Fallback default
def get_random_route(from_edge, to_stop):
"""Get a valid walking route to a train stop"""
try:
from_edge_obj = net.getEdge(from_edge)
to_edge_obj = net.getEdge(to_stop["lane"].split('_')[0])
route = net.getShortestPath(from_edge_obj, to_edge_obj)[0]
if route:
return [edge.getID() for edge in route]
return None
except:
return None
def get_junction_coordinates(net_file):
"""
Extract junction coordinates from SUMO .net.xml file.
Returns a dictionary of {junction_id: (x, y)}.
"""
net = sumolib.net.readNet(net_file)
junctions = {
j.getID(): (j.getCoord()[0], j.getCoord()[1])
for j in net.getNodes()
if not j.getID().startswith(":") # Ignore internal junctions
}
return junctions
def find_center_junction(junctions):
"""
Find junction closest to the geometric center (centroid).
"""
coords = np.array(list(junctions.values()))
centroid = np.mean(coords, axis=0)
min_dist = float("inf")
center_junction = None
for junction_id, (x, y) in junctions.items():
dist = np.hypot(x - centroid[0], y - centroid[1])
if dist < min_dist:
min_dist = dist
center_junction = junction_id
return center_junction
def compute_attractiveness(junctions, center_junction, peak_lambda=30, decay_beta=0.0005):
"""
Compute attractiveness values for all junctions using exponential decay.
"""
center_x, center_y = junctions[center_junction]
attractiveness = {}
for junction_id, (x, y) in junctions.items():
dist = np.hypot(x - center_x, y - center_y)
lam = peak_lambda * np.exp(-decay_beta * dist)
attractiveness[junction_id] = lam
return attractiveness
def initialize_attractiveness():
"""
Initialize attractiveness map by reading junctions from SUMO network file.
Assumes 'grid.net.xml' is in current directory.
Returns: {junction_id: attractiveness λ}
"""
net_file = "grid.net.xml" # Path to the network file
junctions = get_junction_coordinates(net_file)
center_junction = find_center_junction(junctions)
attractiveness_map = compute_attractiveness(junctions, center_junction)
return attractiveness_map
# Example usage:
attractiveness_map = initialize_attractiveness()
# Preview top 5 most attractive junctions
for junction, lam in sorted(attractiveness_map.items(), key=lambda x: -x[1])[:5]:
print(f"Junction: {junction}, Attractiveness: {lam:.2f}")
def get_random_destination():
station = random.choice(list(train_stops.keys()))
direction = random.choice(["up", "down"])
return train_stops[station][direction]
def is_valid_route(route):
"""Check if route pattern is valid for taxis"""
route_ids = [edge.getID() for edge in route]
# Check consecutive edges
for i in range(len(route_ids) - 1):
current = route_ids[i]
next_edge = route_ids[i + 1]
# Skip if edges don't match AxAy pattern
if (len(current) != 4 or len(next_edge) != 4 or
not (current.startswith('A') and next_edge.startswith('A'))):
continue
# Check if edges are reciprocal (A1A2 followed by A2A1)
if (current[1] == next_edge[3] and
current[3] == next_edge[1] and
current[2] == next_edge[2]):
return False
return True
# def change_person_destination(person_id, new_lane):
# try:
# # Get current stage
# stage = traci.person.getStage(person_id)
# if stage.type != traci.constants.STAGE_DRIVING:
# print(f"[Skip] Person {person_id} is not in a driving stage.")
# return
# vehicle_id = traci.person.getVehicle(person_id)
# # if not vehicle_id:
# # return
# # route = stage.edges # list of edges
# # Find current pickup edge
# current_edge = traci.person.getRoadID(person_id)
# if not current_edge:
# print(f"[Error] Could not get current road for {person_id}")
# return
# # Rebuild a new driving stage to the new lane
# # You must end the current stage and append a new one
# # Clear current stages: remove person from vehicle
# traci.person.removeStages(person_id)
# traci.person.appendDrivingStage(person_id, new_lane, "taxi")
# print(f"[Updated] Person {person_id}'s destination changed to lane {new_lane}")
# except Exception as e:
# print(f"[Error] Failed to change destination for {person_id}: {e}")
def spawn_pedestrians(step):
"""
Spawn pedestrians with higher probability near attractive junctions.
"""
global ped_id_counter, attractiveness_map
current_min = step // 60
base_lambda = get_lambda(current_min)
# print(f"\n[Pedestrian Spawn] Step={step}, Minute={current_min}, Base λ={base_lambda}")
for edge in edge_ids:
# Get the "from" junction of this edge
from_junction = edge[:2] # e.g., 'C3C4' -> 'C3'
# Get attractiveness-modified spawn rate
local_lambda = attractiveness_map.get(from_junction, base_lambda) / 100.0
num_peds = np.random.poisson(local_lambda)
for _ in range(num_peds):
destination = get_random_destination()
route = get_random_route(edge, destination)
if not route:
continue
ped_id = f"ped_{ped_id_counter}"
ped_id_counter += 1
try:
# Random position along edge
pos = random.randint(30, 450)
# Add person to simulation
traci.person.add(ped_id, edge, depart=step, pos=pos)
# Calculate best arrival position
distances = {
20: abs(pos - 20),
255: abs(pos - 255),
480: abs(pos - 480)
}
arrival_pos = min(distances, key=distances.get)
# Add walking stage
traci.person.appendWalkingStage(
ped_id,
route[0],
arrivalPos=arrival_pos
)
# Add taxi stage
traci.person.appendDrivingStage(
ped_id,
destination["lane"],
lines="taxi"
)
# print(f" Spawned pedestrian {ped_id} at {edge} (pos={pos})")
except Exception as e:
print(f"Error spawning pedestrian: {str(e)}")
def get_all_shortest_routes(net_file, source_edge_id, destination_edge_id):
net = sumolib.net.readNet(net_file)
G = nx.DiGraph()
for edge in net.getEdges():
if edge.isSpecial():
continue
for succ in edge.getOutgoing():
if not succ.isSpecial():
G.add_edge(edge.getID(), succ.getID(), weight=edge.getLength())
if source_edge_id not in G or destination_edge_id not in G:
return []
try:
shortest_length = nx.shortest_path_length(G, source=source_edge_id, target=destination_edge_id)
except nx.NetworkXNoPath:
return []
all_shortest_paths = list(nx.all_simple_paths(G, source=source_edge_id, target=destination_edge_id, cutoff=shortest_length))
shortest_routes = [path for path in all_shortest_paths if len(path) - 1 == shortest_length]
return shortest_routes
# Add this at the top of your file with other global variables
# Add at the top with other global variables
taxi_assignments = {}
dispatched_taxis = {}
assigned_reservations = set()
# Add these new global variables at the top with other globals
taxi_current_passengers = {} # Store current passengers in each taxi
taxi_capacity = 4 # Maximum passengers per taxi
taxi_routes = {} # Store current routes of taxis
taxi_id_counter = 0
def decide_next_junction_for_idle_taxi(taxi_id, net, attractiveness_map, damping_factor=0.8):
try:
current_edge_id = traci.vehicle.getRoadID(taxi_id)
# Skip internal lanes (junctions like ":C3_0")
if current_edge_id.startswith(":"):
# print(f"[Skip] Taxi {taxi_id} is currently in internal junction: {current_edge_id}")
return
current_edge = net.getEdge(current_edge_id)
from_node = current_edge.getToNode() # The junction this edge leads to
outgoing_edges = from_node.getOutgoing()
if not outgoing_edges:
# print(f"[Dead End] No outgoing edges from junction {from_node.getID()} for taxi {taxi_id}")
return
# print(f"\n[Routing] Taxi {taxi_id} at edge {current_edge_id} reaching junction {from_node.getID()}")
# print(f"→ Evaluating outgoing edges from junction {from_node.getID()}:")
# Compute attractiveness scores
edge_utilities = {}
total = 0
for edge in outgoing_edges:
to_junction = edge.getToNode().getID()
attractiveness = attractiveness_map.get(to_junction, 0)
utility = damping_factor * attractiveness
edge_utilities[edge.getID()] = utility
total += utility
# print(f" - Edge {edge.getID()} leads to junction {to_junction} with attractiveness {attractiveness:.3f} → utility {utility:.3f}")
cumulative = []
acc = 0
for edge_id, utility in edge_utilities.items():
prob = utility / total
acc += prob
cumulative.append((edge_id, acc))
r = np.random.uniform(0, 1)
# print(f"[Prob] Random value: {r:.4f}")
selected_edge_id = None
for edge_id, upper_bound in cumulative:
if r <= upper_bound:
selected_edge_id = edge_id
break
if selected_edge_id is None:
# print("[Error] No edge selected — check distribution logic.")
return
path_edges = net.getShortestPath(net.getEdge(current_edge_id), net.getEdge(selected_edge_id))[0]
if path_edges is None:
# print(f"[Warning] No path found from {current_edge_id} to {selected_edge_id}")
return None
route = [edge.getID() for edge in path_edges]
traci.vehicle.setRoute(taxi_id, route)
# print(f"[Selected] Probabilistically chose edge {selected_edge_id} → junction {net.getEdge(selected_edge_id).getToNode().getID()}")
return selected_edge_id
except Exception as e:
print(f"[Error] While routing taxi {taxi_id}: {e}")
# dispatch_logs = {}
# LOG_FILENAME = "taxi_dispatch_log.txt"
taxi_destinations = {} # taxi_id → fixed destination edge
taxi_diversions = {} # taxi_id → current diversion count
taxi_paths = {} # taxi_id → list of shortest paths (list of lists)
def controlled_routing_after_onboarding(taxi_id):
"""
After onboarding, control the taxi's route:
- Follow probabilistic routing, but allow max 2 diversions from any shortest path.
- If 2 diversions are taken, let taxi route itself (do not override).
"""
global taxi_destinations, taxi_diversions, taxi_paths
# If taxi is not dispatched or has no destination, skip
if taxi_id not in taxi_destinations or taxi_id not in taxi_paths:
return
# If taxi has already reached destination, skip
current_edge = traci.vehicle.getRoadID(taxi_id)
dest_edge = taxi_destinations[taxi_id]
if current_edge == dest_edge:
return
# If max diversions reached, let taxi route itself
if taxi_diversions.get(taxi_id, 0) >= 2:
return
# Get all shortest routes for this taxi
shortest_routes = taxi_paths[taxi_id]
if not shortest_routes:
return
# Find all possible next edges from current position in any shortest route
possible_next_edges = set()
for route in shortest_routes:
if current_edge in route:
idx = route.index(current_edge)
if idx < len(route) - 1:
possible_next_edges.add(route[idx + 1])
# Probabilistically select next edge
next_edge = decide_next_junction_for_idle_taxi(taxi_id, net, attractiveness_map)
if not next_edge:
return
# Check if next_edge is a valid next step in any shortest route
if next_edge in possible_next_edges:
# Not a diversion, continue
traci.vehicle.setRoute(taxi_id, [current_edge, next_edge])
else:
# Diversion taken
taxi_diversions[taxi_id] = taxi_diversions.get(taxi_id, 0) + 1
traci.vehicle.setRoute(taxi_id, [current_edge, next_edge])
print(f"[Taxi {taxi_id}] Diversion taken! Total diversions: {taxi_diversions[taxi_id]}")
if taxi_diversions[taxi_id] >= 2:
print(f"[Taxi {taxi_id}] Max diversions reached. Letting taxi route itself.")
def manage_taxi_fleet(step):
global dispatched_taxis, dispatch_logs, net, attractiveness_map, assigned_reservations, taxi_assignments, taxi_current_passengers, taxi_routes
global taxi_destinations, taxi_diversions, taxi_paths, taxi_capacity
# Get current reservations and available taxis
reservations = [r for r in traci.person.getTaxiReservations(0) if r.id not in assigned_reservations]
fleet = list(traci.vehicle.getTaxiFleet(0))
fleet = [taxi for taxi in fleet if taxi not in dispatched_taxis]
available_taxis = [taxi for taxi in fleet if taxi not in dispatched_taxis]
if not reservations or not fleet:
return
# Group reservations by (from_edge, destination_lane)
grouped = {}
res_map = {}
for res in reservations:
# print(res)
person_id = res.persons[0]
person_info = traci.person.getStage(person_id)
# print(person_info)
to_junction = person_info.edges[1][2:4]
group_key = (person_info.edges[0], to_junction)
res_map[res.id] = res
if group_key not in grouped:
grouped[group_key] = []
# Add reservation id to the last sublist if not full, else start a new sublist
if not grouped[group_key] or len(grouped[group_key][-1]) >= taxi_capacity:
grouped[group_key].append([])
grouped[group_key][-1].append(res.id)
assigned_taxis = 0
from math import dist
for taxi_id in fleet[:]:
try:
if traci.vehicle.getPersonNumber(taxi_id) > 0:
continue # Already onboarded someone
current_edge = traci.vehicle.getRoadID(taxi_id)
taxi_pos = traci.vehicle.getPosition(taxi_id)
next_edge = decide_next_junction_for_idle_taxi(taxi_id, net, attractiveness_map)
# STEP 1: Try to onboard on CURRENT EDGE using distance > 5m
onboarded_reservations = []
dest_junction = None
for res in reservations:
if res.id in assigned_reservations:
continue
person_id = res.persons[0]
person_info = traci.person.getStage(person_id)
if person_info.edges[0] != current_edge:
continue
person_pos = traci.person.getPosition(person_id)
if dist(taxi_pos, person_pos) <= 5:
continue # Too close; skip this passenger
# First person defines the destination
if not dest_junction:
dest_junction = person_info.edges[1][2:4]
group_key = (current_edge, dest_junction)
# Allow only same-destination passengers
if person_info.edges[1][2:4] == dest_junction:
if len(onboarded_reservations) >= taxi_capacity:
break
onboarded_reservations.append(res.id)
if onboarded_reservations:
dispatch_list = onboarded_reservations + onboarded_reservations
traci.vehicle.dispatchTaxi(taxi_id, dispatch_list)
dispatched_taxis[taxi_id] = onboarded_reservations.copy()
taxi_current_passengers[taxi_id] = onboarded_reservations.copy()
for rid in onboarded_reservations:
assigned_reservations.add(rid)
available_taxis.remove(taxi_id)
destination_edge = person_info.edges[1]
shortest_routes = get_all_shortest_routes("grid.net.xml", current_edge, destination_edge)
taxi_destinations[taxi_id] = destination_edge
taxi_paths[taxi_id] = shortest_routes
taxi_diversions[taxi_id] = 0
assigned_taxis += 1
print(f"[Step {step}] Taxi {taxi_id} onboarded from current edge: {onboarded_reservations}")
continue # Taxi is now busy, skip to next
# STEP 2: Try to onboard on NEXT EDGE
if not next_edge:
continue
# Find nearest reservation on that edge
edge_reservations = []
for res in reservations:
if res.id in assigned_reservations:
continue
person_id = res.persons[0]
person_info = traci.person.getStage(person_id)
if person_info.edges[0] != next_edge:
continue
edge_reservations.append((res, res.departPos))
if not edge_reservations:
continue
# Sort by departPos (nearest first)
edge_reservations.sort(key=lambda x: x[1])
base_res = edge_reservations[0][0]
base_person_info = traci.person.getStage(base_res.persons[0])
dest_junction = base_person_info.edges[1][2:4]
group_key = (next_edge, dest_junction)
# Gather rest of group with same destination
onboarded_group = []
for res, _ in edge_reservations:
if res.id in assigned_reservations:
continue
person_info = traci.person.getStage(res.persons[0])
if person_info.edges[1][2:4] == dest_junction:
if len(onboarded_reservations) >= taxi_capacity:
break
onboarded_group.append(res.id)
if len(onboarded_group) >= taxi_capacity:
break
if onboarded_group:
dispatch_list = onboarded_group + onboarded_group
traci.vehicle.dispatchTaxi(taxi_id, dispatch_list)
dispatched_taxis[taxi_id] = onboarded_group.copy()
taxi_current_passengers[taxi_id] = onboarded_group.copy()
for rid in onboarded_group:
assigned_reservations.add(rid)
available_taxis.remove(taxi_id)
destination_edge = person_info.edges[1]
shortest_routes = get_all_shortest_routes("grid.net.xml", current_edge, destination_edge)
taxi_destinations[taxi_id] = destination_edge
taxi_paths[taxi_id] = shortest_routes
taxi_diversions[taxi_id] = 0
assigned_taxis += 1
print(f"[Step {step}] Taxi {taxi_id} onboarded from next edge: {onboarded_group}")
except traci.TraCIException as e:
print(f"[Error] Failed to manage taxi {taxi_id}: {e}")
continue
if assigned_taxis == 0:
print(f"[Step {step}] No taxis dispatched this round.")
for taxi_id in list(dispatched_taxis.keys()):
try:
controlled_routing_after_onboarding(taxi_id)
except traci.TraCIException as e:
print(f"[Error] Failed to process taxi {taxi_id}: {e}")
continue
# Reset taxis that finished all trips
# reset_taxi_after_dropoff()
# for taxi_id in fleet:
# if not grouped:
# break
# selected_group = None
# for key in grouped:
# if key not in assigned_keys and len(grouped[key]) > 0:
# selected_group = key
# break
# if not selected_group:
# break
# group_lists = grouped[selected_group]
# assigned_keys.add(selected_group)
# for sublist in group_lists:
# if not sublist:
# continue
# # Mark reservations as assigned
# for pid in sublist:
# assigned_reservations.add(pid)
# used_reservations.add(pid)
# # Dispatch taxi for this sublist (repeat list for pickup/drop)
# dispatch_list = sublist + sublist
# try:
# traci.vehicle.dispatchTaxi(taxi_id, dispatch_list)
# dispatched_taxis[taxi_id] = sublist.copy()
# taxi_current_passengers[taxi_id] = sublist.copy()
# # Move to next taxi for next sublist
# break
# except traci.TraCIException as e:
# print(f"Dispatch error for taxi {taxi_id}: {e}")
# continue
def spawn_taxis(step):
"""
Spawn taxis with higher probability near attractive junctions.
Taxis are more likely to spawn in areas with high pedestrian demand.
"""
global taxi_id_counter, attractiveness_map
current_min = step // 60
base_lambda = get_lambda(current_min) / 4 # Divide by 4 to maintain reasonable taxi numbers
# total_attr = sum(attractiveness_map.get(edge[:2], 0) for edge in edge_ids)
# if total_attr == 0:
# total_attr = 1
# print(f"\n[Taxi Spawn] Step={step}, Minute={current_min}, Base λ={base_lambda}")
for edge in edge_ids:
# Get the origin junction of this edge
from_junction = edge[:2] # e.g., 'C3C4' -> 'C3'
local_lambda = attractiveness_map.get(from_junction, base_lambda) / 400.0
num_taxis = np.random.poisson(local_lambda)
for _ in range(num_taxis):
taxi_id = f"taxi_{taxi_id_counter}"
try:
# Find a valid route from current edge
# Using a major junction (like E4) as common destination for initial routing
destination_edge = "E4E5" # Can be adjusted based on your network
route = net.getShortestPath(net.getEdge(edge), net.getEdge(destination_edge))[0]
if route and is_valid_route(route):
route_edges = [e.getID() for e in route]
# Limit initial route length
initial_route = route_edges[:5]
# Create and add route
traci.route.add(f"route_{taxi_id}",initial_route)
# Add taxi to simulation
traci.vehicle.add(
vehID=taxi_id,
routeID=f"route_{taxi_id}",
typeID="taxi",
depart=step
)
# traci.vehicle.setSpeed(taxi_id,)
# Initialize taxi state tracking
taxi_routes[taxi_id] = initial_route
taxi_current_passengers[taxi_id] = []
# print(f"→ Taxi {taxi_id} spawned at {edge} (λ={local_lambda:.2f})")
# print(f" Initial route: {' → '.join(initial_route)}")
taxi_id_counter += 1
except traci.TraCIException as e:
print(f"Error spawning taxi {taxi_id}: {e}")
continue
def remove_empty_taxis(step):
"""Remove empty taxis when lambda is 13"""
current_min = step // 60
lam = get_lambda(current_min)
if lam == 13:
fleet = list(traci.vehicle.getTaxiFleet(0))
empty_taxis = [tid for tid in fleet if tid not in taxi_current_passengers]
empty_taxis.sort(key=lambda x: int(x.split('_')[1]), reverse=True)
to_remove = min(lam, len(empty_taxis))
print(f"\n=== Removing Empty Taxis at step {step} ===")
print(f"Found {len(empty_taxis)} empty taxis")
print(f"Attempting to remove {to_remove} taxis")
removed = 0
for i in range(to_remove):
taxi_id = empty_taxis[i]
try:
traci.vehicle.remove(taxi_id, reason=2)
if taxi_id in taxi_routes:
del taxi_routes[taxi_id]
removed += 1
print(f"Removed empty taxi {taxi_id}")
except traci.TraCIException as e:
print(f"Error removing taxi {taxi_id}: {e}")
print(f"Successfully removed {removed} empty taxis")