mamut_routing_tools.td.traffic¶
Time-dependent traffic generation on the tool's own road graph.
A Python port of the workbench's stage-3 traffic stage: per-edge hourly speed
profiles over a 24 h day from two traffic models, exposed as the "TD bridge"
records (:mod:mamut_routing_tools.td.bridge) the family builder turns into
TDVRP/TDVRPTW instances.
Traffic models:
wave: no simulation. Each edge gets a bimodal rush-hour speed dip scaled by road class, distance to the city centre and a seeded per-edge jitter.bpr: a synthetic commuter population routed on the free-flow fastest path; per-edge hourly flows drive the Bureau of Public Roads volume-delay function. Routing is grouped by the trip's workplace endpoint (the small POI pool), so Dijkstra runs ~twice per workplace instead of once per home:work -> hometrips keep a forward tree (bit-identical to a per-origin forward routing) andhome -> worktrips use a reverse tree over the transposed graph, which matches forward routing except on equal-cost shortest-path ties. (Ported separately; see :func:bpr_speeds.)
:func:build_bridge assembles the bridge records in memory (the streamlined
path a per-instance derivation uses); :func:export_bridge serializes the same
records to the git-ignored graph.json / speeds-*.json / nodes-*.json
intermediate for the cached / inspectable path. The canonical published data is
whatever the family builder freezes into the road-graph speed sidecars, so
speeds are rounded here to keep sidecar size down. This generator is additive:
cross-language RNG means numpy cannot reproduce the original Julia
MersenneTwister stream, so regenerated speeds differ from any previously frozen
overlays by design. Validation is therefore structural (same edge set and bin
count, free-flow clamp, plausible per-intensity speed distributions) and
downstream (materialize ATFs, run the Duration checker), never a byte-for-byte
diff.
Wave speeds may exceed the static free-flow limit by up to the jitter fraction; the builder clamps every bridge speed to its edge's free-flow limit when it canonicalizes the overlay sidecar (overlays are slowdowns by contract), so over-limit bridge speeds are expected.
TrafficModelError
¶
Bases: ValueError
Raised on an unknown traffic model, intensity, or an empty edge set.
BridgeEdge
dataclass
¶
One deduplicated directed edge of the bridge graph.
u / v are 0-based graph vertex indices; osm_u / osm_v are
the OSM node ids that key the bridge.
BridgeBuild
dataclass
¶
BridgeBuild(graph: BridgeGraph, speeds: dict[tuple[str, str], BridgeSpeeds], nodes: dict[str, BridgeNodes])
The full TD bridge for one city, held in memory.
graph is shared by every combination; speeds is keyed by (model,
intensity); nodes is keyed by instance base name (one entry per
stage-1 meta). The consumer (mamut_routing_tools.family) takes these
records directly, so a per-instance derivation skips the JSON round-trip
entirely; :func:export_bridge serializes the same records to disk for the
cached / inspectable path.
td_free_speed_ms
¶
td_round_speed
¶
Round a speed to mm/s, keeping it strictly positive.
td_rush_curve
¶
Bimodal rush-hour curve at the centre of hourly bin_index (0..23),
in [0, 1]. Gaussians peak near 08:15 and 17:45.
Source code in MAMUT-routing-tools/src/mamut_routing_tools/td/traffic.py
collect_edges
¶
Deduplicated directed edge list, one entry per (u, v) graph-vertex
pair, keeping the fastest free-flow representative (min free-flow time,
then min length). Self-loops and non-positive lengths are dropped.
RoadGraph already stores a single edge per (u, v), so the dedup is
a safety net; the filters mirror td_collect_edges exactly. Edges are
returned sorted by (u, v) so speed profiles align by position.
Source code in MAMUT-routing-tools/src/mamut_routing_tools/td/traffic.py
vertex_latlon
¶
Per-vertex (lat, lon) indexed by 0-based graph vertex.
Source code in MAMUT-routing-tools/src/mamut_routing_tools/td/traffic.py
bridge_seed
¶
Per-combination seed, matching the workbench's seed arithmetic so the
recorded seed field is stable across regenerations.
Source code in MAMUT-routing-tools/src/mamut_routing_tools/td/traffic.py
wave_speeds
¶
wave_speeds(edges: list[BridgeEdge], vertex_ll: list[tuple[float, float]], center_latlon: tuple[float, float], intensity: str, seed: int) -> list[list[float]]
Per-edge 24-bin wave speed profiles (m/s), aligned with edges.
Each edge draws one uniform jitter in edge order, then applies a bimodal rush dip scaled by amplitude, road-centre proximity and jitter, clamped to a free-flow floor.
Source code in MAMUT-routing-tools/src/mamut_routing_tools/td/traffic.py
bpr_work_pool
¶
Workplace vertex pool: amenity-POI-snapped graph vertices when the OSM
file has enough of them (>= 50), otherwise all vertices (uniform
fallback). Mirrors td_work_pool: each POI snaps to its nearest road
node, which must itself be a graph vertex (the findnode + md.v
membership rule); first-seen dedup; sorted.
Source code in MAMUT-routing-tools/src/mamut_routing_tools/td/traffic.py
bpr_speeds
¶
bpr_speeds(graph: RoadGraph, edges: list[BridgeEdge], osm_path: str | Path, intensity: str, seed: int) -> tuple[list[list[float]], int]
Per-edge 24-bin BPR speed profiles and the total number of trips routed.
Samples trips_per_vertex * |V| commuters (homes uniform on vertices,
workplaces from the POI pool with uniform fallback), routes every trip on
the free-flow fastest path, accumulates per-edge hourly flows at edge
entry time, then applies the BPR volume-delay function with class-based
capacities. Deterministic per seed within Python.
Routing is grouped by each trip's workplace endpoint (the small POI pool)
rather than by home, so Dijkstra runs ~2*|work pool| times instead of once
per home: work -> home trips keep a forward tree per workplace (bit-
identical to a per-origin forward routing) and home -> work trips use a
reverse tree per workplace over the transposed graph, which matches forward
routing except on equal-cost shortest-path ties.
Source code in MAMUT-routing-tools/src/mamut_routing_tools/td/traffic.py
model_params
¶
Parameter record embedded in a speeds file (provenance only).
Source code in MAMUT-routing-tools/src/mamut_routing_tools/td/traffic.py
build_bridge_graph
¶
build_bridge_graph(graph: RoadGraph, edges: list[BridgeEdge], city_slug: str, osm_path: str | Path, only_intersections: bool, trim_to_connected: bool) -> BridgeGraph
Assemble the shared :class:BridgeGraph for a road graph and its edges.
Edges carry the static free-flow limit (m/s, same rounding as the speed
profiles) so the consumer never needs the class table; every vertex
incident to an edge ships its WGS84 position for the consumer's geo cache.
Serializing this record (serialize_bridge_graph) then loading it back
(load_bridge_graph) returns an equal record, so the in-memory and disk
paths never diverge.
Source code in MAMUT-routing-tools/src/mamut_routing_tools/td/traffic.py
build_bridge_speeds
¶
build_bridge_speeds(city_slug: str, model: str, intensity: str, seed: int, num_trips: int, speeds: list[list[float]]) -> BridgeSpeeds
Wrap one combination's per-edge speed profiles in a :class:BridgeSpeeds.
Source code in MAMUT-routing-tools/src/mamut_routing_tools/td/traffic.py
node_osm_ids_from_meta
¶
node_osm_ids_from_meta(graph: RoadGraph, meta: dict, *, only_intersections: bool, trim_to_connected: bool) -> list[int]
Map a stage-1 meta's instance nodes (depot first) to OSM node ids on
graph.
The meta's graph_vertex_id values are indices into the road graph the
instance was sampled on, so the meta's map_options must match the graph
the bridge is built on for the numbering to align (this is the stage-1a /
stage-3 shared-numbering invariant).
Source code in MAMUT-routing-tools/src/mamut_routing_tools/td/traffic.py
build_bridge_nodes
¶
build_bridge_nodes(graph: RoadGraph, meta: dict, city_slug: str, *, only_intersections: bool, trim_to_connected: bool) -> BridgeNodes
Map one stage-1 meta's instance nodes (depot first) to a :class:BridgeNodes.
Source code in MAMUT-routing-tools/src/mamut_routing_tools/td/traffic.py
build_bridge
¶
build_bridge(*, osm_path: str | Path, city_slug: str, models: list[str] | tuple[str, ...] = TD_MODELS, intensities: list[str] | tuple[str, ...] = TD_INTENSITIES, seed: int = 42, only_intersections: bool = True, trim_to_connected: bool = True, metas: list[dict] | tuple[dict, ...] = ()) -> BridgeBuild
Build the full TD bridge for one city in memory (no disk round-trip).
Loads the city road graph once, collects its deduplicated edges, then
assembles the shared :class:BridgeGraph, a :class:BridgeSpeeds for every
model x intensity combination, and a :class:BridgeNodes for every
stage-1 meta dict in metas (keyed by instance base name). This is the
streamlined path the per-instance TD derivation uses; it produces exactly the
records :func:export_bridge writes to and reads back from disk.
Each meta's graph_vertex_id values must index the same road graph the
bridge is built on, so the metas' map_options must match
only_intersections / trim_to_connected.
Source code in MAMUT-routing-tools/src/mamut_routing_tools/td/traffic.py
build_static_bridge
¶
build_static_bridge(*, osm_path: str | Path, city_slug: str, metas: list[dict] | tuple[dict, ...] = (), only_intersections: bool = True, trim_to_connected: bool = True) -> BridgeBuild
The bridge a CVRP-only family needs: the graph and the nodes, no speeds.
:func:build_bridge simulates six traffic overlays per city, which is the
dominant cost and is pure waste for a static family. This builds the same
:class:BridgeGraph and :class:BridgeNodes records and leaves speeds
empty, so family.build_base works unchanged while family.build_td
stays unavailable (it would raise on the missing combinations, which is the
correct failure for a collection that has no time-dependent layer).
Source code in MAMUT-routing-tools/src/mamut_routing_tools/td/traffic.py
export_bridge
¶
export_bridge(*, osm_path: str | Path, city_slug: str, out_root: str | Path, models: list[str] | tuple[str, ...] = TD_MODELS, intensities: list[str] | tuple[str, ...] = TD_INTENSITIES, seed: int = 42, force: bool = False, only_intersections: bool = True, trim_to_connected: bool = True, meta_paths: list[str | Path] | tuple[str | Path, ...] = ()) -> Path
Serialize the TD bridge for one city under <out_root>/<city_slug>/.
Writes graph.json (deduplicated directed edges keyed by OSM node ids),
speeds-<model>-<intensity>.json for every requested combination (speed
profiles aligned with the graph edge order, m/s), one
nodes-<instance_base>.json per stage-1 meta in meta_paths (instance
node -> OSM node ids, depot first), and a bridge-manifest.json. The bytes
written are exactly the serialized :func:build_bridge records, so
load_bridge_* reads them back unchanged. Existing per-combination speed
files are reused unless force=True (the disk path's only advantage over
building in memory: skipping an expensive recompute). Returns the city output
directory.
Each meta's graph_vertex_id values must index the same road graph the
bridge is built on, so the metas' map_options must match
only_intersections / trim_to_connected.
Source code in MAMUT-routing-tools/src/mamut_routing_tools/td/traffic.py
835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 | |