monte-carlo/ — runs many independent simulations of the same
net with bounded memory, aggregates metrics inside the worker,
and ships only small JSON metric frames to the UI. Frame buffers never
cross the thread boundary.
createMonteCarloSimulator(config) →
MonteCarloSimulator (synchronous, thread-agnostic)
monte-carlo.worker.ts + worker/messages.ts
createMonteCarloExperiment() → stores:
status · progress · metrics + events;
start/cancel/dispose
MonteCarloWorkerProgress (run counters) +
MonteCarloUserDefinedMetricFrame[]
createMonteCarloExperiment(); subscribes to the handle’s
status/progress/metrics stores and renders the metric frames.
runtime/experiment.ts — worker mode
(createWorker/transport) or
local mode (runs the simulator on the calling thread, used by
tests/embedding).
advanceAll() × batchSize (default 4), post
progress + pending metricFrames, yield,
repeat. cancel is honoured between batches.
MonteCarloRun; deterministic round-robin
advanceAll() advances every active run one frame per
call, so long runs don’t starve short ones.
transition-effect.ts
adapts them to the MC buffers).
| Per run | Meaning |
|---|---|
seed, parameterValues,
initialMarking
|
Defaults derived from the experiment config; overridable per run
(runs[]), e.g. seed = base seed + index.
|
status |
ready → running → complete | error — errors are per
run, other runs continue.
|
frameNumber · currentTime · rngState · completionReason
|
Progress; a run completes on its own deadlock or the shared
maxTime.
|
tokenByteCount · tokenByteCapacity · reallocations
|
Buffer telemetry, exposed in MonteCarloRunSummary.
|
current ⇄ next — no allocation, no history. If the
next token count doesn’t fit, the target buffer alone reallocates:
nextCapacityBytes = max(requiredBytes, capacity × 2, 64).
observeFrame.
The buffer layout (frame-buffer.ts) is a leaner sibling of
the quick-sim EngineFrame: same sections,
no 64-byte header, one extra
transitionElapsedFrames section, and a token region with
spare capacity:
All views
(Uint32Array/Float64Array/Uint8Array)
are created once per buffer over a single ArrayBuffer; IDs
resolve to dense indices through the shared EngineFrameLayout
(layout.ts).
MonteCarloMetricSpec: expression (metric
code body) · placeTokenCountMean ·
transitionFiringCount — serializable, sent in
init.
observeFrame(ctx) visits every run’s current frame as a
SimulationFrameReader (forEachRunFrame);
sampleRuns filters active/completed/all.
mean·sum·min·max·last → scalar, or keep the
run axis and bin it → distribution (exact or bin
width). Optionally aggregate over time.
MonteCarloUserDefinedMetricFrame — scalar
(value/frameValue/timeValue) or distribution (bins: [value, frequency][]) per frame. Small JSON.
monte-carlo/worker/messages.ts)| Direction | Type | Payload |
|---|---|---|
| Host → Worker | init |
sdcpn, extensions?, initialMarking, parameterValues, seed, dt,
maxTime, runCount, batchSize?, metricSpecs?
|
start |
— | |
cancel |
— (checked between batches) | |
| Worker → Host | ready |
— |
progress |
MonteCarloWorkerProgress = advance counters (advancedRuns, completedRuns, erroredRuns, activeRuns,
allFinished) + frameNumber, time, runCount
|
|
metricFrames |
Pending MonteCarloUserDefinedMetricFrame[] batch |
|
complete / cancelled |
Final (or last-known) progress | |
error |
message, itemId |
cancel. And the payloads are aggregates, not frames: the UI
never holds Monte Carlo simulation state.
status / progress /
metrics stores (frames +
latestByMetricId); completion disposes the worker while the
accumulated metric frames remain available for display.
value /
frameValue / timeValue — directly plottable as
a per-frame series or a time-aggregated number.
bins: [value, frequency][] — hosts can paint a bins ×
frames heatmap and derive run aggregations (mean, median, percentiles)
from the bins, all without ever holding simulation state.
@hashintel/petrinaut/ARCHITECTURE.md.
TokenRecord objects per firing — the
README’s planned “IR compilation” would let lambdas/kernels operate
directly on the numeric buffers. (3) Buffer growth is a naive ×2 doubling;
arc-weight static analysis could size buffers up front and eliminate
reallocations.