Files
SRS_evac_sim/archive/prompt_full-run-sim1.txt
Varyngoth 676659e5b9 First
2026-01-28 13:31:49 -04:00

558 lines
19 KiB
Plaintext

I'm creating a simulation using JuPedSim to see how pedestrians interact with a crosswalk system. The crosswalk system will have a user-defined "active" interval, and will be activated by agents being within a predefined distance. All agents will spawn in a specified spawn area, and are attempting to get from one side of the road to the other. They may only use the crosswalk to cross, and can only cross when the crosswalk is "active". Below are provided 4 separate files to create the JuPedSim simulation model:
============================
(1.1)
============================
=>geometry_setup.py(file):
class GeometrySetup(current_setup:bool)
spawn:defines the Polygon for the spawn_area
queue:defines the Polygon for the queue_area
trigger:defines the Polygon for the trigger_area
crosswalk:defines the Polygon for the crosswalk_area
grass:defines the Polygon for the grass_area
============================
(1.2)
============================
=>spawning.py(file):
class SpawnManager(spawn_area:Polygon,min_spacing:float):
__init__:
spawn_area: area for valid spawns
min_spacing: minimum spacing between spawn points
spawn_coords: all coordinates for spawning agents
filled_coords: spawn points currently occupied by agents
spawned_agents: set of agent ids already spawned
agent_pos: dictionary which connects agent ids to a spawn point
generate_coords:
uses Poisson disk method with CDTree from scipy.spatial for all spawn points
get_coords:
provides a tuple[float,float] or None based on if there is room to spawn agents
spawn_agent:
provides AgentSetup object or None depending on if there is room to spawn and there are agents left to spawn
unfill_coords:
returns None, used to indicate a spawn point has been made available by an agent moving
get_agent_pos;
returns (0,0) or the spawn coordinates of agents if they are in agent_pos
check_spawn_complete:
checks whether the number of agents spawned is the same as all_agents list, and if all spawn points are empty.
============================
(1.3)
============================
=>agents(folder):
|>agent_data.py(file):
| dictionary with group keys and values
|>agent_setup.py(file):
defines the AgentSetup class and computes list of all_agents:
"""
@dataclass
class AgentSetup:
id:int
grade:str
speed:float
radius:float
"""
============================
(1.4)
============================
=>crosswalk_setup.py(file):
class CrosswalkStatus(Enum):
INACTIVE
ACTIVE
COOLDOWN
@dataclass(frozen=True)
class CrosswalkConfig:
trigger_dist:float
activation_time:float
cooldown_time:float
min_agents_activation:int
activation_delay:float
crosswalk_area:Polygon
trigger_area:Polygon
@dataclass
class CrosswalkState:
status:CrosswalkStatus
is_active:bool
time_active:float
time_remaining:float
agents_in_trigger:int
agents_waiting:int
activation_count:int
class CrosswalkController:
__init__(self,config:CrosswalkConfig,current_setup:bool):
update: calls the following methods, then returns get_state()
update_agents_in_trigger
update_state
update_statistics
update_agents_in_trigger: updates what agents are in the trigger_area for the crosswalk
@staticmethod
points_in_area:finding what points lie in a given area
update_state:updates crosswalk state to be COOLDOWN,ACTIVE or INACTIVE based on specified parameters
activate:changes the system to the ACTIVE state
deactivate:changes the system to the COOLDOWN state
update_statistics:updates info on crosswalks if status is ACTIVE
get_state: provides the state based on the current time in relation to the activation time, then updates the system
can_agent_cross: checks whether ACTIVE or INACTIVE to either let agent pass or add agent to waiting group
@property
is_active: provides a boolean response whether the system is ACTIVE or not
get_statistics:provides stats from the system at the current time
============
here is the code:
============================
(2.1) (geometry_setup.py)
============================
import sys
import matplotlib
matplotlib.use('QtAgg')
import matplotlib.pyplot as plt
from shapely import Polygon
from shapely.plotting import plot_polygon
class GeometrySetup:
def __init__(self,current_setup:bool):
self.current_setup = current_setup
self.spawn()
self.queue()
self.trigger()
self.crosswalk()
self.grass()
def spawn(self)->Polygon:
spawn_area = Polygon([
(25.0,16.823),
(25.0,4.569),
(26.163,4.569),
(28.214,5.07),
(28.214,17.761),
(25.787,17.761),
])
plot_polygon(spawn_area,color="purple",add_points=False)
return spawn_area
def queue(self)->Polygon:
if self.current_setup is True:
queue_area = Polygon([
(18.767,9.395),
(16.924,9.395),
(16.924,4.569),
(25.0,4.569),
(25.0,16.823)
])
else:
queue_area = Polygon([
(19.531,10.306),
(15.214,10.306),
(15.214,0),
(16.924,0),
(16.924,4.569),
(25.0,4.569),
(25.0,16.823)
])
plot_polygon(queue_area,color="blue",add_points=False)
return queue_area
def trigger(self)->Polygon:
if self.current_setup is True:
trigger_area = Polygon([
(15.214,0),
(15.214,9.395),
(16.924,9.395),
(16.924,0)
])
else:
trigger_area = Polygon([
(15.214,10.306),
(15.214,0),
(12.98,0),
(12.98,9.896),
(13.88,10.306)
])
plot_polygon(trigger_area,color="green",add_points=False)
return trigger_area
def crosswalk(self)->Polygon:
if self.current_setup is True:
crosswalk_area = Polygon([
(4,6.068),
(4,7.896),
(4,7.896),
(15.214,7.896),
(15.214,6.068),
(4,6.068)
])
else:
crosswalk_area = Polygon([
(6.23,4.982),
(6.23,8.982),
(12.98,8.982),
(12.98,4.982)
])
plot_polygon(crosswalk_area,color="red",add_points=False)
return crosswalk_area
def grass(self)->Polygon:
if self.current_setup is True:
grass_area = Polygon([
(4,0),
(0,0),
(0,17.761),
(4,17.761)
])
else:
grass_area = Polygon([
(6.23,0),
(0,0),
(0,17.761),
(4,17.761),
(4,10.306),
(5.33,10.306),
(6.23,9.896)
])
plot_polygon(grass_area,color="blue",add_points=False)
return grass_area
if __name__ == "__main__":
from PyQt6 import QtWidgets
app = QtWidgets.QApplication(sys.argv)
geo_map = GeometrySetup(False)
plt.show(block=False)
sys.exit(app.exec())
============================
(2.2) (spawning.py)
============================
from shapely.geometry import Polygon,Point
from typing import Tuple, List, Dict, Set
from scipy.spatial import cKDTree
import sys
import math
import numpy as np
import config
sys.path.insert(0,str(config.AGENTS_DIR))
from agents.agent_setup import AgentSetup
class SpawnManager:
def __init__(self,spawn_area:Polygon,min_spacing:float=0.55):
"""
self.spawn_area: geometry where agents may spawn
self.min_spacing: minimum spacing for spawn_points
self.spawn_coords: all spawn points available
self.filled_coords: spawn points currently filled by agents
self.spawned_agents: all agents already spawned in sim
self.agent_pos: connects agent_id to spawn_index
self.rng: random number generator object
"""
self.spawn_area = spawn_area
self.min_spacing = min_spacing
self.spawn_coords: np.ndarray = np.array([])
self.filled_coords: np.ndarray = np.array([])
self.spawned_agents: set = set()
self.agent_pos:dict={}
self.rng = np.random.default_rng()
def generate_coords(self,max_samples:int=1000)->None:
min_x,min_y,max_x,max_y = self.spawn_area.bounds
points = []
while len(points) ==0:
x = self.rng.uniform(min_x,max_x)
y = self.rng.uniform(min_y,max_y)
if self.spawn_area.contains(Point(x,y)):
points.append([x,y])
for _ in range(max_samples):
idx = self.rng.integers(0,len(points),endpoint=True)
base = points[idx]
for _ in range(25):
angle = self.rng.uniform(0,2*np.pi)
radius = self.rng.uniform(self.min_spacing,2*self.min_spacing)
x = base[0]+radius*np.cos(angle)
y = base[1]+radius*np.sin(angle)
if not self.spawn_area.contains(Point(x,y)):
continue
if len(points)>0:
tree = cKDTree(points)
distance,_ = tree.query([[x,y]],k=1)
if distance[0]<self.min_spacing:
continue
points.append([x,y])
break
self.spawn_coords = np.array(points)
self.filled_coords = np.zeros(len(points),dtype=bool)
def get_coords(self)->Tuple[float,float]|None:
'''
points = [
pt for pt in self.generate_coords if pt \
not in self.filled_coords.values()
]
return self.rng.choice(points) if points else None
'''
free_idx = np.where(~self.filled_coords)[0]
if len(free_idx) == 0:
return None
idx = self.rng.choice(free_idx)
return tuple(self.spawn_coords[idx])
def spawn_agent(self,all_agents:List[AgentSetup])->AgentSetup|None:
if len(self.spawned_agents) >= len(all_agents):
return None
spawn_point = self.get_coords()
if not spawn_point:
return None
free_agents = [
agent for agent in all_agents \
if agent.id not in self.spawned_agents
]
if not free_agents:
return None
agent = self.rng.choice(free_agents)
self.spawned_agents.add(agent.id)
distances = np.linalg.norm(self.spawn_coords-spawn_point,axis=1)
spawn_idx = np.argmin(distances)
self.filled_coords[spawn_idx] = True
self.agent_pos[agent.id] = spawn_idx
return agent
def unfill_coords(self,agent_id:int)->None:
if agent_id in self.agent_pos:
spawn_idx = self.agent_pos[agent_id]
self.filled_coords[spawn_idx] = False
del self.agent_pos[agent_id]
self.spawned_agents.discard(agent_id)
def get_agent_pos(self,agent_id:int)->Tuple[float,float]:
if agent_id in self.agent_pos:
spawn_idx = self.agent_pos[agent_id]
return tuple(self.spawn_coords[spawn_idx])
return (0,0)
def check_spawn_complete(self,all_agents:List[AgentSetup])->bool:
return (len(self.spawned_agents)<len(all_agents) and\
np.sum(~self.filled_coords) > 0)
============================
(2.3) (agent_setup.py)
============================
from agent_data import grade_data
from dataclasses import dataclass
import numpy as np
from typing import List
@dataclass
class AgentSetup:
id:int
grade:str
speed:float
radius:float
def AgentConfig()->List[List[AgentSetup],List[int]]:
agent_id = 1
pop_list = []
all_agents = []
for idx,key in enumerate(grade_data.keys()):
grade = grade_data[key]
pop_list.append(grade["Pop Current"])
for i in range(pop_list[idx]):
rng = np.random.default_rng()
agent = AgentSetup(
id=agent_id,
grade=key,
speed=rng.normal(
grade["Speed Mean"],
grade["Speed Std Dev"]
),
radius=grade["Radius"]
)
all_agents.append(agent)
agent_id +=1
return all_agents, pop_list
============================
(2.4) (crosswalk_setup.py)
============================
from dataclasses import dataclass,field
from enum import Enum
from typing import List,Dict,Tuple,Set
from shapely import Polygon,Point
from geometry_setup import GeometrySetup
import shapely
import numpy as np
class CrosswalkStatus(Enum):
INACTIVE = "inactive"
ACTIVE = "active"
COOLDOWN = "cooldown"
@dataclass(frozen=True)
class CrosswalkConfig:
trigger_dist:float
activation_time:float
cooldown_time:float
min_agents_activation:int
activation_delay:float
crosswalk_area:Polygon
trigger_area:Polygon
@dataclass
class CrosswalkState:
status:CrosswalkStatus
is_active:bool
time_active:float
time_remaining:float
agents_in_trigger:int
agents_waiting:int
activation_count:int
class CrosswalkController:
def __init__(self,config:CrosswalkConfig,current_setup:bool):
self.config = config
self.crosswalk_area = config.crosswalk_area
self.trigger_area = config.trigger_area
self.status = CrosswalkStatus.INACTIVE
self.state_start_time = 0.0
self.current_time = 0.0
self.activation_count = 0
self.agents_in_trigger: Set[int] = set()
self.agents_waiting: Set[int] = set()
self.total_active_time = 0.0
self.agents_served = 0
def update(
self,
agent_pos:Dict[int,Tuple[float,float]],
time_step:float,
current_time:float=None
)->CrosswalkState:
if current_time is not None:
self.current_time = current_time
else:
self.current_time +=time_step
self.update_agents_in_trigger(agent_pos)
self.update_state(time_step)
self.update_statistics(time_step)
return self.get_state()
def update_agents_in_trigger(
self,
agent_pos:Dict[int,Tuple[float,float]]
)->None:
self.agents_in_trigger.clear()
if not agent_pos:
return
agent_ids = list(agent_pos.keys())
positions = np.array(list(agent_pos.values()),dtype=np.float32)
in_trigger = self.points_in_area(positions,self.trigger_area)
for i, agent_id in enumerate(agent_ids):
if in_trigger[i]:
self.agents_in_trigger.add(agent_id)
@staticmethod
def points_in_area(points:np.ndarray,polygon:Polygon)->np.ndarray:
if len(points) ==0:
return np.array([],dtype=bool)
x,y = points[:,0],points[:,1]
vertices = shapely.get_coordinates(polygon)
n = len(vertices)
inside = np.zeros(len(points),dtype=bool)
j = n-1
for i in range(n):
xi, yi = vertices[i]
xj, yj = vertices[j]
mask = ((yi>y) != (yj>y)) & (x<(xj-xi)*(y-yi)/(yj-yi)+xi)
inside ^= mask
j = i
return inside
def update_state(self,time_step:float)->None:
elapsed = self.current_time - self.state_start_time
if self.status == CrosswalkStatus.ACTIVE:
if elapsed >= self.config.activation_time:
self.deactivate()
elif self.status == CrosswalkStatus.COOLDOWN:
if elapsed >= self.config.cooldown_time:
self.status = CrosswalkStatus.INACTIVE
self.state_start_time = self.current_time
elif self.status ==CrosswalkStatus.INACTIVE:
if (len(self.agents_in_trigger)>=self.config.min_agents_activation and \
elapsed >= self.config.activation_delay):
self.activate()
def activate(self)->None:
self.status = CrosswalkStatus.ACTIVE
self.state_start_time = self.current_time
self.activation_count +=1
self.agents_served += len(self.agents_waiting)
self.agents_waiting.clear()
def deactivate(self)->None:
self.status = CrosswalkStatus.COOLDOWN
self.state_start_time = self.current_time
def update_statistics(self,time_step:float)->None:
if self.status == CrosswalkStatus.ACTIVE:
self.total_active_time += time_step
def get_state(self)->CrosswalkState:
elapsed = self.current_time-self.state_start_time
if self.status == CrosswalkStatus.ACTIVE:
time_remaining = max(0.0,self.config.activation_time-elapsed)
elif self.status == CrosswalkStatus.COOLDOWN:
time_remaining = max(0.0,self.config.cooldown_duration-elapsed)
else:
time_remaining = 0.0
return CrosswalkState(
status=self.status,
is_active=(self.status == CrosswalkStatus.ACTIVE),
time_active=elapsed if self.status == CrosswalkStatus.ACTIVE else 0.0,
time_remaining=time_remaining,
agents_in_trigger=len(self.agents_in_trigger),
agents_waiting=len(self.agents_waiting),
activation_count = self.activation_count
)
def can_agent_cross(self,agent_id:int)->bool:
if self.status == CrosswalkStatus.ACTIVE:
return True
else:
if agent_id in self.agents_in_trigger:
self.agents_waiting.add(agent_id)
return False
@property
def is_active(self)->bool:
return self.status == CrosswalkStatus.ACTIVE
def get_statistics(self)->Dict[str,float]:
return {
"total_activations":self.activation_count,
"total_active_time":self.total_active_time,
"agents_served":self.agents_served,
"current_agents_in_trigger":len(self.agents_in_trigger),
"current_agents_waiting":len(self.agents_waiting)
}
=========================
The last part which needs to be done is using the JuPedSim module to create a full simulation using all these pieces;