Skip to content

Network

The Network builder is the high-level entry point: add nodes and links, mark origins/destinations, then compile() to a runnable Simulation. NetworkState is the read-only-plus-mutation view that plugins and routing policies see at run time. The convenience builders construct common topologies. See Building networks.

Network builder

Network

Network(default_fd: dict[str, float] | None = None)

A mutable description of a road network that compiles to a simulation.

Attributes:

Name Type Description
default_fd

Default fundamental-diagram parameters (v_f, w, rho_jam) applied to links that do not specify their own.

Create an empty network.

Parameters:

Name Type Description Default
default_fd dict[str, float] | None

Optional default {"v_f", "w", "rho_jam"} for links.

None

add_node

add_node(node_id: NodeId, pos: tuple[float, float] | None = None) -> NodeId

Add a node (idempotent) and optionally record its (x, y) position.

Parameters:

Name Type Description Default
node_id NodeId

Any hashable identifier.

required
pos tuple[float, float] | None

Optional position used for auto link length and plotting.

None

Returns:

Type Description
NodeId

The node identifier.

add_link(u: NodeId, v: NodeId, length: float | None = None, link_id: int | None = None, **fd: float) -> int

Add a directed link from u to v and return its id.

Multiple links between the same pair are allowed (each gets a distinct id): a slow lane is a parallel link with lower v_f/rho_jam, and a detour is a parallel link whose length is larger than the direct distance — no intermediate nodes required.

Parameters:

Name Type Description Default
u NodeId

Upstream node id (auto-created if new).

required
v NodeId

Downstream node id (auto-created if new).

required
length float | None

Link length in metres. If omitted, uses the Euclidean distance between node positions when both are known.

None
link_id int | None

Optional explicit id; auto-assigned when omitted.

None
**fd float

Fundamental-diagram overrides (v_f, w, rho_jam).

{}

Returns:

Type Description
int

The assigned link_id.

set_origin

set_origin(node_id: NodeId, vehicles: list[Vehicle] | None = None) -> None

Mark a node as an origin and attach demand vehicles to release from it.

Parameters:

Name Type Description Default
node_id NodeId

The origin node.

required
vehicles list[Vehicle] | None

Vehicles to release (routes over real link ids). Additional calls append more vehicles.

None

set_destination

set_destination(node_id: NodeId) -> None

Mark a node as a destination that absorbs arriving vehicles.

set_merge_priorities

set_merge_priorities(node_id: NodeId, alpha: dict[int, float]) -> None

Override the merge priority shares of a node's inbound links.

By default a merge/general junction serves its inbound links with priority shares alpha_i proportional to each link's capacity. Call this to set the shares explicitly at the point where the node's links are defined — e.g. to give a main road priority over a ramp regardless of capacity.

Parameters:

Name Type Description Default
node_id NodeId

The merge/general junction whose priorities to set.

required
alpha dict[int, float]

Mapping {inbound_link_id: share} of relative priority weights (they are normalised internally, so any positive scale works). Inbound links not listed — including any auto-inserted origin connector — fall back to their capacity-proportional weight.

required

compile

compile(time_step: float, total_time: float, routing_policy: RoutingPolicy | None = None, plugins: list[Plugin] | None = None, injection_budget: int = 100, record_history: bool = False, history_path: str | None = None, history_classify: ClassifyFn | None = None) -> Simulation

Build links, nodes and connectors and return a runnable simulation.

Parameters:

Name Type Description Default
time_step float

Simulation step dt in seconds.

required
total_time float

Simulated horizon in seconds.

required
routing_policy RoutingPolicy | None

Optional policy overriding per-vehicle next-link decisions at branching nodes. When omitted, vehicles follow their own (connector-spliced) routes.

None
plugins list[Plugin] | None

Optional per-step plugins run before flows each step (loop hooks, e.g. rerouting logic, link gating, or auctions).

None
injection_budget int

Upper bound on the number of vehicles that will be added dynamically via :meth:~mesoltm.core.simulation.Simulation.inject during the run (default 100). Origin/destination connectors are sized to stay transparent for the static demand plus this many injections, so a dispatcher that injects into an origin carrying little or no static demand is not throttled by the connector. A larger value only makes connectors more transparent (never more binding), so an over-estimate is safe; it does not affect purely static runs. It is still recommended to set this explicitly to the number of vehicles you expect to inject: if more vehicles are injected than the budget, the connectors may be too small and a :class:RuntimeWarning is emitted (the affected vehicle is held in its origin's queue rather than silently discarded — see :meth:~mesoltm.core.simulation.Simulation.inject).

100
record_history bool

If True, capture a per-step snapshot of every vehicle's position for animation/video (see :mod:mesoltm.recording). Off by default — it costs memory and, with history_path, disk. The recorded run exposes the frames on Simulation.history.

False
history_path str | None

Optional JSON file the history is written to (on run()/write_outputs()), so the video can be generated in a separate step.

None
history_classify ClassifyFn | None

Optional classify(vehicle, state) -> str giving each vehicle a colour category in the animation (e.g. a coin-toss outcome); None colours every vehicle the same.

None

Returns:

Type Description
Simulation

A configured :class:~mesoltm.core.simulation.Simulation.

link_capacity(v_f: float, w: float, rho_jam: float) -> float

Return the triangular-FD capacity rho_jam * v_f * w / (v_f + w) in veh/s.

Network state

NetworkState exposes topology, static link kinematics, and live per-step quantities (occupancy, density, queues), plus the mutation seams used for rerouting (set_route) and dynamic demand (inject).

NetworkState

NetworkState(links_by_id: dict[int, Link], out_links: dict[NodeId, list[int]], in_links: dict[NodeId, list[int]], origin_nodes: dict[NodeId, OriginNode], node_positions: dict[NodeId, tuple[float, float] | None], endpoints: dict[int, tuple[NodeId, NodeId]], nodes_by_id: dict[NodeId, BaseNode] | None = None)

A read-only accessor over the links and topology of a compiled network.

Instances are created by :meth:~mesoltm.network.network.Network.compile and handed to routing policies and plugins so external logic can inspect the live network without reaching into internal objects. Live quantities (vehicles on a link, instantaneous density and queue) are read from each link's current queue; cumulative quantities are read from the link's cumulative-count arrays at a given step.

Attributes:

Name Type Description
links_by_id

Mapping link_id -> link for every real and connector link.

step

The current simulation step, updated by the engine each step.

Create a network state view.

Parameters:

Name Type Description Default
links_by_id dict[int, Link]

link_id -> link for all links.

required
out_links dict[NodeId, list[int]]

node_id -> list[link_id] of real outbound links.

required
in_links dict[NodeId, list[int]]

node_id -> list[link_id] of real inbound links.

required
origin_nodes dict[NodeId, OriginNode]

node_id -> OriginNode for nodes that inject demand.

required
node_positions dict[NodeId, tuple[float, float] | None]

node_id -> (x, y) (may be empty).

required
endpoints dict[int, tuple[NodeId, NodeId]]

link_id -> (u, v) node endpoints for each real link.

required
nodes_by_id dict[NodeId, BaseNode] | None

node_id -> junction node model for every through junction (used by :meth:movement_demand); may be empty.

None

nodes

nodes() -> list[NodeId]

Return all node identifiers in the network.

link_ids() -> list[int]

Return all link identifiers (real and connector).

out_links(node_id: NodeId) -> list[int]

Return the real outbound link ids of node_id.

in_links(node_id: NodeId) -> list[int]

Return the real inbound link ids of node_id.

links_between(u: NodeId, v: NodeId) -> list[int]

Return all real link ids going directly from node u to node v.

endpoints

endpoints(link_id: int) -> tuple[NodeId, NodeId] | None

Return the (u, v) node endpoints of a real link, or None.

position

position(node_id: NodeId) -> tuple[float, float] | None

Return the (x, y) position of node_id if one was given.

length

length(link_id: int) -> float

Return the length (m) of a link.

capacity

capacity(link_id: int) -> float

Return the capacity (veh/s) of a link.

continuous_free_flow_time

continuous_free_flow_time(link_id: int) -> float

Return the continuous-time free-flow travel time (s) of a link.

This is the continuous LTM value length / v_f — the exact time a vehicle would need to traverse the link at free-flow speed, independent of the simulation time step. The discrete model advances vehicles in whole steps, so the achievable free-flow time is instead the link's integer wave lag T1 * dt (see :func:mesoltm.metrics.free_flow_time for the route-level discrete value). Use this continuous value where a fine-grained, dt-agnostic cost is wanted (e.g. as a routing edge weight).

vehicles_on

vehicles_on(link_id: int) -> list[Vehicle]

Return the vehicles currently queued on a link (live).

occupancy

occupancy(link_id: int) -> int

Return the number of vehicles currently on a link (live).

density

density(link_id: int) -> float

Return the current density (veh/m) of a link (live).

entry_queue

entry_queue(node_id: NodeId) -> int

Return the number of vehicles waiting to enter at an origin node.

waiting_vehicles

waiting_vehicles(node_id: NodeId) -> list[Vehicle]

Return the vehicles waiting in an origin's vertical entry queue (live).

These are vehicles whose departure time has passed but that the first link has not yet admitted (see :class:~mesoltm.core.nodes.origin_node). Unlike :meth:entry_queue (a count), this returns the vehicles themselves so callers (e.g. the animation recorder) can read each one's id, next link and category. Empty for non-origin nodes.

inject

inject(node_id: NodeId, vehicle: Vehicle, at_time: float | None = None, check_reentry_node: bool = True) -> None

Add a vehicle to an origin's demand during the run (dynamic injection).

The vehicle's route is treated as a sequence of real link ids; the origin/destination connector links are spliced on automatically (matching how static demand is compiled), so external callers only ever reason about real links. The vehicle then joins the origin's departure queue in departure-time order and is released like any other vehicle.

The same vehicle may be injected repeatedly to make several trips, each recorded as its own journey (see :attr:~mesoltm.core.vehicle.Vehicle.journeys). Two guardrails protect re-injection:

  • The vehicle must not still be moving through, or waiting to enter, the network — its previous journey must have completed (it was absorbed at a destination). Otherwise a :class:RuntimeError is raised.
  • By default the vehicle must re-enter at the same real node where it last left the network (its previous journey's final real link's downstream node — auxiliary O/D connector nodes are never considered). A mismatch raises :class:ValueError; pass check_reentry_node=False to allow a deliberate re-entry elsewhere.

If the number of injections exceeds the injection_budget the connectors were sized for (see :meth:~mesoltm.network.network.Network.compile), a :class:RuntimeWarning is emitted: the connector buffer may then be too small to admit the vehicle promptly, so it waits in the origin queue (and may not enter within the horizon) rather than being silently discarded.

Parameters:

Name Type Description Default
node_id NodeId

An origin node (marked via Network.set_origin).

required
vehicle Vehicle

The vehicle to inject; scheduled_departure, route and position are set here (and the live journey state reset on re-injection).

required
at_time float | None

Departure time in seconds; defaults to the current step's time (step * dt) so it is considered in the next step.

None
check_reentry_node bool

When re-injecting an already-used vehicle, require it to re-enter at the real node it last left from. Defaults to True.

True

Raises:

Type Description
ValueError

If node_id is not a registered origin node, or (on re-injection with check_reentry_node) the vehicle last left the network at a different real node.

RuntimeError

If the vehicle is still active (its current journey has not completed).

vehicles_in_network

vehicles_in_network() -> list[VehicleView]

Return a snapshot of every vehicle currently on a real link.

Used by :class:~mesoltm.plugins.plugin.ReroutingPlugin: each view carries the vehicle, the link it is on, its remaining real-link route and its destination. Connector links are skipped — a vehicle on an access connector has no downstream choice to reroute yet. Called at the start of a step (before flows), when every vehicle is on exactly one link.

movement_demand

movement_demand(node_id: NodeId, out_link_id: int) -> list[VehicleView]

Return the vehicles demanding to cross onto one outbound link this step.

For the movement at node_id toward out_link_id, returns one :class:VehicleView per vehicle whose next link resolves to out_link_id, taken from the current sending flow of the node's inbound links — real approaches and any origin connector, whose queued vehicles also carry routes that may load this movement — in FIFO order (len(...) is the count). Each view carries the vehicle and the inbound link it is on, so a caller can ration the movement and reroute the rest with :meth:set_route.

Next-link resolution matches the node model (an attached routing policy, else the vehicle's own route). It is a pure query: it refreshes the inbound links' demand for self.step so it works from a plugin (which runs before the demand phase) without changing any flow result. Returns [] if the node has no through-junction model.

Parameters:

Name Type Description Default
node_id NodeId

The junction the movement is at.

required
out_link_id int

The outbound (downstream) link id of the movement.

required

peek_flows

peek_flows(node_id: NodeId, supply_overrides: dict[int, int] | None = None) -> dict[int, list[VehicleView]]

Predict the vehicles that will cross node_id this step, per movement.

A read-only replay of the junction's own flow algorithm — same priorities, FIFO order, per-outbound supply bookkeeping and locking — keyed by outbound link id, each value the predicted crossing vehicles in order (every outbound link id of the junction is present, possibly with an empty list). Unlike :meth:movement_demand, which lists every vehicle wanting a movement, this returns only those the node model would actually transfer, so under congestion it is the realistic per-step crossing set.

supply_overrides maps out_link_id -> supply and replaces the matching links' receiving flow in the replay only — e.g. to model a cap an access-control plugin will itself enforce. The prediction assumes routes stay as they are between the query and the flow phase.

Like :meth:movement_demand it is a pure query: it refreshes the junction's adjacent links' demand/supply for self.step (so it works from a plugin, before the demand phase) and never touches vehicles, flows, or the node's persistent priority state. Returns {} if the node has no through-junction model.

Parameters:

Name Type Description Default
node_id NodeId

The junction to replay.

required
supply_overrides dict[int, int] | None

Optional per-outbound-link supply replacements.

None

remaining_real_route

remaining_real_route(vehicle: Vehicle, current_link_id: int | None = None) -> list[int]

Return the real (non-connector) links of vehicle.route, forward-only.

This reads vehicle.route (the plan the vehicle already carries) and never recomputes a route: the recorder and rerouting logic use it so the logged/replaced plan always matches what actually ran. When current_link_id lies on the route the tail from there onward is returned (starting with the current link); otherwise — e.g. a vehicle still queued at its origin, not yet on a real link — the full real route is returned.

set_route

set_route(vehicle: Vehicle, real_route: list[int]) -> None

Replace an in-network vehicle's route with a new real-link route.

The new route must start at the link the vehicle is currently on — the vehicle keeps moving, so its remaining plan can only be rewritten from where it is. We read the current link from route[position] (the pointer the movement logic keeps in sync) and reject a mismatch, so a bad update can never silently strand the vehicle. The destination access connector is re-attached and the position pointer reset to the (unchanged) current link, so :meth:Vehicle.next_link resolves the new next link seamlessly.

Parameters:

Name Type Description Default
vehicle Vehicle

An in-network vehicle (see :meth:vehicles_in_network).

required
real_route list[int]

Ordered real link ids from the current link to the destination, starting with the current link.

required

Raises:

Type Description
ValueError

If real_route is empty or does not start at the vehicle's current link.

cumulative_inflow

cumulative_inflow(link_id: int, t: int | None = None) -> float

Return cumulative inflow of a link at step t (default: current step).

cumulative_outflow

cumulative_outflow(link_id: int, t: int | None = None) -> float

Return cumulative outflow of a link at step t (default: current step).

VehicleView

Bases: NamedTuple

A read-only snapshot of one in-network vehicle, for rerouting logic.

Attributes:

Name Type Description
vehicle Vehicle

The vehicle itself (carries vehicle_id, destination, ...).

link_id int

The real link the vehicle is currently travelling on (its location).

route list[int]

The remaining ordered real link ids from the current link onward (connectors excluded), i.e. the plan the reroute logic may replace.

destination NodeId

The vehicle's destination node (convenience copy).

Builders

grid_network

grid_network(rows: int, cols: int, link_length: float = 200.0, spacing: float = 1.0, fd: dict[str, float] | None = None, bidirectional: bool = True, skip_nodes: Iterable[tuple[int, int]] | None = None, skip_edges: Iterable[tuple[tuple[int, int], tuple[int, int]]] | None = None, all_nodes_od: bool = False) -> Network

Build a rectangular grid, optionally partial (missing nodes/edges).

Nodes are (i, j) tuples for row i and column j. Adjacent nodes are connected horizontally and vertically; with bidirectional a link is added in each direction. Custom / partially-connected layouts are supported by excluding nodes (skip_nodes) or individual directed edges (skip_edges).

Parameters:

Name Type Description Default
rows int

Number of grid rows.

required
cols int

Number of grid columns.

required
link_length float

Length (m) assigned to every grid link.

200.0
spacing float

Geometric spacing between nodes (for positions/plots).

1.0
fd dict[str, float] | None

Optional fundamental-diagram params for all links.

None
bidirectional bool

If True add both directions of each edge.

True
skip_nodes Iterable[tuple[int, int]] | None

Iterable of (i, j) nodes to omit entirely.

None
skip_edges Iterable[tuple[tuple[int, int], tuple[int, int]]] | None

Iterable of directed ((i, j), (k, l)) edges to omit.

None
all_nodes_od bool

If True mark every present node as both origin and destination.

False

Returns:

Name Type Description
A Network

class:Network describing the (possibly partial) grid.

corridor_network

corridor_network(lengths: list[float], fd: dict[str, float] | None = None, node_prefix: str = 'n') -> Network

Build a linear corridor of consecutive links with an origin and destination.

Parameters:

Name Type Description Default
lengths list[float]

Length (m) of each link in order; len(lengths) links and len(lengths) + 1 nodes are created.

required
fd dict[str, float] | None

Optional fundamental-diagram params applied to all links.

None
node_prefix str

Prefix for the generated node ids (n0, n1, ...).

'n'

Returns:

Name Type Description
A Network

class:Network with the first node set as origin and the last as

Network

destination (no demand attached yet — use set_origin to add vehicles).

network_to_dict

network_to_dict(net: Network) -> dict

Serialise a network's topology to a plain dict (JSON-friendly).

Parameters:

Name Type Description Default
net Network

The network to serialise.

required

Returns:

Type Description
dict

A dict with nodes, links, origins and destinations. Node

dict

ids are stringified so the result round-trips through JSON; demand

dict

vehicles are not included (attach them after loading).

network_from_dict

network_from_dict(data: dict) -> Network

Reconstruct a :class:Network from :func:network_to_dict output.

Parameters:

Name Type Description Default
data dict

A dict with nodes, links, origins, destinations.

required

Returns:

Type Description
Network

The reconstructed network (without demand vehicles).