Product Requirements Document: Crowd Control
Documents a controller-based path expansion feature that lets designated nodes dynamically reroute pedestrians around congestion during LTM simulations.
What this file does
Documents a controller-based path expansion feature that lets designated nodes dynamically reroute pedestrians around congestion during LTM simulations.
When to use it
- You want to add dynamic rerouting to a pedestrian traffic simulation
- Your simulation suffers from stuck traffic when primary paths become blocked
- You need time-based activation of rerouting at specific intersections
- You are extending an LTM-based simulation with adaptive route choice
Assumes this stack
Product Requirements Document: Crowd Control
1. Introduction
PedNStream is a pedestrian traffic simulation tool based on the Link Transmission Model (LTM). It is designed to model and simulate the movement of pedestrians through a network of interconnected links and nodes, representing pathways and decision points. The simulation incorporates traffic dynamics to provide insights into pedestrian flow behavior under various conditions. And it can be wraped up as a training environment for Reinforcement Learning.
2. Goals
This project aims to achieve the following:
- Accurately simulate pedestrian flow dynamics within a configurable network environment.
- Model key elements of pedestrian movement, such as link capacities, travel speeds, densities, and queue formation.
- Support various demand patterns for origin-destination (OD) flows to reflect real-world scenarios.
- Enable pathfinding capabilities to determine pedestrian routes through the network.
- Provide visualization tools to analyze simulation outputs, including network state, pedestrian densities, and flow rates over time.
3. Target Audience
The simulation tool is built for pedestrian traffic practitioner.
4. Features
4.1. Implemented Features
The Crowd Control project currently includes the following implemented features:
- Core Simulation Engine (
src/LTM/network.py):- Manages the overall simulation network, including initialization of network components (nodes and links).
- Executes the simulation on a step-by-step basis, updating network states over time.
- Link Module (
src/LTM/link.py):- Models physical pathways (links) with attributes such as length, width, free-flow speed, capacity, and critical/jam densities.
- Simulates traffic dynamics on links, including calculating sending/receiving flows and updating pedestrian density and speed.
- Node Module (
src/LTM/node.py):- Represents intersections or decision points within the network.
- Manages flow distribution between connected links based on defined logic and turning movements.
- Origin-Destination (OD) Management (
src/LTM/od_manager.py):- Manages OD pairs and associated pedestrian demand.
- Includes a
DemandGeneratorcapable of producing various demand patterns (e.g., constant flow, Gaussian peaks, sudden demand surges).
- Pathfinding Module (
src/LTM/path_finder.py):- Determines available paths between origin and destination nodes within the network.
- Calculates turning fractions and probabilities at nodes to influence route choice.
- Visualization Utilities (
src/utils/visualizer.py,network_dashboard.py):- Provides tools for visualizing network states at specific time steps.
- Offers capabilities to animate the simulation, showing the evolution of pedestrian movement (e.g., density, speed) over time.
- Includes an interactive dashboard (
network_dashboard.py) for exploring simulation results.
4.2. Planned Features / Enhancements
Based on the current codebase, the following features or improvements are planned:
- LTM Module (
src/LTM/link.py):- Integrate diffusion flow into the existing sending flow mechanism.
- Address and rectify issues in the flow release logic.
- Path Finding Module (
src/LTM/path_finder.py):- Calibrate and fine-tune parameters for the utility function.
- Complete the Randomize features (
src/utils/env_loader.py)
Planned Validation / Testing
- Validate the simulated sensor flow with real-world data to ensure accuracy.
- Validate the simulated sensor flow with a micro-simulation tool to ensure consistency.
5. Technical Considerations
- Parameter calibration for pathfinding will be crucial for optimal performance.
6. Future Considerations / Open Questions
- The specifics of the "diffusion flow" integration need to be detailed.
- The exact nature of the issues with the "flow release logic" needs to be investigated and documented before fixing.
Controller-Based Path Expansion Feature
Overview
This feature enables dynamic rerouting at designated controller nodes in the pedestrian network. When enabled, controller nodes can expand their routing options to include detour paths through neighboring nodes that weren't part of the original k-shortest paths, allowing pedestrians to avoid congestion.
Problem Solved
Original Issue
In the basic route choice model, if a downstream node B is not included in any of the k-shortest paths for an OD pair, its turn probability is always 0. When the original path's downstream node A becomes blocked/congested, pedestrians cannot switch to B because P(B) = 0, leading to stuck traffic.
Solution
Controller nodes can expand their path set by:
- Identifying all outgoing neighbors (not just those on k-shortest paths)
- Computing shortest paths from each neighbor to the destination
- Creating concatenated detour paths through these neighbors
- Updating turn probabilities to allow switching when congestion occurs
This provides local adaptability without exhaustive path enumeration.
Configuration
YAML Structure
controllers:
enabled: true # Master switch for controller functionality
nodes: [3, 4, 7, 12] # List of node IDs that act as controllers
# Optional: time-based activation
schedule:
3: [[0, 150], [300, 500]] # Node 3 active during steps 0-150 and 300-500
4: [[100, 200]] # Node 4 active only during steps 100-200
Parameters
| Parameter | Type | Description | Default |
|---|---|---|---|
enabled | bool | Enable/disable controller feature | false |
nodes | list[int] | Node IDs that perform rerouting | [] |
schedule | dict | Time-based activation windows (optional) | {} |
Example Configurations
Simple Network (6 nodes)
controllers:
enabled: true
nodes: [2, 3] # Intersection nodes
Complex Network with Scheduling
controllers:
enabled: true
nodes: [10, 25, 50, 75, 100]
schedule:
50: [[0, 200], [400, 500]] # Active in two windows
75: [[100, 300]] # Active during peak period
Implementation Details
Code Structure
1. Configuration Loading (src/utils/config.py)
# Controllers config is parsed and added to path_finder params
path_finder_params['controllers'] = {
'enabled': config['controllers'].get('enabled', True),
'nodes': config['controllers'].get('nodes', []),
'schedule': config['controllers'].get('schedule', {})
}
2. PathFinder Initialization (src/LTM/path_finder.py)
# Controller attributes
self.controllers_enabled = controller_config.get('enabled', False)
self.controller_nodes = set(controller_config.get('nodes', []))
self.controller_schedule = controller_config.get('schedule', {})
3. Controller Check Method
def is_controller_node(self, node_id, time_step=None):
"""Check if a node is an active controller at given time step"""
if not self.controllers_enabled:
return False
if node_id not in self.controller_nodes:
return False
# Check schedule if provided
if time_step is not None and node_id in self.controller_schedule:
schedule = self.controller_schedule[node_id]
for start, end in schedule:
if start <= time_step <= end:
return True
return False
return True
4. Path Expansion Logic
def expand_controller_paths(self, current_node, od_pair):
"""
Expand paths at controller nodes by adding detours through
non-path neighbors that can reach the destination.
"""
# For each existing path through this controller:
# 1. Find all outgoing neighbors (not just on-path ones)
# 2. Compute shortest path from neighbor to destination
# 3. Concatenate: path_prefix + [current_node] + detour_suffix
# 4. Add new path to od_paths if unique
# 5. Update bookkeeping (nodes_in_paths, node_to_od_pairs)
Algorithm Flow
Initialization (calculate_turn_probabilities):
├─ For each OD pair at this node:
│ ├─ If node is controller AND not yet initialized:
│ │ ├─ expand_controller_paths(node, od_pair)
│ │ │ ├─ Get all outgoing neighbors
│ │ │ ├─ For each path through this node:
│ │ │ │ ├─ Get on-path downstream
│ │ │ │ ├─ For each other neighbor:
│ │ │ │ │ ├─ Compute shortest_path(neighbor → dest)
│ │ │ │ │ ├─ Concatenate full detour path
│ │ │ │ │ └─ Add if unique
│ │ │ └─ Update bookkeeping
│ ├─ Build turn distances from all paths (including new detours)
│ └─ Calculate turn probabilities (distance + congestion + capacity)
└─ During simulation:
└─ update_node_turn_probs uses utility model to shift probability
Usage Examples
Example 1: Enable Controllers at Key Intersections
Suppose you have a Delft network and nodes 50, 100, 150 are major intersections:
# data/delft/sim_params.yaml
controllers:
enabled: true
nodes: [50, 100, 150]
Result: At initialization, these nodes will expand their routing table to include all outgoing neighbors. During simulation, when congestion builds up on the primary path, the utility model will shift probability to less-congested detours.
Example 2: Time-Based Traffic Management
Activate controllers only during peak hours (steps 100-300):
controllers:
enabled: true
nodes: [50, 100, 150]
schedule:
50: [[100, 300]]
100: [[100, 300]]
150: [[100, 300]]
Result: Dynamic rerouting is available only during the specified time window, simulating managed traffic control.
Example 3: Testing on Small Network
Use the 6-node example to verify functionality:
# data/od_flow_example/sim_params.yaml
controllers:
enabled: true
nodes: [2, 3] # Middle nodes with multiple connections
Network structure:
1 ─── 0 ─── 2 ─── 4
└─── 3 ───┘
└─── 5
Result: At node 2, if the path to 4 is congested, pedestrians can take the detour through 3.
Behavioral Impact
Without Controllers
- Fixed k-shortest paths
- P(downstream not in paths) = 0 always
- Pedestrians stuck when primary path blocks
- No local adaptation to congestion
With Controllers
- Expanded path set at designated nodes
- P(all feasible downstreams) > 0
- Probability shifts based on utility (distance, density, capacity)
- Local detours enable congestion avoidance
Probability Computation
The utility model balances three factors:
utilities = (alpha * distances / sum(distances)
+ beta * normalized_densities
- omega * capacities / sum(capacities))
probs = softmax(-theta * utilities)
alpha: distance weight (prefer shorter paths)beta: congestion weight (avoid high density)omega: capacity weight (prefer higher capacity)theta: sensitivity (temperature in softmax)
When a downstream becomes congested:
- Its density ↑ → utility ↑ → probability ↓
- Alternative downstream becomes relatively more attractive
Performance Considerations
Computational Cost
- Path expansion happens once during initialization
- Cost scales with:
O(num_controllers × avg_neighbors × avg_od_pairs) - Negligible for typical networks (<100 controllers)
Memory
- Additional paths stored in
self.od_paths - Deduplication prevents exponential growth
- Typical increase: 10-30% more paths per OD pair at controllers
Optimization Tips
- Selective controllers: Only designate critical intersection nodes
- Schedule limiting: Use time windows to reduce active controllers
- K-paths tuning: Fewer initial paths = more benefit from expansion
- Cache shortest paths: Precompute distance-to-destination for all nodes
Debug and Monitoring
Console Output
When controllers expand paths, you'll see:
Controller node 50: Added 2 detour path(s) for OD (0, 8)
Controller node 100: Added 3 detour path(s) for OD (0, 8)
Verification
Check expanded paths in Python:
from src.utils.config import load_config
from src.utils.env_loader import load_environment
config = load_config('data/delft/sim_params.yaml')
env = load_environment('data/delft', config)
# Check OD paths
for od_pair, paths in env.network.path_finder.od_paths.items():
print(f"OD {od_pair}: {len(paths)} paths")
for i, path in enumerate(paths):
print(f" Path {i}: {path}")
Future Enhancements
Planned
- Link-based controllers (control specific upstream links)
- Dynamic controller activation based on congestion thresholds
- Reinforcement learning for optimal controller placement
- Multi-hop path expansion (currently only 1-hop detours)
Advanced Features
- Adaptive epsilon-greedy exploration
- Path quality scoring and pruning
- Real-time path invalidation when links fail
- Controller coordination strategies
References
- Base LTM model: Yperman et al. (2005)
- Route choice: Logit-based discrete choice model
- Path finding: k-shortest paths (Yen's algorithm / NetworkX)
Troubleshooting
Controllers not activating?
- Check
enabled: truein config - Verify node IDs exist in network
- Check schedule windows if using time-based activation
Too many paths generated?
- Reduce number of controller nodes
- Decrease initial
k_pathsparameter - Add schedule constraints
Pedestrians still getting stuck?
- Increase
beta(congestion weight) to make them more sensitive - Decrease
theta(temperature) for sharper probability differences - Verify detour paths actually reach destination
Performance issues?
- Limit controllers to <20 nodes for large networks
- Use schedule to activate only during peak periods
- Profile with smaller
k_pathsinitially
Implementation Summary: Controller-Based Path Expansion
What We Built
A complete controller-based dynamic rerouting system that allows designated nodes to expand their routing options beyond the k-shortest paths, enabling pedestrians to avoid congestion by taking local detours.
Files Modified
1. src/utils/config.py
Changes:
- Added parsing for
controllerssection from YAML - Integrated controller config into
path_finderparams - Backward compatible: defaults to disabled if section missing
Key Code:
# Parse controllers and add to path_finder params
if 'controllers' in config:
path_finder_params['controllers'] = {
'enabled': config['controllers'].get('enabled', True),
'nodes': config['controllers'].get('nodes', []),
'links': config['controllers'].get('links', []),
'schedule': config['controllers'].get('schedule', {})
}
2. src/LTM/path_finder.py
Changes:
- Added controller configuration attributes to
__init__ - Implemented
is_controller_node(node_id, time_step)helper - Implemented
expand_controller_paths(current_node, od_pair)method - Integrated path expansion into
calculate_turn_probabilities
Key Methods:
Controller Check
def is_controller_node(self, node_id, time_step=None):
"""Check if a node is an active controller at given time step"""
- Checks master enable flag
- Verifies node is in controller set
- Validates time window if schedule provided
Path Expansion
def expand_controller_paths(self, current_node, od_pair):
"""Expand paths at controller nodes by adding detours"""
- Gets all outgoing neighbors (not just on-path)
- Computes shortest path from neighbor to destination
- Concatenates: prefix + current_node + detour_suffix
- Updates od_paths, nodes_in_paths, node_to_od_pairs
- Returns list of new paths added
Integration Point
def calculate_turn_probabilities(self, current_node):
for od_pair in relevant_od_pairs:
# NEW: Expand paths at controller nodes during initialization
if not self._initialized and self.is_controller_node(current_node_id):
new_paths = self.expand_controller_paths(current_node, od_pair)
if new_paths:
print(f"Controller node {current_node_id}: Added {len(new_paths)} detour path(s) for OD {od_pair}")
# Existing: Build turn distances from all paths (now including detours)
paths = self.od_paths[od_pair]
# ... rest of turn probability calculation
3. Configuration Files Updated
data/delft/sim_params.yaml
controllers:
enabled: false
nodes: []
# schedule: {}
data/melbourne/sim_params.yaml
controllers:
enabled: false
nodes: []
# schedule: {50: [[0, 200]], 100: [[100, 300]]}
data/od_flow_example/sim_params.yaml
controllers:
enabled: false
nodes: [] # Example: [2, 3]
# schedule: {2: [[0, 500]], 3: [[100, 400]]}
How It Works
Initialization Flow
1. Load config from YAML
├─ Parse controllers section
└─ Add to path_finder params
2. PathFinder.__init__
├─ Store controller config
│ ├─ controllers_enabled
│ ├─ controller_nodes (set)
│ └─ controller_schedule (dict)
3. find_od_paths (initial k-shortest paths)
└─ calculate_all_turn_probs
└─ For each node in paths:
└─ calculate_turn_probabilities(node)
├─ If is_controller_node(node):
│ └─ expand_controller_paths(node, od_pair)
│ ├─ Find all outgoing neighbors
│ ├─ For each neighbor not on existing paths:
│ │ ├─ Compute shortest_path(neighbor → dest)
│ │ ├─ Build concatenated detour path
│ │ └─ Add to od_paths if unique
│ └─ Update bookkeeping
└─ Build turn probabilities from all paths (original + detours)
Runtime Flow
During simulation (each time step):
1. update_turning_fractions(node, time_step, od_manager)
├─ For each (upstream, downstream) turn:
│ ├─ For each OD pair using this turn:
│ │ └─ update_node_turn_probs(node, od_pair, time_step)
│ │ ├─ Get densities of downstream options
│ │ ├─ Get capacities of downstream links
│ │ ├─ Compute utilities (distance + density - capacity)
│ │ └─ Apply softmax → probabilities
│ └─ Weighted sum: P(turn) = Σ P(down|up,od) × P(od|up)
└─ Return turning_fractions array
Key: Detour paths now included in "downstream options" at controllers,
so their probabilities can become > 0 when congestion favors them
Key Design Decisions
1. Local Expansion Only
- Only expand at designated controller nodes
- Only consider immediate outgoing neighbors
- Prevents exponential path growth
- Maintains computational efficiency
2. Reachability Guarantee
- Only add neighbors that have shortest path to destination
- Prevents routing into dead-ends
- Ensures all added paths are viable
3. Path Concatenation Strategy
- Prefix: existing path up to controller node
- Detour: neighbor node + shortest path to destination
- Ensures complete path from origin to destination
4. Initialization-Time Expansion
- Paths expanded once during
calculate_all_turn_probs - Guarded by
if not self._initialized - No runtime overhead from path creation
5. Backward Compatibility
- Feature disabled by default
- No changes to existing simulations unless explicitly enabled
- Graceful fallback if config section missing
Usage Guide
Enable Controllers
- Edit YAML config:
controllers:
enabled: true
nodes: [3, 7, 12] # Your controller node IDs
- Run simulation:
python examples/delft_exp.py
# or
python network_dashboard.py
- Monitor output:
Controller node 3: Added 2 detour path(s) for OD (0, 8)
Controller node 7: Added 1 detour path(s) for OD (0, 8)
With Time-Based Activation
controllers:
enabled: true
nodes: [3, 7, 12]
schedule:
3: [[0, 200], [400, 500]] # Active in two windows
7: [[100, 300]] # Active during peak
12: [[0, 500]] # Always active
Testing Recommendations
1. Small Network Test
Use data/od_flow_example/sim_params.yaml:
controllers:
enabled: true
nodes: [2, 3]
- Verify paths are expanded
- Check console output for confirmation
- Inspect
path_finder.od_pathsfor new paths
2. Congestion Scenario
- Create bottleneck on primary path
- Enable controller upstream of bottleneck
- Verify probability shifts to detour when congestion builds
3. Schedule Test
controllers:
enabled: true
nodes: [2]
schedule:
2: [[100, 200]]
- Verify expansion only happens if step 0 is in window
- (Current implementation expands at initialization, time_step=0)
Benefits
Solves Original Problem
- Before: P(non-path downstream) = 0 always → stuck when primary path blocks
- After: P(all reachable downstreams) > 0 → can detour when congestion occurs
Minimal Overhead
- Path expansion: one-time cost at initialization
- Runtime: existing utility model handles all paths
- Memory: ~10-30% more paths at controller nodes
Realistic Behavior
- Local decisions (no global replanning)
- Congestion-responsive (via utility model)
- Controlled scope (only at designated nodes)
Future Enhancements
- Dynamic controller activation: Enable based on congestion thresholds
- Link-based control: Control specific upstream approaches
- Multi-hop expansion: Allow 2+ hop detours
- Adaptive parameters: Learn optimal alpha, beta, omega per controller
- RL integration: Optimize controller placement and activation timing
Validation Checklist
- Config parsing works
- PathFinder receives controller config
-
is_controller_nodechecks correctly -
expand_controller_pathscreates valid paths - New paths added to
od_paths - Bookkeeping updated (
nodes_in_paths,node_to_od_pairs) - Turn probabilities computed for new paths
- No linter errors
- Backward compatible (disabled by default)
- Documentation complete
Next Steps for User
-
Identify controller nodes in your network
- Key intersections
- Nodes with multiple downstream options
- Strategic rerouting points
-
Enable in config with initial small set
controllers: enabled: true nodes: [node1, node2, node3] -
Run simulation and monitor
- Check console for path expansion messages
- Verify pedestrian flow adapts to congestion
-
Tune parameters if needed
- Adjust
beta(congestion sensitivity) - Adjust
theta(choice sharpness) - Add schedule constraints
- Adjust
-
Scale up based on results
- Add more controllers if beneficial
- Optimize placement for specific scenarios
Support
For questions or issues:
- See
CONTROLLER_FEATURE.mdfor detailed documentation - Check console output for debugging info
- Verify controller nodes exist in network
- Test on small network first
Implementation Date: 2025-10-07
Status: Complete and ready for testing
What's inside
One PRD section, one feature spec with 6 subsections, one implementation summary with 3 modified files and 3 config examples
Change this for your project
- Replace
data/delft/sim_params.yamlwith your own network config path - Replace
data/melbourne/sim_params.yamlwith your own network config path - Replace
data/od_flow_example/sim_params.yamlwith your own network config path - Replace node IDs like
[50, 100, 150]with your network's controller nodes
Where it goes
Keep it in your repository where the agent or team that needs it will read it.
Worth borrowing
- Controller nodes expand path sets only at initialization, avoiding runtime overhead
- Time-based activation schedules let you simulate managed traffic control windows
- Utility model balances distance, density, and capacity for realistic rerouting decisions
Related Documents
Project Requirements Document: Long Video Generation Pipeline
Breaks long video prompts into segments, generates keyframes and clips via local or remote models, then stitches them into a final video.
AGENTS.md — ShakkaShell v2.0
Guides AI coding agents through building a CLI that translates natural language into offensive security commands, with a defined tech stack, structure, and implementation order.
SourceAtlas PRD v2.9.6
Defines the product requirements, architecture, and command interface for an AI-powered codebase understanding assistant integrated into Claude Code.
Fleet Management System - Product Requirements Document (PRD)
Defines functional, non-functional, and technical requirements for a fleet management system with compressed GPS tracking and predictive maintenance.