← QueueSim home  ·  All models

Machine Inspector — README

Machine & Inspector

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.

Problem

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.

Model in this directory

Shop (machine_inspector.odin:56-69) holds:

Why this shape

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:

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.

Alternatives considered

Model the accept/reject paths as separate terminal states

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.

Use db.d_uniform from sdidb

The 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.

Branch in flow (re-queue rejects for rework)

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.

A single puck combining machine and part

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.

What this example teaches

This is the reference for:

CLI

./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.

Running it

odin run examples/machine_inspector

Default output is a console summary with counts, reject-rate check, wait stats, sojourn, and utilization.

See also