Skip to content
or press P · arrows to step · Esc to leavetap or swipe to step

Discrete-Event Simulation by Building

Week 3 — Priorities and triage

Quisim

Week 3: Priorities and triage ​

Where we left off ​

Week 1: build a queue

  • Arrive → Queue → Perform → Leave
  • Stable only if ρ=λkμ<1\rho = \dfrac{\lambda}{k\mu} < 1
  • Variability and ρ\rho near 1 make waits explode

Week 2: trust the numbers

  • One run is one sample
  • Replications and confidence intervals
  • Simulation agrees with Erlang C, and keeps working where the formula stops

Today's question ​

In an emergency department, critical patients go first. Who wins, who pays, and does anybody wait less overall?

Recap in one model: the M/M/1 example ​

diagramdiagram
Model codeOpen in Quisim
qml
NumberInput { id: number0; initValue:125; label:"1/λ"; lower:1 }
NumberInput { id: number1; initValue:100; label:"1/µ"; lower:1 }
Wire { from: number0.measureValue; to: rnd0.parameterMean }
Wire { from: number1.measureValue; to: rnd1.parameterMean }
RandomExponential { id:rnd0 }
RandomExponential { id:rnd1 }
Wire { from:rnd0.measureRandomVariate; to:arrive0.parameterDuration }
Wire { from:rnd1.measureRandomVariate; to:perform0.parameterDuration }
Arrive { id:arrive0 }
Pipe { from:arrive0.outlet; to:queue0.inlet }
Queue { id:queue0 }
Pipe { from:queue0.outlet; to:perform0.inlet }
Perform { id:perform0 }
Pipe { from:perform0.outlet; to:leave0.inlet }
Leave { id:leave0 }
Wire { from: queue0.measureLoad; to: chart0.parameters[0] }
Statistics { id: statistics0; height:48; width:128; label:"Sojourn\ntime" }
Wire { from: leave0.measureSojourn; to: statistics0.parameters[0] }
Chart { id: chart0; height:66; width:384 }

One line, first come, first served (FIFO). ρ=100125=0.8\rho = \frac{100}{125} = 0.8, theory W=1μ−λ=500W = \frac{1}{\mu-\lambda} = 500 ms. Today we split this line into classes.

How to follow along in the free demo ​

  • Show the editor pane (pen icon). The canvas is locked while it is hidden
  • Examples tab: load Triage, No Triage, and later Renege, Decide, To Shorter
  • Type new values into the NumberInput fields on the canvas; nothing else can be changed in an example
  • New gives an empty canvas; the Blocks tab offers a small kit: Arrive, Queue, Perform, Leave, NumberInput, 2× RandomExponential, Chart, Statistics
  • Start clean (play, Ctrl+Enter) runs from time 0; Animate (film) shows the patients; Step and Monitor show the events one by one

Full version: open Model code under a picture and copy it into the editor instead.

Rule for today (as in week 2) ​

Write down your prediction before every run.

Why priorities? ​

Not all customers are equal ​

  • Emergency department: triage ranks patients by severity
  • Phone lines: emergency calls before routine calls
  • Computers: interrupt handlers before background jobs

Predict ​

One doctor, three classes of patients. If critical patients jump the line, does the average patient wait less, the same, or more than with one FIFO line?

Keep your answer. We test it in 20 minutes.

Build it: critical patients alone ​

New (empty canvas), then from the Blocks tab, step by step:

  1. Arrive, then drag from its outlet (O) into the Blocks tab onto the inlet of Queue; same from Queue to Perform, from Perform to Leave
  2. Two RandomExponential: wire the value output (V) of one to Arrive's duration (D), of the other to Perform's duration
  3. Two NumberInput into the mean (µ) of each RandomExponential. Type 2000 into the one for arrivals (1/λ), 250 into the one for treatment (1/µ)
  4. Statistics on Leave's sojourn (LS), Chart on Queue's load (L)

Build it: the result ​

diagramdiagram
Model codeOpen in Quisim
qml
NumberInput { id: number0; label:"1/λ"; initValue:2000; lower:1 }
NumberInput { id: number1; label:"1/µ"; initValue:250; lower:1 }
Wire { from: number0.measureValue; to: rnd0.parameterMean }
Wire { from: number1.measureValue; to: rnd1.parameterMean }
RandomExponential { id:rnd0 }
RandomExponential { id:rnd1 }
Wire { from:rnd0.measureRandomVariate; to:arrive0.parameterDuration }
Wire { from:rnd1.measureRandomVariate; to:perform0.parameterDuration }
Arrive { id:arrive0 }
Pipe { from:arrive0.outlet; to:queue0.inlet }
Queue { id:queue0 }
Pipe { from:queue0.outlet; to:perform0.inlet }
Perform { id:perform0 }
Pipe { from:perform0.outlet; to:leave0.inlet }
Leave { id:leave0 }
Wire { from: queue0.measureLoad; to: chart0.parameters[0] }
Statistics { id: statistics0; height:48; width:128; label:"Sojourn\ntime" }
Wire { from: leave0.measureSojourn; to: statistics0.parameters[0] }
Chart { id: chart0; height:66; width:384 }

Only the critical stream: one patient every 2000 ms, 250 ms with the doctor. ρ=2502000=0.125\rho = \frac{250}{2000} = 0.125. The Chart shows that no queuing is happening.

Predict: how long does a critical patient stay? ​

Predict ​

The doctor works for critical patients only. Mean sojourn time (waiting + treatment)?

Run it: the Statistics block shows a different mean at every Start clean (the RandomExponential blocks have no seed). Six runs gave about 100 to 420 ms.

  • Theory, M/M/1: W=1μ−λ=11250−12000≈286W = \dfrac{1}{\mu-\lambda} = \dfrac{1}{\frac{1}{250}-\frac{1}{2000}} \approx 286 ms
  • Almost no waiting: sojourn ≈ the 250 ms treatment
  • Only about 5 patients arrive in a 10 s run: expect a wide spread

Build it: now everybody in one line ​

Same model, one change: set 1/λ to 286.

diagramdiagram
Model codeOpen in Quisim
qml
NumberInput { id: number0; label:"1/λ"; initValue:286; lower:1 }
NumberInput { id: number1; label:"1/µ"; initValue:250; lower:1 }
Wire { from: number0.measureValue; to: rnd0.parameterMean }
Wire { from: number1.measureValue; to: rnd1.parameterMean }
RandomExponential { id:rnd0 }
RandomExponential { id:rnd1 }
Wire { from:rnd0.measureRandomVariate; to:arrive0.parameterDuration }
Wire { from:rnd1.measureRandomVariate; to:perform0.parameterDuration }
Arrive { id:arrive0 }
Pipe { from:arrive0.outlet; to:queue0.inlet }
Queue { id:queue0 }
Pipe { from:queue0.outlet; to:perform0.inlet }
Perform { id:perform0 }
Pipe { from:perform0.outlet; to:leave0.inlet }
Leave { id:leave0 }
Wire { from: queue0.measureLoad; to: chart0.parameters[0] }
Statistics { id: statistics0; height:48; width:128; label:"Sojourn\ntime" }
Wire { from: leave0.measureSojourn; to: statistics0.parameters[0] }
Chart { id: chart0; height:66; width:384 }

λ=1500+11000+12000=1286\lambda = \frac{1}{500}+\frac{1}{1000}+\frac{1}{2000} = \frac{1}{286}: minor, urgent and critical together, ρ=0.875\rho = 0.875.

Predict: one FIFO line for everyone ​

Predict ​

Same doctor, seven times the arrivals. Mean sojourn time?

Run it: the Chart shows a queue that builds up and drains again; the mean jumps from one Start clean to the next. Six runs gave about 470 to 1000 ms.

  • Theory: W=11250−1286≈2000W = \dfrac{1}{\frac{1}{250}-\frac{1}{286}} \approx 2000 ms, seven times the 286 ms
  • The 10 s run starts empty and is far too short to reach that (week 2: start-up bias)

Your turn: three systems in one model ​

In your own model (10 min) ​

Set 1/λ as in the table, press Start clean three times each, note the Sojourn time mean.

who is in the line1/λρ\rhotheory WWrun 1run 2run 3
critical only20000.125286
critical + urgent6670.375400
everybody2860.8752000

Which row has the largest spread between runs? Why?

Key insight: what a class "sees" ​

Key insight ​

With strict priority, each class lives in a smaller system: critical patients see almost only critical patients; minor patients see everybody.

  • If higher classes could even interrupt treatment, critical patients would get exactly the first row; the second row would be the mean over critical and urgent patients together (urgent alone a bit more, about 460 ms)
  • Quisim's Triage example does not interrupt: a critical patient still waits for the patient already being treated

The Triage example ​

The Triage example ​

Three Arrive → Queue streams, one Perform (the doctor), one Leave and Statistics block per class, plus Overall.

How the priority works ​

  • Three pipes end at the same Perform inlet. When the doctor is free, the pipe defined last in the code is tried first (its inPriority is 1): critical, then urgent, then minor
  • Each Arrive stamps a symbol on its patients: 0 critical, 1 urgent, 2 minor
  • After treatment, Get reads the symbol and Decide sends the patient to outlet 0, 1 or 2

Which NumberInput is which? Look at the defaults:

1/λ = 5001/λ = 10001/λ = 20001/µ = 250Capacity = 1
minorurgentcriticaltreatmentdoctors

ρ=250(1500+11000+12000)=0.875\rho = 250\left(\frac{1}{500}+\frac{1}{1000}+\frac{1}{2000}\right) = 0.875: same as your "everybody" line.

Your turn: check the model before you trust it ​

Verify (5 min) ​

Load Triage, Start clean. Read the counts on the three Arrive blocks and the three Leave blocks.

Minor ArriveUrgent ArriveCritical Arrive
arrived
left at the Leave next to …

Which Statistics box collects which class?

  • Critical Arrive counts 4, and so does the Leave that feeds the box labelled Critical: the labels match the classes
  • Symbol 0 (critical) goes to Decide outlet 0, symbol 2 (minor) to outlet 2
  • Week 2's lesson in action: verify a model before you report its numbers

Predict: Triage at the defaults ​

Predict ​

Rank the three classes by mean sojourn time. Then: is Overall lower than one FIFO line would give?

Run it (fixed seeds: everyone gets these numbers; classes read by the counts):

critical (4)urgent (8)minor (18)overall (30)
Triage720315600540
  • Urgent clearly fast; critical surprisingly slow: 4 patients, and one long treatment dominates
  • For the overall question we need the same patients without triage

No Triage: same patients, one line ​

The No Triage example ​

Same three streams, same seeds, same doctor. One Queue: the doctor takes whoever has waited longest.

Predict: what changes without triage? ​

Predict ​

Load No Triage, keep the defaults. For each class: longer or shorter than with triage? And Overall?

Run it:

mscritical (4)urgent (8)minor (18)overall (30)
No Triage745451535540.37
Triage720315600540.37

Critical and urgent gain, minor pays. Overall is identical to the last digit.

Why is Overall exactly the same? ​

  • Same seeds: the same patients arrive at the same times, and the doctor draws the same treatment times in the same order
  • The doctor is never idle while someone waits, in both models: so patients leave at the same moments
  • Triage only changes who leaves at each moment

Key insight ​

Priority redistributes waiting; it does not remove it. Total waiting is set by ρ\rho, not by the order of service.

Queueing theory calls this the conservation law (Kleinrock): it holds whenever service times do not depend on the order.

Your turn: recompute Overall by hand ​

5 min, both examples ​

From your runs, fill in mean Wˉi\bar W_i and Leave count nin_i per class:

criticalurgentminor
Wˉi\bar W_i Triage
Wˉi\bar W_i No Triage
nin_i

Compute ∑iniWˉi∑ini\dfrac{\sum_i n_i \bar W_i}{\sum_i n_i} for both. Do you get the Overall box?

4⋅720+8⋅315+18⋅60030≈540\frac{4\cdot 720 + 8\cdot 315 + 18\cdot 600}{30} \approx 540: Overall is the patient-weighted mean, which is why minor patients dominate it.

Thirty patients are not enough: a long run ​

Full version: add Steering { id: steering0; duration:1000000 } to both models (1000 s, about 3400 patients).

diagramdiagram
Model codeOpen in Quisim
qml
NumberInput { id: number0; label:"1/λ"; initValue:500; lower:1 }
Wire { from: number0.measureValue; to: rnd0.parameterMean }
RandomExponential { id:rnd0; seed:412 }
Wire { from:rnd0.measureRandomVariate; to:arrive0.parameterDuration }
NumberInput { id: number1; label:"1/λ"; initValue:1000; lower:1 }
Wire { from: number1.measureValue; to: rnd1.parameterMean }
RandomExponential { id:rnd1; seed:512 }
Wire { from:rnd1.measureRandomVariate; to:arrive1.parameterDuration }
NumberInput { id: number2; label:"1/λ"; initValue:2000; lower:1 }
Wire { from: number2.measureValue; to: rnd2.parameterMean }
RandomExponential { id:rnd2; seed:612 }
Wire { from:rnd2.measureRandomVariate; to:arrive2.parameterDuration }
Arrive { id: arrive0; symbol:"2"; label:"Minor Arrive" }
Pipe { from: arrive0.outlet; to: queue0.inlet }
Queue { id: queue0 }
Arrive { id: arrive1; symbol:"1"; label:"Urgent Arrive" }
Pipe { from: arrive1.outlet; to: queue1.inlet }
Queue { id: queue1 }
Arrive { id: arrive2; symbol:"0"; label:"Critical Arrive" }
Pipe { from: arrive2.outlet; to: queue2.inlet }
Queue { id: queue2 }
Pipe { from: queue0.outlet; to: perform0.inlet }
Pipe { from: queue1.outlet; to: perform0.inlet }
Pipe { from: queue2.outlet; to: perform0.inlet }
NumberInput { id: number4; initValue:1; label:"Capacity"; lower:1 }
Wire { from: number4.measureValue; to: perform0.parameterCapacity }
NumberInput { id: number3; label:"1/µ"; initValue:250; lower:1 }
Wire { from: number3.measureValue; to: rnd3.parameterMean }
RandomExponential { id:rnd3; seed:920 }
Wire { from:rnd3.measureRandomVariate; to:perform0.parameterDuration }
Perform { id: perform0 }
Get { id: get0; attribute:"symbol" }
Wire { from: get0.measureValue; to: decide0.parameterSelectedOutlet }
Decide { id: decide0; numOutlets:3 }
Pipe { from: get0.outlet; to: decide0.inlet }
Pipe { from: perform0.outlet; to: get0.inlet }
Pipe { from: decide0.outlets[0]; to: leave0.inlet }
Leave { id: leave0 }
Statistics { id: statistics0; height:48; width:128; label:"Critical\nsojourn\ntime" }
Wire { from: leave0.measureSojourn; to: statistics0.parameters[0] }
Pipe { from: decide0.outlets[1]; to: leave1.inlet }
Leave { id: leave1 }
Statistics { id: statistics1; height:48; width:128; label:"Urgent\nsojourn\ntime" }
Wire { from: leave1.measureSojourn; to: statistics1.parameters[0] }
Pipe { from: decide0.outlets[2]; to: leave2.inlet }
Leave { id: leave2 }
Statistics { id: statistics2; height:48; width:128; label:"Minor\nsojourn\ntime" }
Wire { from: leave2.measureSojourn; to: statistics2.parameters[0] }
Statistics { id: statistics3; height:48; width:128; label:"Overall\nsojourn\ntime" }
Wire { from: leave0.measureSojourn; to: statistics3.parameters[0] }
Wire { from: leave1.measureSojourn; to: statistics3.parameters[0] }
Wire { from: leave2.measureSojourn; to: statistics3.parameters[0] }
Steering { id: steering0; duration:1000000 }

Long run against theory ​

mscriticalurgentminoroverall
No Triage, simulated1453143415281491
Triage, simulated49464121321491
Triage, theory50065030502000

Theory: non-preemptive priority (Cobham), with σi\sigma_i the load of class ii and all classes above it: Wq,i=ρ/μ(1−σi−1)(1−σi)W_{q,i} = \dfrac{\rho/\mu}{(1-\sigma_{i-1})(1-\sigma_i)}, here σ=0.125, 0.375, 0.875\sigma = 0.125,\ 0.375,\ 0.875.

  • Critical and urgent match closely; minor and overall lag: at 1−σ=0.1251-\sigma = 0.125 even 1000 s is one short sample (week 2)

Capacity and arrival mix ​

When does triage matter? ​

Triage changes who waits. That only matters when somebody waits at all.

  • Few patients for the doctor: hardly anybody waits, the order does not matter
  • Busy doctor, few critical patients: triage helps them a lot at little cost
  • Many critical patients: they fill the doctor; minor patients starve

Three tasks, one per regime. Each time: predict, change the NumberInputs in both examples, run, fill in the table. Read the classes by the Leave counts.

Your turn A: plenty of capacity ​

Set Capacity = 2 in Triage and in No Triage (5 min) ​

ρ=0.8752≈0.44\rho = \frac{0.875}{2} \approx 0.44 per doctor.

mscriticalurgentminoroverall
No Triage
Triage

Predict first: how big will the difference between the rows be?

Run it A: two doctors ​

diagramdiagram
Model codeOpen in Quisim
qml
NumberInput { id: number0; label:"1/λ"; initValue:500; lower:1 }
Wire { from: number0.measureValue; to: rnd0.parameterMean }
RandomExponential { id:rnd0; seed:412 }
Wire { from:rnd0.measureRandomVariate; to:arrive0.parameterDuration }
NumberInput { id: number1; label:"1/λ"; initValue:1000; lower:1 }
Wire { from: number1.measureValue; to: rnd1.parameterMean }
RandomExponential { id:rnd1; seed:512 }
Wire { from:rnd1.measureRandomVariate; to:arrive1.parameterDuration }
NumberInput { id: number2; label:"1/λ"; initValue:2000; lower:1 }
Wire { from: number2.measureValue; to: rnd2.parameterMean }
RandomExponential { id:rnd2; seed:612 }
Wire { from:rnd2.measureRandomVariate; to:arrive2.parameterDuration }
Arrive { id: arrive0; symbol:"2"; label:"Minor Arrive" }
Pipe { from: arrive0.outlet; to: queue0.inlet }
Queue { id: queue0 }
Arrive { id: arrive1; symbol:"1"; label:"Urgent Arrive" }
Pipe { from: arrive1.outlet; to: queue1.inlet }
Queue { id: queue1 }
Arrive { id: arrive2; symbol:"0"; label:"Critical Arrive" }
Pipe { from: arrive2.outlet; to: queue2.inlet }
Queue { id: queue2 }
Pipe { from: queue0.outlet; to: perform0.inlet }
Pipe { from: queue1.outlet; to: perform0.inlet }
Pipe { from: queue2.outlet; to: perform0.inlet }
NumberInput { id: number4; initValue:2; label:"Capacity"; lower:1 }
Wire { from: number4.measureValue; to: perform0.parameterCapacity }
NumberInput { id: number3; label:"1/µ"; initValue:250; lower:1 }
Wire { from: number3.measureValue; to: rnd3.parameterMean }
RandomExponential { id:rnd3; seed:920 }
Wire { from:rnd3.measureRandomVariate; to:perform0.parameterDuration }
Perform { id: perform0 }
Get { id: get0; attribute:"symbol" }
Wire { from: get0.measureValue; to: decide0.parameterSelectedOutlet }
Decide { id: decide0; numOutlets:3 }
Pipe { from: get0.outlet; to: decide0.inlet }
Pipe { from: perform0.outlet; to: get0.inlet }
Pipe { from: decide0.outlets[0]; to: leave0.inlet }
Leave { id: leave0 }
Statistics { id: statistics0; height:48; width:128; label:"Critical\nsojourn\ntime" }
Wire { from: leave0.measureSojourn; to: statistics0.parameters[0] }
Pipe { from: decide0.outlets[1]; to: leave1.inlet }
Leave { id: leave1 }
Statistics { id: statistics1; height:48; width:128; label:"Urgent\nsojourn\ntime" }
Wire { from: leave1.measureSojourn; to: statistics1.parameters[0] }
Pipe { from: decide0.outlets[2]; to: leave2.inlet }
Leave { id: leave2 }
Statistics { id: statistics2; height:48; width:128; label:"Minor\nsojourn\ntime" }
Wire { from: leave2.measureSojourn; to: statistics2.parameters[0] }
Statistics { id: statistics3; height:48; width:128; label:"Overall\nsojourn\ntime" }
Wire { from: leave0.measureSojourn; to: statistics3.parameters[0] }
Wire { from: leave1.measureSojourn; to: statistics3.parameters[0] }
Wire { from: leave2.measureSojourn; to: statistics3.parameters[0] }

Both rows are identical: critical 392, urgent 276, minor 228, overall 263. Nobody overtakes anybody when nobody waits.

  • Sojourn ≈ treatment time: critical ranks last only by the luck of 4 long treatments

Your turn B: busy doctor, few critical patients ​

Set minor 1/λ = 400 in both examples (5 min) ​

ρ=250(1400+11000+12000)=1.0\rho = 250\left(\frac{1}{400}+\frac{1}{1000}+\frac{1}{2000}\right) = 1.0: the doctor never rests.

mscriticalurgentminoroverall
No Triage
Triage

Who gains how much, who pays how much? Is it a good trade for an emergency department?

Run it B: triage pays off ​

diagramdiagram
Model codeOpen in Quisim
qml
NumberInput { id: number0; label:"1/λ"; initValue:400; lower:1 }
Wire { from: number0.measureValue; to: rnd0.parameterMean }
RandomExponential { id:rnd0; seed:412 }
Wire { from:rnd0.measureRandomVariate; to:arrive0.parameterDuration }
NumberInput { id: number1; label:"1/λ"; initValue:1000; lower:1 }
Wire { from: number1.measureValue; to: rnd1.parameterMean }
RandomExponential { id:rnd1; seed:512 }
Wire { from:rnd1.measureRandomVariate; to:arrive1.parameterDuration }
NumberInput { id: number2; label:"1/λ"; initValue:2000; lower:1 }
Wire { from: number2.measureValue; to: rnd2.parameterMean }
RandomExponential { id:rnd2; seed:612 }
Wire { from:rnd2.measureRandomVariate; to:arrive2.parameterDuration }
Arrive { id: arrive0; symbol:"2"; label:"Minor Arrive" }
Pipe { from: arrive0.outlet; to: queue0.inlet }
Queue { id: queue0 }
Arrive { id: arrive1; symbol:"1"; label:"Urgent Arrive" }
Pipe { from: arrive1.outlet; to: queue1.inlet }
Queue { id: queue1 }
Arrive { id: arrive2; symbol:"0"; label:"Critical Arrive" }
Pipe { from: arrive2.outlet; to: queue2.inlet }
Queue { id: queue2 }
Pipe { from: queue0.outlet; to: perform0.inlet }
Pipe { from: queue1.outlet; to: perform0.inlet }
Pipe { from: queue2.outlet; to: perform0.inlet }
NumberInput { id: number4; initValue:1; label:"Capacity"; lower:1 }
Wire { from: number4.measureValue; to: perform0.parameterCapacity }
NumberInput { id: number3; label:"1/µ"; initValue:250; lower:1 }
Wire { from: number3.measureValue; to: rnd3.parameterMean }
RandomExponential { id:rnd3; seed:920 }
Wire { from:rnd3.measureRandomVariate; to:perform0.parameterDuration }
Perform { id: perform0 }
Get { id: get0; attribute:"symbol" }
Wire { from: get0.measureValue; to: decide0.parameterSelectedOutlet }
Decide { id: decide0; numOutlets:3 }
Pipe { from: get0.outlet; to: decide0.inlet }
Pipe { from: perform0.outlet; to: get0.inlet }
Pipe { from: decide0.outlets[0]; to: leave0.inlet }
Leave { id: leave0 }
Statistics { id: statistics0; height:48; width:128; label:"Critical\nsojourn\ntime" }
Wire { from: leave0.measureSojourn; to: statistics0.parameters[0] }
Pipe { from: decide0.outlets[1]; to: leave1.inlet }
Leave { id: leave1 }
Statistics { id: statistics1; height:48; width:128; label:"Urgent\nsojourn\ntime" }
Wire { from: leave1.measureSojourn; to: statistics1.parameters[0] }
Pipe { from: decide0.outlets[2]; to: leave2.inlet }
Leave { id: leave2 }
Statistics { id: statistics2; height:48; width:128; label:"Minor\nsojourn\ntime" }
Wire { from: leave2.measureSojourn; to: statistics2.parameters[0] }
Statistics { id: statistics3; height:48; width:128; label:"Overall\nsojourn\ntime" }
Wire { from: leave0.measureSojourn; to: statistics3.parameters[0] }
Wire { from: leave1.measureSojourn; to: statistics3.parameters[0] }
Wire { from: leave2.measureSojourn; to: statistics3.parameters[0] }
mscritical (4)urgent (8)minor (23)overall
No Triage625673715695
Triage296513828695

Critical waits less than half as long; each minor patient pays about 110 ms.

Your turn C: a surge of critical patients ​

Set critical 1/λ = 400, minor back to 500 (5 min) ​

ρ=250(1500+11000+1400)≈1.38\rho = 250\left(\frac{1}{500}+\frac{1}{1000}+\frac{1}{400}\right) \approx 1.38: more work than one doctor can do.

mscriticalurgentminoroverallminor patients treated
No Triage
Triage

Predict: what happens to the minor patients under triage?

Run it C: minor patients starve ​

diagramdiagram
Model codeOpen in Quisim
qml
NumberInput { id: number0; label:"1/λ"; initValue:500; lower:1 }
Wire { from: number0.measureValue; to: rnd0.parameterMean }
RandomExponential { id:rnd0; seed:412 }
Wire { from:rnd0.measureRandomVariate; to:arrive0.parameterDuration }
NumberInput { id: number1; label:"1/λ"; initValue:1000; lower:1 }
Wire { from: number1.measureValue; to: rnd1.parameterMean }
RandomExponential { id:rnd1; seed:512 }
Wire { from:rnd1.measureRandomVariate; to:arrive1.parameterDuration }
NumberInput { id: number2; label:"1/λ"; initValue:400; lower:1 }
Wire { from: number2.measureValue; to: rnd2.parameterMean }
RandomExponential { id:rnd2; seed:612 }
Wire { from:rnd2.measureRandomVariate; to:arrive2.parameterDuration }
Arrive { id: arrive0; symbol:"2"; label:"Minor Arrive" }
Pipe { from: arrive0.outlet; to: queue0.inlet }
Queue { id: queue0 }
Arrive { id: arrive1; symbol:"1"; label:"Urgent Arrive" }
Pipe { from: arrive1.outlet; to: queue1.inlet }
Queue { id: queue1 }
Arrive { id: arrive2; symbol:"0"; label:"Critical Arrive" }
Pipe { from: arrive2.outlet; to: queue2.inlet }
Queue { id: queue2 }
Pipe { from: queue0.outlet; to: perform0.inlet }
Pipe { from: queue1.outlet; to: perform0.inlet }
Pipe { from: queue2.outlet; to: perform0.inlet }
NumberInput { id: number4; initValue:1; label:"Capacity"; lower:1 }
Wire { from: number4.measureValue; to: perform0.parameterCapacity }
NumberInput { id: number3; label:"1/µ"; initValue:250; lower:1 }
Wire { from: number3.measureValue; to: rnd3.parameterMean }
RandomExponential { id:rnd3; seed:920 }
Wire { from:rnd3.measureRandomVariate; to:perform0.parameterDuration }
Perform { id: perform0 }
Get { id: get0; attribute:"symbol" }
Wire { from: get0.measureValue; to: decide0.parameterSelectedOutlet }
Decide { id: decide0; numOutlets:3 }
Pipe { from: get0.outlet; to: decide0.inlet }
Pipe { from: perform0.outlet; to: get0.inlet }
Pipe { from: decide0.outlets[0]; to: leave0.inlet }
Leave { id: leave0 }
Statistics { id: statistics0; height:48; width:128; label:"Critical\nsojourn\ntime" }
Wire { from: leave0.measureSojourn; to: statistics0.parameters[0] }
Pipe { from: decide0.outlets[1]; to: leave1.inlet }
Leave { id: leave1 }
Statistics { id: statistics1; height:48; width:128; label:"Urgent\nsojourn\ntime" }
Wire { from: leave1.measureSojourn; to: statistics1.parameters[0] }
Pipe { from: decide0.outlets[2]; to: leave2.inlet }
Leave { id: leave2 }
Statistics { id: statistics2; height:48; width:128; label:"Minor\nsojourn\ntime" }
Wire { from: leave2.measureSojourn; to: statistics2.parameters[0] }
Statistics { id: statistics3; height:48; width:128; label:"Overall\nsojourn\ntime" }
Wire { from: leave0.measureSojourn; to: statistics3.parameters[0] }
Wire { from: leave1.measureSojourn; to: statistics3.parameters[0] }
Wire { from: leave2.measureSojourn; to: statistics3.parameters[0] }
mscriticalurgentminoroverall
No Triage162611691408 (13 treated)1479
Triage5416023410 (10 treated)1307

Overloaded: the queue of minor patients only grows. Both models treat 38 patients, but not the same 38, so Overall now differs.

Key insight: triage is a policy ​

Key insight ​

Triage is not a free improvement: it moves waiting time from urgent to non-urgent patients, and how much depends on ρ\rho and the mix.

Discussion (5 min) ​

  • A minor patient waits 3.4 s instead of 1.4 s (scale up: hours instead of one). Acceptable? Who decides?
  • What would you add to protect minor patients? (A second doctor for minors only? An upper limit on waiting?)
  • Real minor patients do not wait forever: some go home. That is the next example.

Bonus: for fast students ​

Bonus: Renege: patients who give up waiting ​

Model codeOpen in Quisim
qml
RandomExponential { id:rnd0; mean:100; seed:88 }
RandomExponential { id:rnd1; mean:100; seed:18 }
Wire { from:rnd0.measureRandomVariate; to:arrive0.parameterDuration }
Wire { from:rnd1.measureRandomVariate; to:perform0.parameterDuration }
Arrive { id:arrive0 }
Pipe { from:arrive0.outlet; to:queue0.inlet }
Queue { id:queue0; renegeDuration:100 }
Pipe { from:queue0.outlet; to:perform0.inlet }
Pipe { from:queue0.outRenege; to:leave1.inlet }
Perform { id:perform0 }
Pipe { from:perform0.outlet; to:leave0.inlet }
Leave { id:leave0 }
Leave { id:leave1; label:"Renege" }

Your turn ​

Arrivals and treatments both average 100 ms (ρ=1\rho = 1); after 100 ms in the Queue a patient leaves through Renege. Predict the share who give up, then run and count: Arrive ≈ 89, served 58, reneged 29. Without reneging, what would the queue do at ρ=1\rho = 1?

Bonus: Decide: routing by a random number ​

Model codeOpen in Quisim
qml
Arrive { id:arrive0; duration:100 }
Pipe { from:arrive0.outlet; to:decide0.inlet }
RandomUniform { id: rnd1; upper:2; floor:true }
Wire { from: rnd1.measureRandomVariate; to: decide0.parameterSelectedOutlet }
Decide { id:decide0 }
Pipe { from:decide0.outlets[0]; to:leave0.inlet }
Pipe { from:decide0.outlets[1]; to:leave1.inlet }
Leave { id:leave0 }
Leave { id:leave1 }

Your turn ​

RandomUniform with floor gives 0 or 1: the outlet. Use Step and the Monitor tab to follow 5 patients. Start clean five times and note both Leave counts (Arrive has no random duration: exactly 11 arrivals every run). Can the split ever be exactly even? How often do you get 5:6 or 6:5 (theory: about 45 %)? The same Decide sorts the classes in Triage, there driven by Get.

Bonus: Joining the shorter line ​

Model codeOpen in Quisim
qml
NumberInput { id: number0; label:"1/λ"; initValue:500; lower:1 }
Wire { from: number0.measureValue; to: rnd0.parameterMean }
RandomExponential { id:rnd0}
Wire { from:rnd0.measureRandomVariate; to:arrive0.parameterDuration }
Arrive { id: arrive0 }
Function { id: function0; numParameters:2; code:"M[0] = (P[0]<=P[1] ? 0 : 1)" }
Wire { from: function0.measures[0]; to: decide0.parameterSelectedOutlet }
Wire { from: queue0.measureLoad; to: function0.parameters[0] }
Wire { from: queue1.measureLoad; to: function0.parameters[1] }
Decide { id: decide0 }
Pipe { from: arrive0.outlet; to: decide0.inlet }
Pipe { from: decide0.outlets[0]; to: queue0.inlet }
Pipe { from: decide0.outlets[1]; to: queue1.inlet }
Queue { id: queue0 }
Queue { id: queue1 }
Pipe { from: queue0.outlet; to: perform0.inlet }
Pipe { from: queue1.outlet; to: perform1.inlet }
Perform { id: perform0 }
Perform { id: perform1 }
Pipe { from: perform0.outlet; to: leave0.inlet }
Pipe { from: perform1.outlet; to: leave0.inlet }
Leave { id: leave0 }

Your turn: two servers, 1000 ms each, separate lines ​

1/λ1000500400250
arrived / left / both queues at the end

Predict where left stops growing. (Answer: about 19, two servers × 10 s / 1000 ms.) Are the two queues ever far apart?

Wrap-up ​

What we learned this week ​

  • Strict priority makes each class see a smaller system: critical patients barely wait
  • Priority redistributes waiting; with the same patients and work, Overall stays the same
  • The effect depends on the regime: none at low load, large gains near ρ=1\rho = 1, starvation in overload
  • Check a model with counts before trusting its numbers
  • Short runs with few patients mislead; long runs agree with priority-queue theory

Three weeks in review ​

Covered

  • DES: events, state, time advance
  • Queues, buffering, ρ\rho
  • Variability (M/D/1 vs M/M/1)
  • Multi-server systems, capacity
  • Replications and confidence intervals
  • Simulation vs Erlang C
  • Priorities as policy

A longer course adds

  • Networks of queues, tandem lines (Two-Step)
  • Routing and load balancing (To Shorter, Select Longer)
  • Batching (Pack, Pack→Unpack)
  • Steady-state vs transient analysis, warm-up
  • Variance reduction, sensitivity (Sensitivity), optimization

All examples in italics are in the Examples tab: try them.

Take-home ​

A priority system from your own life ​

  1. Pick a system with priorities: a clinic, an IT help desk, airport security, a print queue
  2. Name the classes and guess their 1/λ1/\lambda and the mean service time
  3. Put the numbers into Triage and No Triage (or your own model) and fill in the per-class table
  4. Who pays for the priority, and how much? Would you keep it?

Bring your table: we compare the trade-offs in the next session.

For the lecturer ​

Running this session ​

  • Timing: recap + demo 10 min, Build it 20 min, Triage and verification 15 min, No Triage and conservation 15 min, three regimes 20 min, discussion 10 min; bonus for fast students
  • Everyone gets the same Triage/No Triage numbers (fixed seeds), so the result tables can be compared across the room; the Build it models are unseeded and differ
  • Ask for a show of hands before every run: "better, same or worse for minor patients?"
  • The identical Overall is the surprise of the session: give it time

Notes on the models ​

  • The default run is 10 s: about 30 patients, only 4 critical. Per-class numbers are noisy; the long-run slide shows the stable values
  • Renege and Decide have no NumberInput: demo users can run and count, not change values

Previous: Week 2: Statistical thinking — replications and confidence intervals

Course page: A 3-week course with Quisim