Skip to content

Stepping & dynamic injection

run() executes the whole horizon at once. To drive an external control loop — observing the network between steps and adding vehicles on demand — use start() / step() and inject(). See The simulation loop for the phase ordering.

Step-driven execution

sim = net.compile(time_step=1.0, total_time=600.0, injection_budget=100)
sim.start()

while sim.current_step < sim.total_steps:
    t = sim.step()                     # runs one step, returns its index
    state = sim.network_state          # inspect the live state between steps
    load = sum(state.occupancy(l) for l in state.link_ids())
    # ... react: decide what to inject next, log, adjust an external policy ...
  • start() initialises links, nodes, and plugins (idempotent).
  • step() runs the four-phase loop for current_step and increments it.
  • current_step is the next step to run; total_steps is total_time / dt floored.

run() is exactly start() + a loop of step() + writing outputs, so batch and step-driven runs produce identical results.

Injecting vehicles mid-run

inject() adds a vehicle to an origin's demand during the run. You supply a route over real link ids; the origin/destination connectors are spliced on automatically, and the vehicle enters the origin's departure queue in time order.

from mesoltm import Vehicle

# Between steps, release a new vehicle from an origin toward a destination now:
sim.inject("a", Vehicle(vehicle_id=999, origin="a", destination="c", route=[l1, l2]))
# at_time defaults to the current step's time, so it is considered next step.

Size the connectors for injection

The origin/destination connectors are sized to stay transparent for the static demand plus injection_budget dynamic injections. It defaults to 100, so light injection works out of the box, but it is still recommended to set it explicitly to the number of vehicles you expect to inject:

sim = net.compile(time_step=1.0, total_time=600.0, injection_budget=N)

Over-estimating is safe — a larger budget only makes connectors more transparent, never more binding, and purely static runs are unaffected. If you inject more vehicles than the budget, a RuntimeWarning is printed naming the vehicle: it is added to its origin's queue but the connector buffer may be full, so it waits there and enters once space frees up (and, if space never frees within the horizon, it may not enter at all). It is never silently discarded — raise injection_budget and re-run.

Injection only appends to a node's demand list, exactly as static demand does — it never perturbs the per-step arithmetic. See Deviations §B5.

Re-injecting a vehicle for another trip

The same vehicle can be injected again once it has finished a trip, to send it on another one. Each trip is recorded as a separate journey on vehicle.journeys — the single source of truth for that vehicle's completed trips. This is uniform with a static demand profile (which makes one vehicle per trip), so trip metrics account for both the same way: one record per journey.

v = Vehicle(vehicle_id=7, origin="a", destination="b", route=[l_ab])
sim.inject("a", v)                                   # journey 0: a → b
while sim.current_step < sim.total_steps and v.active:
    sim.step()                                       # v.active turns False when it arrives

v.origin, v.destination, v.route = "b", "c", [l_bc]  # define the next trip
sim.inject("b", v)                                   # journey 1: re-enter at b, b → c

Set v.route to the next trip's real links before re-injecting (re-injection resets the live journey state but not route). Two guardrails apply: a vehicle that is still moving (v.active) cannot be re-injected (RuntimeError), and by default it must re-enter at the real node where it last left the network (ValueError otherwise; pass check_reentry_node=False to override). Count each re-injection toward injection_budget. See multi_trip_injection.py for a full runnable demo.

Seeding an injected route with shortest path

To inject toward a destination without hand-writing the route, plan it first with a ShortestPathPolicy:

from mesoltm import ShortestPathPolicy
policy = ShortestPathPolicy(dynamic=True)
route = policy.route(sim.network_state, from_node="a", to_node="c")
sim.inject("a", Vehicle(vehicle_id=999, origin="a", destination="c", route=route))

For a full worked example (a coin-toss bottleneck admission policy driven with start/step/inject), see bottleneck_access_policy.py.