Skip to content

Supply chain

Multi-SKU warehouses and the inventory record they hold.

InventoryItems

A dataclass capturing the per-SKU attributes a warehouse needs:

inv = sw.InventoryItems(
    part_names=["widget", "gizmo", "sprocket"],
    unit_cost=[1.0, 2.5, 0.7],
    stock_level=[20.0, 10.0, 50.0],
    batchsize=[20.0, 10.0, 50.0],
    reorder_points=[5.0, 3.0, 15.0],
    repairable_prc=[0.0, 0.0, 0.0],
    repair_times=[1.0, 1.0, 1.0],
    newbuy_leadtimes=[3.0, 5.0, 2.0],
)

All list-shaped fields must be the same length. Numeric fields are coerced to np.ndarray after construction so warehouse operations stay vectorised.

Warehouse

wh = sw.Warehouse(inventory=inv, name="store")

env = sw.SimEnvironment(dt=1.0, end=80.0)
env.register(wh)
env.run()

Decrement stock during a tick using wh.decrement_vector(per_sku_amounts). The warehouse handles the reorder logic against inventory.reorder_points.

Recording stock for plots

rec = sw.WarehouseStockRecorder(wh, name="store_stock")
env.register(rec)        # AFTER the warehouse so the recorder snapshots
                         # post-tick state
...
sw.plot_warehouse_stock(rec).write_html("stock.html",
                                        include_plotlyjs="cdn")

API

Inventory and supply-chain simulation primitives.

InventoryItems dataclass

InventoryItems(part_names: Sequence[str], unit_cost: Sequence[float], stock_level: Sequence[float], batchsize: Sequence[float], reorder_points: Sequence[float], repairable_prc: Sequence[float], repair_times: Sequence[float], newbuy_leadtimes: Sequence[float], shelf_life: Sequence[float] = list(), failure_rate: Sequence[float] = list())

A collection of SKUs that share a storage location.

All list-shaped fields must be the same length (one entry per SKU). Numeric fields are coerced to np.ndarray on __post_init__ so vectorised warehouse operations don't have to re-check types at runtime.

Warehouse

Warehouse(inventory: InventoryItems, name: str | None = None, parent_warehouse: 'Warehouse | str' = 'industry')

Bases: Entity

Multi-SKU warehouse with (R, Q) reorder policy.

Source code in src/simweave/supplychain/warehouse.py
def __init__(
    self,
    inventory: InventoryItems,
    name: str | None = None,
    parent_warehouse: "Warehouse | str" = "industry",
) -> None:
    super().__init__(name=name)
    self.inv = inventory
    self.parent: "Warehouse | str" = parent_warehouse

    n = inventory.n_items
    self._reorder_time_remaining = np.zeros(n, dtype=float)
    self._orders_placed_bool = np.zeros(n, dtype=bool)
    self._reorders_volume = np.zeros(n, dtype=float)
    self._lifetime_total_orders = inventory.stock_level.copy()
    self._lifetime_backorders = np.zeros(n, dtype=float)
    self._demand_rate: np.ndarray | None = None

decrement_vector

decrement_vector(vec: ndarray) -> ndarray

Vectorised consumption; returns a boolean mask of which SKUs succeeded.

Source code in src/simweave/supplychain/warehouse.py
def decrement_vector(self, vec: np.ndarray) -> np.ndarray:
    """Vectorised consumption; returns a boolean mask of which SKUs succeeded."""
    vec = np.asarray(vec, dtype=float)
    avail = self.inv.stock_level >= vec
    self.inv.stock_level = self.inv.stock_level - vec * avail.astype(float)
    if not avail.all():
        log.debug("%s: %d SKUs unavailable this tick.", self.name, (~avail).sum())
    return avail

process_orders

process_orders(elapsed: float = 1.0) -> None

Tick the reorder logic by elapsed time units.

Source code in src/simweave/supplychain/warehouse.py
def process_orders(self, elapsed: float = 1.0) -> None:
    """Tick the reorder logic by ``elapsed`` time units."""
    self._receive_pending(elapsed)
    self._place_reorders()

estimate_demand_rate

estimate_demand_rate(total_sim_time: float) -> ndarray

Crude demand rate = lifetime orders / total_sim_time.

Source code in src/simweave/supplychain/warehouse.py
def estimate_demand_rate(self, total_sim_time: float) -> np.ndarray:
    """Crude demand rate = lifetime orders / total_sim_time."""
    if total_sim_time <= 0:
        raise ValueError("total_sim_time must be positive.")
    self._demand_rate = self._lifetime_total_orders / total_sim_time
    return self._demand_rate