GPSS Example #3: a machine produces one part every 5 minutes and
hands it to a single inspector. 15% of inspected parts fail and are
scrapped; 85% pass. The reference model for probabilistic outcome
recording (the GPSS TRANSFER .15,,Reject block) and for
deterministic arrivals.
Constant production: one part every 5 minutes, flat, for an 8-hour
shift (96 parts). Each part enters a single-server inspection queue
(Uniform(1..7) minutes). At end of inspection, the part is marked
accepted or rejected with probability 0.85 / 0.15. The model reports
counts, wait times, and inspector utilization; the observed reject
rate should hover near the target 15% with noise bounded by sample
size.
GPSS reference: docs/gpss-examples.md
example #3. Original GPSS transcribed at the top of
machine_inspector.odin.
Part (machine_inspector.odin:80-138)
— four-state machine (Arriving / Waiting / In_Inspection / Done).
Uses the peek-then-seize fast path from the barbershop plus a
dispatcher call at completion (hybrid of the barbershop and the
tool crib).Machine (machine_inspector.odin:166-190)
— the generator. Distinct from the other examples: no RNG draw for
inter-arrival, just eng.advance(..., minutes(Minutes(5))) on
every tick. A constant-rate generator is a normal puck whose
schedule is deterministic.dispatch (machine_inspector.odin:153-162)
— identical-in-shape to the tool crib's dispatcher, minus the
priority concern. Called from In_Inspection on completion.Shop (machine_inspector.odin:56-69) holds:
inspector: sim.Facilitywait_list: sim.Set (FIFO)svc_rng for inspection time,
outcome_rng for the accept/reject Bernoulli drawtotal_svc_time)The "TRANSFER .15" branch is a stat decision, not a flow decision.
GPSS TRANSFER .15,,Reject reads as two distinct transaction paths,
one terminating at Reject, one at TERMINATE. In practice, both
paths just count the part and exit — no further processing
happens on either branch. The puck model collapses this into a
single post-service moment where a Bernoulli draw picks which
counter to increment
(machine_inspector.odin:119-125):
u := db.random_real(&shop.outcome_rng)
if u < REJECT_PROB {
shop.rejected += 1
} else {
shop.accepted += 1
}
If the two branches had different behavior (rework, re-queue,
alternate routing), the decision would move earlier and the flow
would actually fork — that's the threshold_routing shape. The
principle: branch in flow only when the downstream work differs;
otherwise branch only in the stat.
Two RNG streams, by purpose not by quantity. svc_rng drives
service time; outcome_rng drives accept/reject. Both could be
collapsed into one stream — each random_real call would still be
independent — but separating them means a seed sensitivity analysis
on service-time distribution does not perturb the reject pattern,
and vice versa. This is the same principle as the barbershop's
two-stream arrangement, applied for a different reason (stat
decorrelation rather than arrival/service decorrelation). See
modeling guide §5.
Deterministic arrivals are just eng.advance with a constant.
No RNG, no distribution, no Poisson process. Machine shows that
"generator" is not a special primitive — it's a puck with a
specific tick pattern. For a paced production line this shape is
cleaner than rigging up a Uniform(5, 0) degenerate distribution.
Peek-then-seize and dispatcher, both. This model uses:
Arriving (skip queue if inspector
free and nobody waiting).dispatch helper on completion.This combination is stable across the three worker-queue examples
(barbershop, tool_crib, machine_inspector) and is the main
candidate for extraction into a sim.worker_cycle(...) helper once
a few more variants land. See
pruning-status.md §"Patterns
emerging."
Utilization is measured by summing service durations, not by
inspector-busy sampling. total_svc_time += svc at each service
start gives an exact numerator; dividing by END_TIME gives
utilization. This is cheaper and more precise than
on-step sampling, at the cost of being tied to the
completed-services count (in-flight services at shift end are
not credited). For an 8-hour shift with 1–7 minute services the
truncation bias is negligible; for shorter runs or longer services,
prefer an area-under-curve integration like
mm1.
Part could enter .Accepted or .Rejected instead of .Done,
each with its own stat-collection logic. For this model that adds
two enum values and no information — the decision is instantaneous
and post-service. If the two outcomes diverged in behavior (rework
loop, disposition delay, failure-mode tabulation), the extra states
would earn their keep.
db.d_uniform from sdidbThe model defines a local uniform(gen, mean, half) helper rather
than calling db.d_uniform (which has the same signature). The
local form predates the sdidb convenience layer; it's not wrong,
just not yet migrated. A future pass will consolidate on
db.d_uniform across the examples.
A plausible extension: rejected parts go back to the inspector for
a second pass, or to a rework station. That model does fork flow,
with a new state transition from In_Inspection back to Waiting
(or to a separate rework facility). Worth noting as the natural
next step if the example set needs a rework variant.
In GPSS the machine is just GENERATE 5, not a transaction. We
could elide the Machine puck and spawn parts from a free-running
scheduler hook. We don't, because keeping the machine as an
explicit puck makes it trivial to add machine behaviors later —
breakdowns, shift changes, variable production rate — without
restructuring.
This is the reference for:
TRANSFER statistical mode)eng.advance with a constant./machine_inspector [options]
Add --json to emit the uniform envelope (metadata, execution_stats,
metrics, details) instead of the default text output.
| Flag | Type | Default | Description |
|---|---|---|---|
--json |
bool | false |
Emit uniform JSON envelope instead of text. |
Example runs:
./machine_inspector # default text run
./machine_inspector --json # uniform envelope
Edit constants at the top of machine_inspector.odin to vary
interval, inspection distribution, reject probability, or shift
length — those are not exposed as CLI flags.
odin run examples/machine_inspector
Default output is a console summary with counts, reject-rate check, wait stats, sojourn, and utilization.
barbershop — source of the
peek-then-seize fast pathtool_crib — source of the dispatcher
pattern; this model is the FIFO-priority specializationthreshold_routing — contrast:
a model where the post-service branch does fork flow, not just
statspruning-status.md, commit
cf45491 — the Phase B commit that added this example