mixle.pipeline_twin module

Digital-twin simulation of a min-cost-flow network under stochastic arrivals (H8).

A PipelineTwin is a period-stepped re-solve of a production network’s flow: each period it re-runs mixle.relations.min_cost_flow() (IC-9) under the current arc capacities/costs and a draw of stochastic arrivals, then accumulates queue/bottleneck diagnostics as state. It is wrapped as a mixle.inference.simulate.Simulator so the per-period stochastic draws come from the same .sampler(seed=...) surface every other simulated model in the codebase uses, and named interventions are registered through the same Scenario bookkeeping the simulate module already exposes – though, unlike a learned Bayesian network, a deterministic flow network has no do-operator to route through, so interventions here are applied directly as arc/capacity/supply overrides (see PipelineTwin._apply_interventions()).

Nothing about the twin itself is domain-specific: it is a general period-stepped supply/demand network simulator with pluggable scenario interventions (new/changed arcs, node outages, supply-rate shifts, demand shifts). This module’s worked instantiation is a mine -> plant -> distribution pipeline (source nodes are mines, sinks are customers, “plant-down” and “supply-rate shift” are the named interventions below), but the network topology, capacities, and interventions are all caller-supplied.

Network capacities can be tightened by H5 (mixle_pde.material_transport) transport-physics numbers – those arrive as a plain {(u, v): capacity} mapping (no cross-plugin import) and are combined with the network’s nameplate capacities in build_twin().

build_twin(network, transport, *, seed=0)[source]

Build a PipelineTwin from a network spec and (optionally empty) H5 transport caps.

network keys: cap/cost ((n, n) arc matrices), supply (length n, positive = source (a mine, in this module’s worked instantiation), negative = demand (a customer)), supply_nodes/demand_nodes (node-index lists; inferred from the sign of supply if omitted), and an optional arrival_noise std-dev for the per-period stochastic-arrivals draw (default 0, deterministic).

transport is a plain {(u, v): capacity} mapping of H5 (mixle_pde.material_transport) derived real-world throughput ceilings (slurry line / conveyor limits, etc.) – these are combined with network’s nameplate arc capacities via min (no cross-plugin import: the twin only ever sees the resulting plain numbers).

Parameters:
Return type:

PipelineTwin

class PipelineTwin(cap, cost, supply, *, supply_nodes, demand_nodes, seed=0, arrival_noise=0.0, interventions=None)[source]

Bases: object

A period-stepped digital twin of the production network’s flow.

Each run() re-solves min_cost_flow per period under the twin’s (possibly scenario-modified) capacities/costs and a draw of stochastic arrivals on the supply nodes, tracking per-period throughput/queue/bottleneck-arc diagnostics.

Parameters:
  • cap (np.ndarray)

  • cost (np.ndarray)

  • supply (np.ndarray)

  • supply_nodes (list[int])

  • demand_nodes (list[int])

  • seed (int)

  • arrival_noise (float)

  • interventions (dict[str, dict[str, Any]] | None)

scenario(name, interventions)[source]

Register a named intervention (node outage / supply-rate shift / demand-spike / new arc / …).

Returns a new twin sharing this one’s network and seed but with name available to run() via its scenario= argument – the base twin (and any other scenario already registered on it) is left untouched.

Parameters:
Return type:

PipelineTwin

run(n_periods, *, scenario=None)[source]

Step the twin n_periods periods, re-solving the flow each period.

Returns a dict of per-period diagnostics: throughput (delivered per period), lost_sales (demand unmet in that period), lost_sales_cumulative (the running total), demand (the effective per-demand-node demand actually solved against, after any scenario interventions), utilization (per-period (n, n) arc flow/capacity ratios), bottleneck_arc (the (u, v) with highest utilization in the final period), and bottleneck_utilization (that arc’s utilization series).

lost_sales is LOST demand, not a backlog. Every period solves against the same nominal demand; unmet demand is counted and dropped, never added to a later period’s demand or carried as state, so it can never be served late. This series used to be reported as queue, which read as a backlog that would eventually clear: a capacity-five network facing demand ten reported a growing “queue” of 5 then 10 while the second solve still requested only ten rather than the outstanding fifteen. Modelling an actual backlog needs per-node carried state in the conservation constraints, with aging, service and terminal treatment defined; until that exists, the honest name for this number is lost sales.

Raises ValueError for n_periods < 1 – matching this class’s other invalid-input conventions (an unregistered scenario name or intervention kind raises rather than returning a degraded result), a zero/negative period count has no meaningful per-period diagnostics to report, and silently returning empty series would only move today’s crash (an unconditional bottleneck_arcs[-1] index) to whatever code reads the result instead of surfacing it here at the call that actually got the invalid count.

Parameters:
  • n_periods (int)

  • scenario (str | None)

Return type:

dict