Skip to content

Intralogistics API

All intralogistics symbols are exported from the single simulatte.intralogistics namespace. For a conceptual overview and worked examples, see the Intralogistics guide and the Intralogistics example gallery.

Layout

Node dataclass

Arc dataclass

LayoutGraph

Pathfinding

PathPlanner

Bases: Protocol

DijkstraPlanner

AStarPlanner

Traffic

TrafficManager

Bases: Protocol

FreeTrafficManager

ResourceBasedTrafficManager

Node reservations backed by one simpy.Resource per node, with node_capacity slots each.

Events (spec §6.4): traffic.reserved when a reservation is recorded in the node's reserved_by (place_now, enter_node after the grant) and traffic.released when leave_node removes it; traffic.wait_started / traffic.wait_ended around an enter_node that has to wait for the node. A node without a binding in the environment (no fleet coordinator bound the graph) records no reservation and emits no reservation event. cancel withdraws waiting requests and releases a grant that enter_node has not recorded yet, so it never changes reserved_by; it ends a pending wait.

PathCheckResult dataclass

Products & storage

SKU dataclass

Warehouse

Bases: Entity

A storage facility with per-SKU inventory and finite pick/put slots; its id is name.

Events (spec §6.4): warehouse.inventory_changed whenever an inventory container completes a put or a get, and warehouse.slot_changed whenever a pick or put slot is acquired or released.

pick_time and put_time are a distribution description or a number (managed) or a callable (sku, quantity) -> float (opaque); they are bound to the streams <name>/pick and <name>/put.

Vehicles

AGV

Bases: Entity

An automated guided vehicle.

Its id is agv_id when given, otherwise agv-<n> in attachment order; :attr:agv_id is an alias of :attr:id. Setting :attr:current_node keeps the agvs lists of the node bindings up to date and emits agv.placed; movement along the graph goes through the fleet coordinator, which emits the agv.move_* events. :meth:transition_to is the only writer of the state and emits agv.state_changed.

AGVType dataclass

AGVState

Bases: Enum

SpeedProfile

Bases: Protocol

Travel time of a segment.

A profile may also define motion(distance, load_weight, battery_level, speed_limit) -> MotionDescription, the curve a viewer uses to interpolate the position (spec §6.5). A portable custom curve may use {"curve": "keyframes", "keyframes": [[elapsed, fraction], ...], "accuracy": meters}. At least two finite points must start at [0, 0], end at fraction 1, have strictly increasing elapsed times and nondecreasing fractions in [0, 1]. Accuracy is finite and nonnegative. The final elapsed time must match the finite positive segment duration within eight float64 ULPs of the larger absolute endpoint time (minimum tolerance 1e-12). Viewers interpolate between points and never call the profile. Profiles without a description are represented by :data:LINEAR_MOTION. The method is optional, so it is not part of this protocol.

TrapezoidalProfile

Battery

Facilities

ChargingStation

Bases: Entity

Models a battery charging/swapping facility placed on a graph node.

AGVs navigate to this station when their battery is low. The station provides concurrent charging slots (modeled as a simpy.Resource) and optionally supports battery swapping via a finite pool of pre-charged batteries (modeled as a simpy.Container). Its id is name.

Events (spec §6.4): charging.started when an AGV is granted a slot and charging.ended when it releases it, charging.pool_changed whenever the swap pool changes, and agv.battery_changed after a recharge or a swap.

recharge_time is a distribution description or a number (managed) or a callable (current_level, target_level) -> float (opaque), bound to the stream <name>/recharge; without it the recharge time is computed from the AGV battery's charging rate.

ParkingArea

Bases: Entity

A facility where idle AGVs wait for their next assignment.

Wraps a simpy.Resource to model finite parking capacity. Each AGV's resource request is tracked individually so that leave() releases the correct slot. Its id is name.

Events (spec §6.4): parking.entered when enter obtained a slot and parking.left when leave released it. FleetCoordinator does not call either.

Orders

TransferOrder dataclass

Bases: Entity

A request to move quantity units of sku from origin to destination.

The order becomes an entity when it is attached: FleetCoordinator.create_order attaches it, and FleetCoordinator.submit attaches an order constructed directly. id is None until then and order-<n> afterwards. The order retires when it reaches a terminal status.

OrderStatus

Bases: Enum

Orchestration

FleetCoordinator

Bases: Entity

Central orchestrator for AGV fleet operations and mission lifecycle.

Manages transfer orders from submission through dispatch, travel, pick, transit, deliver, and completion. Analogous to ShopFloor for production simulations but focused on warehouse-to-warehouse AGV transport.

Its id is name when given, otherwise fleet-<n>. Construction binds the graph's nodes (sorted by id) in the environment and registers an activation initializer that places the AGVs on their starting nodes with the traffic manager (place_now). Orders are attached by :meth:create_order (or by :meth:submit for orders constructed directly) and retire at a terminal status, once the hooks of that transition ran and the mission bookkeeping was cleaned up. :meth:submit and :meth:cancel are deferrable: before activation they are queued and run at activation (spec §10).

Unless built with default_metrics=False, it attaches an :class:~simulatte.intralogistics.OrderEMACollector as metrics (with smoothing factor ema_alpha, default 0.01); other collectors (:class:~simulatte.intralogistics.FleetTimeSeries, :class:~simulatte.intralogistics.FleetKPIs) are attached with collector.attach(env).

Events (spec §6.4): fleet.agv_added per AGV at construction, fleet.pending_changed at every change of the pending queue, order.status_changed at every assignment of an order status, order.assigned / order.unassigned for the order-AGV link, and the AGV events of movement (agv.move_started, agv.move_ended, agv.move_interrupted), cargo (agv.load_changed) and stranding (agv.stranded), and traffic.wait_started / traffic.wait_ended around the delays a path check or a deadlock backoff imposes.

Policies

DispatchStrategy

Bases: Protocol

NearestIdleStrategy

Select the closest idle AGV that can carry the order's SKU and quantity.

Distance is measured as the sum of Euclidean segment lengths along the shortest graph path from the AGV's current node to the origin warehouse's nearest output bay. Ties are broken by agv_id (lexicographic).

RoundRobinStrategy

Cycle through compatible idle AGVs via an internal cursor.

The cursor increments monotonically. If fleet composition changes mid-simulation, cycling order over the filtered candidate list becomes non-deterministic. This is by design -- the modulo arithmetic prevents errors, but strict round-robin fairness is not guaranteed across fleet changes.

ReplenishmentPolicy

Bases: Protocol

ReorderPointPolicy

Trigger replenishment orders when inventory drops below a reorder point.

For each SKU whose inventory is below the configured threshold, creates a TransferOrder sourced from the warehouse with the highest stock of that SKU — unless an in-transit order for the same SKU to the same warehouse already exists.

RepositioningPolicy

Bases: Protocol

RepositioningContext dataclass

StayInPlace

AGV stays at its current node after completing a task.

NearestParkingPolicy

Send the AGV to the nearest parking area that has available capacity.

LoadRecoveryStrategy

Bases: Protocol

ReturnToOrigin

Signal the coordinator to return cargo to the origin warehouse.

Sets order status to PENDING. Physical travel and inventory return are handled by the coordinator, which re-queues the order after a successful return. A failed return leaves the order FAILED.

ResumeDelivery

Keep the current AGV assignment and set the order to IN_TRANSIT.

The actual movement is orchestrated by FleetCoordinator.

Metrics

Built-in fleet collectors on the event bus. Each is bound to its fleet coordinator and attached with collector.attach(env); every FleetCoordinator attaches an OrderEMACollector as coordinator.metrics unless built with default_metrics=False.

OrderEMACollector

Bases: Collector

Exponential moving averages (EMA) of the orders a fleet delivers, updated when an order is delivered.

  • ema_fulfillment_time: time from order creation to delivery.
  • ema_dispatch_delay: time from creation to the (last) dispatch.
  • ema_travel_time_empty: time from the dispatch to the pickup.
  • ema_travel_time_loaded: time from the pickup to the delivery.
  • ema_late_orders: share of orders delivered after their due date (orders without one are on time).

Each is None until the first delivery, which seeds it; then it moves by alpha * (value - ema) per order. The update runs at order.status_changed with reason delivered. The averages are not window KPIs: they include orders delivered during the warm-up and are not part of :meth:~simulatte.environment.Environment.fingerprint; :class:FleetKPIs has the windowed results.

Example

A fleet coordinator attaches one by default; set its ema_alpha or attach your own::

coordinator = FleetCoordinator(..., default_metrics=False)
metrics = OrderEMACollector(coordinator, alpha=0.05).attach(env)
env.run(until=1000)
print(metrics.ema_fulfillment_time)

FleetTimeSeries

Bases: Collector

Fleet utilization, pending orders, throughput and inventory of a fleet over time, as (time, value) lists.

  • fleet_utilization_ts: at each agv.state_changed of a fleet AGV, the mean over the fleet's AGVs of the share of their lifetime spent traveling, loading or unloading (what AGV.utilization returns). The state durations are accumulated from the events, starting from the AGVs' accounting when the collector is created; the AGVs are not read afterwards.
  • pending_orders_ts: the pending queue length when an order is submitted, at its creation time and before it joins the queue, and when an order is dispatched (an order dispatched from the queue is still counted).
  • throughput_ts: delivered orders, starting with (0.0, 0), at each delivery.
  • inventory_ts: per warehouse id, (time, {SKU id: level}) snapshots of the origin at each pickup (picked_at, levels after the load) and of the destination at each delivery.

Create and attach it before the run. Example::

series = FleetTimeSeries(coordinator).attach(env)
env.run(until=1000)
series.plot_fleet_utilization()

plot_fleet_utilization

plot_fleet_utilization() -> None

Step plot of the fleet utilization over time (nothing without data).

plot_inventory

plot_inventory() -> None

Step plot of each warehouse's inventory per SKU over time (nothing without data).

plot_pending_orders

plot_pending_orders() -> None

Step plot of the pending orders over time (nothing without data).

plot_throughput

plot_throughput() -> None

Step plot of the cumulative delivered orders over time.

FleetKPIs

Bases: Collector

Window-aware KPIs of a fleet (spec §12.1-§12.3), keyed "<fleet id>/<name>".

Order KPIs are means over the orders delivered in the observation window (delivery at or after the warm-up); orders still open at the end, cancelled or failed are excluded:

  • fulfillment_time: time from creation to delivery;
  • dispatch_delay: time from creation to the (last) dispatch;
  • travel_time_empty: time from the dispatch to the pickup;
  • travel_time_loaded: time from the pickup to the delivery;
  • late_fraction: share of orders delivered after their due date (orders without one are on time).

throughput is the number of those orders divided by the window length. Time-weighted KPIs are means over the window of a signal that changes with the events, clipped at the window boundaries:

  • utilization: AGVs traveling, loading or unloading (agv.state_changed) divided by the fleet size;
  • pending_orders: orders in the pending queue (fleet.pending_changed).

A KPI without observations, or over an empty window, is left out of :meth:scalars. Attach the collector before the run: the time-weighted signals start from the state at activation.

Builders

build_simple_system

build_simple_system(
    env: Environment,
    *,
    n_agvs: int = 2,
    agv_max_speed: float = 2.0,
    agv_acceleration: float = 1.0,
    agv_battery_capacity: float = 100.0,
    agv_weight_capacity: float = 500.0,
    agv_volume_capacity: float = 10.0,
    products: list[SKU] | None = None,
    initial_inventory_a: dict[SKU, int] | None = None,
    initial_inventory_b: dict[SKU, int] | None = None,
    prefix: str = "",
    layout: Layout,
    nodes: Mapping[str, NodeRef | PortRef] | None = None,
) -> tuple[
    FleetCoordinator,
    list[AGV],
    Warehouse,
    Warehouse,
    GraphRef,
]
build_simple_system(
    env: Environment,
    *,
    n_agvs: int = 2,
    agv_max_speed: float = 2.0,
    agv_acceleration: float = 1.0,
    agv_battery_capacity: float = 100.0,
    agv_weight_capacity: float = 500.0,
    agv_volume_capacity: float = 10.0,
    products: list[SKU] | None = None,
    initial_inventory_a: dict[SKU, int] | None = None,
    initial_inventory_b: dict[SKU, int] | None = None,
    prefix: str = "",
    layout: Layout | None = None,
    nodes: Mapping[str, NodeRef | PortRef] | None = None,
) -> tuple[
    FleetCoordinator,
    list[AGV],
    Warehouse,
    Warehouse,
    LayoutGraph | GraphRef,
]
build_simple_system(
    env: Environment,
    *,
    n_agvs: int = 2,
    agv_max_speed: float = 2.0,
    agv_acceleration: float = 1.0,
    agv_battery_capacity: float = 100.0,
    agv_weight_capacity: float = 500.0,
    agv_volume_capacity: float = 10.0,
    products: list[SKU] | None = None,
    initial_inventory_a: dict[SKU, int] | None = None,
    initial_inventory_b: dict[SKU, int] | None = None,
    prefix: str = "",
    layout: Layout | None = None,
    nodes: Mapping[str, NodeRef | PortRef] | None = None,
) -> tuple[
    FleetCoordinator,
    list[AGV],
    Warehouse,
    Warehouse,
    LayoutGraph | GraphRef,
]

Create a complete intralogistics system with sensible defaults.

Builds a 5-node linear graph::

WH_A_OUT(0,0) -- N1(5,0) -- N2(10,0) -- N3(15,0) -- WH_B_IN(20,0)

with bidirectional arcs, two warehouses (A at the left end, B at the right end), a charging station at N2, and n_agvs AGVs starting at N1.

Entities are named f"{prefix}{default}": nodes as above, warehouses WH-A and WH-B, charging station CS-CENTER, parking area PA-N1 and coordinator fleet. AGVs are named f"{prefix}agv-{i}" when a prefix is given; without one their ids are generated (agv-<n>). A prefix keeps several systems apart in one environment.

layout defaults to env.layout. With a Layout attached, all consumers receive handles and the fifth return value is a GraphRef. Physical getters become available after layout.prepare() or environment activation. Without a prefix, layout AGVs have explicit names vehicle-0, vehicle-1, etc.

If no network is declared, layout mode declares the same five-node line. Otherwise nodes is required: its exact role keys are WH_A_OUT, N1, N2, N3, WH_B_IN (unprefixed), with node/port refs from this layout. Declare a union network and pass distinct mappings to compose independent lines; the builder never replaces an existing network. Partial file network patches may override the builder defaults. The traffic manager remains free-flowing.

Layout warehouses default to obstacle=False. Plain bay nodes have no owner clearance exemption: use a port connector for an obstructing warehouse.

Returns:

Type Description
tuple[FleetCoordinator, list[AGV], Warehouse, Warehouse, LayoutGraph | GraphRef]

(coordinator, agvs, warehouse_a, warehouse_b, graph)

Cancellation and recovery

Cancelling a terminal order is a no-op, including a completed order whose AGV is still repositioning. When ReturnToOrigin successfully returns cargo after an interruption, the order goes back into the pending queue. Interrupts received during recovery are merged until rollback finishes; cancellation remains terminal. Idle callbacks run after the previous mission's ownership and order retirement have been updated.

Interrupted charging and battery swaps release granted slots or cancel waiting requests. A swap interrupted before installation returns its battery to the pool. Traffic waits propagate mission interruptions even with deadlock_timeout=None. Entering a parking area again for the same parked AGV keeps its existing slot.