Skip to content

Commit 6d5da45

Browse files
authored
Add richer observability to the simulation outputs (#427)
* Enhance docs * Add traveltime and queue length in avg stats * Enable visualization of more parameters * Add grid example * Add docstring * Unused var * Unused var
1 parent 4e5136b commit 6d5da45

7 files changed

Lines changed: 454 additions & 55 deletions

File tree

examples/simulate_city.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
"""Run a 24-hour traffic simulation using OpenStreetMap city cartography.
2+
3+
This script downloads city data, builds network CSV assets, configures origins
4+
and destinations, initializes the dynamics engine, and runs a full-day
5+
simulation while periodically updating shortest paths.
6+
"""
7+
18
import argparse
29
from datetime import datetime
310
import logging
@@ -19,6 +26,7 @@
1926

2027
@cfunc(float64(float64, float64), nopython=True, cache=True)
2128
def custom_speed(max_speed, density):
29+
"""Compute a density-aware speed multiplier for custom speed modeling."""
2230
if density < 0.35:
2331
return max_speed * (0.9 - 0.1 * density)
2432
return max_speed * (1.2 - 0.7 * density)

examples/simulate_grid.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
"""Run a 24-hour traffic simulation on a synthetic Manhattan-style grid.
2+
3+
This script generates grid cartography CSV files, builds a road network,
4+
configures the dynamics engine, and simulates agent flow with a 1-second
5+
integration step and 10-second agent insertion cadence.
6+
"""
7+
8+
import argparse
9+
from datetime import datetime
10+
import logging
11+
12+
from dsf.cartography import create_manhattan_cartography
13+
from dsf.mobility import (
14+
RoadNetwork,
15+
Dynamics,
16+
AgentInsertionMethod,
17+
)
18+
19+
from tqdm import trange
20+
from numba import cfunc, float64
21+
import numpy as np
22+
23+
24+
@cfunc(float64(float64, float64), nopython=True, cache=True)
25+
def custom_speed(max_speed, density):
26+
"""Compute a density-aware speed multiplier for custom speed modeling."""
27+
if density < 0.35:
28+
return max_speed * (0.9 - 0.1 * density)
29+
return max_speed * (1.2 - 0.7 * density)
30+
31+
32+
logging.basicConfig(
33+
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
34+
)
35+
36+
if __name__ == "__main__":
37+
parser = argparse.ArgumentParser()
38+
parser.add_argument(
39+
"--seed", type=int, default=69, help="Random seed for reproducibility"
40+
)
41+
parser.add_argument(
42+
"--dim", type=str, default="12x12", help="Dimensions of the grid (e.g., 10x10)"
43+
)
44+
parser.add_argument(
45+
"--amp", type=int, required=True, help="Amplitude of the vehicle input"
46+
)
47+
args = parser.parse_args()
48+
np.random.seed(args.seed)
49+
50+
# Parse the grid dimensions
51+
try:
52+
rows, cols = map(int, args.dim.split("x"))
53+
except ValueError:
54+
raise ValueError(
55+
"Invalid grid dimensions. Please use the format 'rowsxcols' (e.g., 10x10)."
56+
)
57+
58+
logging.info(f"Creating manhattan cartography for {rows}x{cols} grid...")
59+
# Get the cartography of the specified city
60+
df_edges, df_nodes = create_manhattan_cartography(rows, cols)
61+
62+
df_nodes["type"] = (
63+
"traffic_signals" # Set all nodes as traffic lights for simplicity
64+
)
65+
66+
df_edges.to_csv(f"grid_{args.dim}_edges.csv", sep=";", index=False)
67+
df_nodes.to_csv(f"grid_{args.dim}_nodes.csv", sep=";", index=False)
68+
69+
del df_edges, df_nodes
70+
71+
logging.info("Creating road network and dynamics model...")
72+
73+
# Create a road network from the cartography
74+
road_network = RoadNetwork()
75+
road_network.importEdges(f"grid_{args.dim}_edges.csv", ";")
76+
road_network.importNodeProperties(f"grid_{args.dim}_nodes.csv", ";")
77+
# Adjust network parameters
78+
road_network.adjustNodeCapacities()
79+
road_network.autoMapStreetLanes()
80+
road_network.autoAssignRoadPriorities()
81+
road_network.autoInitTrafficLights()
82+
road_network.describe()
83+
84+
# Generate a random vector of integer values for vehicle input
85+
# We want values to have a 10s entry for a whole day
86+
vehicle_input = np.random.normal(args.amp, args.amp * 0.1, size=8640)
87+
vehicle_input = np.clip(vehicle_input, 0, None).astype(int)
88+
89+
# Create a dynamics model for the road network
90+
dynamics = Dynamics(road_network, seed=args.seed)
91+
# To use a custom speed function, you must pass the pointer to the compiled function using the address attribute
92+
# dynamics.setSpeedFunction(SpeedFunction.CUSTOM, custom_speed.address)
93+
# Get epoch time of today at midnight
94+
epoch_time = int(
95+
datetime.combine(datetime.today(), datetime.min.time()).timestamp()
96+
)
97+
98+
dynamics.setMeanTravelDistance(10e3) # Set mean travel distance to 10 km
99+
dynamics.killStagnantAgents(40.0)
100+
dynamics.setInitTime(epoch_time)
101+
dynamics.connectDataBase(f"grid_{args.dim}.db")
102+
dynamics.saveData(300, True, True, True)
103+
104+
# Simulate traffic for 24 hours with a time step of 1 seconds
105+
for time_step in trange(86400):
106+
# Update paths every 5 minutes (300 seconds)
107+
if time_step % 300 == 0:
108+
dynamics.updatePaths()
109+
# Add agents every 10 seconds
110+
if time_step % 10 == 0:
111+
dynamics.addAgents(
112+
vehicle_input[time_step // 10], AgentInsertionMethod.RANDOM
113+
)
114+
dynamics.evolve(False)
115+
116+
dynamics.summary()

src/dsf/cartography/cartography.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,7 @@ def create_manhattan_cartography(
342342
n_x (int): Number of nodes in the x-direction (longitude). Defaults to 10.
343343
n_y (int): Number of nodes in the y-direction (latitude). Defaults to 10.
344344
spacing (float): Distance between nodes in meters. Defaults to 2000.0.
345+
maxspeed (float): Maximum speed for all edges in km/h. Defaults to 50.0.
345346
center_lat (float): Latitude of the network center. Defaults to 0.0.
346347
center_lon (float): Longitude of the network center. Defaults to 0.0.
347348

src/dsf/mobility/FirstOrderDynamics.cpp

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -870,7 +870,9 @@ namespace dsf::mobility {
870870
"mean_speed_kph REAL, "
871871
"std_speed_kph REAL, "
872872
"mean_density_vpk REAL NOT NULL, "
873-
"std_density_vpk REAL NOT NULL)");
873+
"std_density_vpk REAL NOT NULL, "
874+
"mean_travel_time_s REAL, "
875+
"mean_queue_length REAL NOT NULL)");
874876

875877
spdlog::info("Initialized avg_stats table in the database.");
876878
}
@@ -1412,7 +1414,8 @@ namespace dsf::mobility {
14121414

14131415
void FirstOrderDynamics::evolve(bool const reinsert_agents) {
14141416
auto const n_threads{std::max<std::size_t>(1, this->concurrency())};
1415-
std::atomic<double> mean_speed{0.}, mean_density{0.};
1417+
std::atomic<double> mean_speed{0.}, mean_density{0.}, mean_traveltime{0.},
1418+
mean_queue_length{0.};
14161419
std::atomic<double> std_speed{0.}, std_density{0.};
14171420
std::atomic<std::size_t> nValidEdges{0};
14181421
bool const bComputeStats = this->database() != nullptr &&
@@ -1466,6 +1469,7 @@ namespace dsf::mobility {
14661469
m_evolveStreet(pStreet, reinsert_agents);
14671470
if (bComputeStats) {
14681471
auto const& density{pStreet->density() * 1e3};
1472+
auto const& queueLength{pStreet->nExitingAgents()};
14691473

14701474
auto const speedMeasure = pStreet->meanSpeed(true);
14711475
if (speedMeasure.is_valid) {
@@ -1475,13 +1479,15 @@ namespace dsf::mobility {
14751479
mean_speed.fetch_add(speed, std::memory_order_relaxed);
14761480
std_speed.fetch_add(speed * speed + speed_std * speed_std,
14771481
std::memory_order_relaxed);
1478-
1482+
mean_traveltime.fetch_add(pStreet->length() / speedMeasure.mean,
1483+
std::memory_order_relaxed);
14791484
++nValidEdges;
14801485
}
14811486
}
14821487
if (m_bSaveAverageStats) {
14831488
mean_density.fetch_add(density, std::memory_order_relaxed);
14841489
std_density.fetch_add(density * density, std::memory_order_relaxed);
1490+
mean_queue_length.fetch_add(queueLength, std::memory_order_relaxed);
14851491
}
14861492

14871493
if (m_bSaveStreetData) {
@@ -1499,7 +1505,7 @@ namespace dsf::mobility {
14991505
record.stdSpeed = speedMeasure.std * 3.6;
15001506
record.nObservations = speedMeasure.n;
15011507
}
1502-
record.queueLength = pStreet->nExitingAgents();
1508+
record.queueLength = queueLength;
15031509
streetDataRecords.push_back(record);
15041510
}
15051511
}
@@ -1589,6 +1595,8 @@ namespace dsf::mobility {
15891595
if (m_bSaveAverageStats) { // Average Stats Table
15901596
mean_speed.store(mean_speed.load() / nValidEdges.load());
15911597
mean_density.store(mean_density.load() / numEdges);
1598+
mean_traveltime.store(mean_traveltime.load() / nValidEdges.load());
1599+
mean_queue_length.store(mean_queue_length.load() / numEdges);
15921600
{
15931601
double std_speed_val = std_speed.load();
15941602
double mean_speed_val = mean_speed.load();
@@ -1605,8 +1613,9 @@ namespace dsf::mobility {
16051613
*this->database(),
16061614
"INSERT INTO avg_stats ("
16071615
"simulation_id, datetime, time_step, n_ghost_agents, n_agents, "
1608-
"mean_speed_kph, std_speed_kph, mean_density_vpk, std_density_vpk) "
1609-
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
1616+
"mean_speed_kph, std_speed_kph, mean_density_vpk, std_density_vpk, "
1617+
"mean_travel_time_s, mean_queue_length) "
1618+
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
16101619
insertStmt.bind(1, static_cast<std::int64_t>(this->id()));
16111620
insertStmt.bind(2, this->strDateTime());
16121621
insertStmt.bind(3, static_cast<std::int64_t>(this->time_step()));
@@ -1621,6 +1630,8 @@ namespace dsf::mobility {
16211630
}
16221631
insertStmt.bind(8, mean_density);
16231632
insertStmt.bind(9, std_density);
1633+
insertStmt.bind(10, mean_traveltime);
1634+
insertStmt.bind(11, mean_queue_length);
16241635
insertStmt.exec();
16251636
}
16261637
// Special case: if m_savingInterval == 0, it was a triggered saveData() call, so we need to reset all flags

webapp/index.html

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,15 @@ <h2>Load SQLite Database</h2>
7676

7777
<!-- Legend container -->
7878
<div class="legend-container">
79+
<div class="legend-controls">
80+
<label for="edgeColorObservableSelector">Edge color by</label>
81+
<select id="edgeColorObservableSelector">
82+
<option value="density">density</option>
83+
<option value="speed">speed</option>
84+
<option value="traveltime">traveltime</option>
85+
<option value="queue_length">queue_length</option>
86+
</select>
87+
</div>
7988
<div class="legend-title">Density</div>
8089
<div class="legend-bar"></div>
8190
<div class="legend-labels">

0 commit comments

Comments
 (0)