Data-driven Traces

Declare demand: sim.Trace and pass per-trial replay data to model.experiment(demand=...): a 1-D array is shared by every trial, a 2-D array maps row i to trial i (trial order is design-point-major with replications innermost), and a sequence of 1-D arrays gives ragged per-trial traces.

A trace field also accepts a callable f(rng) or f(rng, trial_index) returning a 1-D array – the idiom for bootstrap resampling and fitted generators. It runs once per trial with a numpy.random.Generator seeded from that trial’s own cimba seed and the field name, so the single experiment(seed=...) argument reproduces the simulation streams and the generated traces together, and distinct trace fields draw independent streams. sim.trace_rng(trial_seed, field_name) rebuilds any trial’s generator from its recorded exp["seed"], e.g. to inspect the trace a failed trial replayed.

Callable traces run serially inside experiment(), before the parallel trial run – negligible for bootstrap resampling, but a bottleneck when a single generation is expensive (fitted time-series or ML models). model.trial_seeds(seed=..., replications=..., **params) returns the exact per-trial seeds that experiment() will assign, so such traces can be generated in parallel outside cimba and passed in as finished rows, with bit-identical results:

seeds = model.trial_seeds(seed=42, scale=[1.0, 2.0], replications=100)
rows = Parallel(n_jobs=-1)(
    delayed(slow_generator)(sim.trace_rng(s, "demand")) for s in seeds)
exp = model.experiment(scale=[1.0, 2.0], demand=rows,
                       replications=100, seed=42)

cimba.bootstrap provides ready-made trace generators that resample an observed series: iid(data, length) for serially independent data, moving_block(data, length, block) and circular_block(data, length, block) for stationary dependent series, and stationary(data, length, mean_block) (random geometric block lengths – a good default for autocorrelated data such as demand histories). Each returns an f(rng) closure to pass directly as a trace field value:

from cimba import bootstrap

demand = bootstrap.stationary(history, length=horizon, mean_block=7)
exp = model.experiment(demand=demand, replications=200, seed=42)

For trending, seasonal, or autoregressive data there are three model-based factories that fit the structure internally from the raw series: residual(data, length, trend=1, period=None, mean_block=None) (polynomial trend or, with a period, STL decomposition; residuals resampled i.i.d. or stationary-block), wild(data, length=None, trend=1, period=None, weights="rademacher") (heteroskedastic residuals, weighted in place), and sieve(data, length, order=None) (AR(p) with AIC order selection and Yule–Walker coefficients, simulated forward with resampled innovations). trend and period also accept "auto"; all three take nonnegative=True (clip at zero, for demand data) and start (evaluate the structure on start..start+length-1, e.g. start=len(data) for the horizon after the history).

For supply-chain demand there are two more: intermittent(data, length, jitter=False) (zero-inflated series: Markov-chain occurrence plus resampled nonzero sizes) and joint(panel, length, name=..., mean_block=...) (a mapping of field name to series, resampled with shared block draws so cross-correlation survives; the returned generators carry a trace_rng_name attribute, which experiment() uses instead of the field name when deriving each trial’s generator – callables sharing the tag receive identical rngs).

Size length to cover warmup + duration + cooldown.

Inside a process body, values = sim.Trace(env.demand) returns the trial’s trace as a float64 NumPy view supporting len(), indexing, slicing, and iteration; treat it as read-only. A generator that exhausts its trace simply finishes – the trial still runs to its configured recording window, so generate traces that cover warmup + duration + cooldown (or derive the experiment duration from the trace span), and consider recording sim.now() into an Output when the loop ends as an exhaustion check.

For a narrative walkthrough of trace replay and bootstrapping, see Data-Driven Traces and Bootstrapping Methods.