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 eachagv.state_changedof a fleet AGV, the mean over the fleet's AGVs of the share of their lifetime spent traveling, loading or unloading (whatAGV.utilizationreturns). 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]
|
|
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.