Skip to content

WIP-Cap Release Policies

This gallery compares two WIP-cap pull policies on a single seeded, multi-stage shop. Both hold arriving jobs in a Pre-Shop Pool (PSP) and meter them onto the floor to keep work-in-process near a target — but they enforce that cap very differently. This page is the featured home for DRACO.

The two policies:

  • ConWIP — Constant Work-In-Process; a single shop-wide job-count cap. A job is released (earliest due date first) only while the floor holds fewer than wip_cap jobs; release is re-checked on every completion and arrival.
  • DRACO — non-hierarchical WIP control (Kasper, Land & Teunter 2023); merges release, authorisation, and dispatching into one per-server decision. On every completion at a server it scores all candidates in that server's queue and PSP by a weighted total impact and dispatches the winner, governing dispatching with FOCUS internally.

See also: Release Policies API

Comparison

"""WIP-cap release policies compared on one seeded shop.

ConWIP (constant WIP, EDD release) vs DRACO (non-hierarchical WIP control that
merges release, authorisation, and dispatching). Both keep shop WIP near a
target; DRACO additionally governs dispatching with FOCUS internally.

Run: uv run python examples/gallery_release_wip.py
"""

from __future__ import annotations

from simulatte.builders import build_conwip_system, build_draco_system
from simulatte.environment import Environment

SEED = 42
HORIZON = 800.0

SYSTEMS = {
    "ConWIP": lambda env: build_conwip_system(env=env, wip_cap=18),
    "DRACO": lambda env: build_draco_system(env=env, wip_target=8, loop_target=4),
}


def run_system(builder) -> tuple[int, float, float, float]:
    with Environment(seed=SEED) as env:
        _psp, _servers, shop_floor, _router, _policy = builder(env)
        env.run(until=HORIZON)
        done = shop_floor.jobs_done
        n = len(done)
        avg_tis = shop_floor.average_time_in_system
        tardiness = [max(0.0, j.lateness) for j in done]
        mean_tard = sum(tardiness) / n if n else 0.0
        pct_tardy = 100.0 * sum(1 for t in tardiness if t > 0) / n if n else 0.0
        return n, avg_tis, mean_tard, pct_tardy


def main() -> None:
    print("WIP-cap release policies (seed=42)")
    print(f"{'Policy':<8}{'Done':>6}{'AvgTIS':>9}{'MeanTard':>10}{'%Tardy':>8}")
    for name, builder in SYSTEMS.items():
        n, tis, mt, pt = run_system(builder)
        print(f"{name:<8}{n:>6}{tis:>9.2f}{mt:>10.2f}{pt:>7.1f}%")


if __name__ == "__main__":
    main()

Run it:

uv run python examples/gallery_release_wip.py

Output

WIP-cap release policies (seed=42)
Policy    Done   AvgTIS  MeanTard  %Tardy
ConWIP    1105    12.60      6.71   62.0%
DRACO     1164     7.84      0.21    1.3%

Interpretation

With wip_cap=18, ConWIP holds the floor at its cap for most of this run and a backlog of about 50 jobs builds in the pool: it completes 1105 jobs, 62.0% of them tardy, with a floor time of 12.60. DRACO completes 1164 jobs at 1.3% tardy with a shorter floor time (7.84) while keeping fewer jobs on the floor: by merging release, authorisation, and dispatching into one per-server score rather than enforcing a single global count, it keeps the right jobs moving and finishes essentially every arrival with near-zero tardiness. A ConWIP cap must be sized to the load: too tight a cap throttles throughput below the arrival rate, so the PSP backs up and most jobs turn tardy. In this run 18 is already on the tight side, and wip_cap=8 is far worse. The contrast shows that how WIP is allocated and dispatched matters as much as the cap itself.