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
- Variability and ρ 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


Model codeOpen in Quisim
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). ρ=125100=0.8, theory W=μ−λ1=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:
- 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
- Two RandomExponential: wire the value output (V) of one to Arrive's duration (D), of the other to Perform's duration
- Two NumberInput into the mean (µ) of each RandomExponential. Type 2000 into the one for arrivals (1/λ), 250 into the one for treatment (1/µ)
- Statistics on Leave's sojourn (LS), Chart on Queue's load (L)
Build it: the result


Model codeOpen in Quisim
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. ρ=2000250=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=2501−200011≈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.


Model codeOpen in Quisim
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 }λ=5001+10001+20001=2861: minor, urgent and critical together, ρ=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=2501−28611≈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 line | 1/λ | ρ | theory W | run 1 | run 2 | run 3 |
|---|---|---|---|---|---|---|
| critical only | 2000 | 0.125 | 286 | |||
| critical + urgent | 667 | 0.375 | 400 | |||
| everybody | 286 | 0.875 | 2000 |
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/λ = 500 | 1/λ = 1000 | 1/λ = 2000 | 1/µ = 250 | Capacity = 1 |
|---|---|---|---|---|
| minor | urgent | critical | treatment | doctors |
ρ=250(5001+10001+20001)=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 Arrive | Urgent Arrive | Critical 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) | |
|---|---|---|---|---|
| Triage | 720 | 315 | 600 | 540 |
- 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:
| ms | critical (4) | urgent (8) | minor (18) | overall (30) |
|---|---|---|---|---|
| No Triage | 745 | 451 | 535 | 540.37 |
| Triage | 720 | 315 | 600 | 540.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 ρ, 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 and Leave count ni per class:
| critical | urgent | minor | |
|---|---|---|---|
| Wˉi Triage | |||
| Wˉi No Triage | |||
| ni |
Compute ∑ini∑iniWˉi for both. Do you get the Overall box?
304⋅720+8⋅315+18⋅600≈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).


Model codeOpen in Quisim
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
| ms | critical | urgent | minor | overall |
|---|---|---|---|---|
| No Triage, simulated | 1453 | 1434 | 1528 | 1491 |
| Triage, simulated | 494 | 641 | 2132 | 1491 |
| Triage, theory | 500 | 650 | 3050 | 2000 |
Theory: non-preemptive priority (Cobham), with σi the load of class i and all classes above it: Wq,i=(1−σi−1)(1−σi)ρ/μ, here σ=0.125, 0.375, 0.875.
- Critical and urgent match closely; minor and overall lag: at 1−σ=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)
ρ=20.875≈0.44 per doctor.
| ms | critical | urgent | minor | overall |
|---|---|---|---|---|
| No Triage | ||||
| Triage |
Predict first: how big will the difference between the rows be?
Run it A: two doctors


Model codeOpen in Quisim
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(4001+10001+20001)=1.0: the doctor never rests.
| ms | critical | urgent | minor | overall |
|---|---|---|---|---|
| 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


Model codeOpen in Quisim
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] }| ms | critical (4) | urgent (8) | minor (23) | overall |
|---|---|---|---|---|
| No Triage | 625 | 673 | 715 | 695 |
| Triage | 296 | 513 | 828 | 695 |
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(5001+10001+4001)≈1.38: more work than one doctor can do.
| ms | critical | urgent | minor | overall | minor patients treated |
|---|---|---|---|---|---|
| No Triage | |||||
| Triage |
Predict: what happens to the minor patients under triage?
Run it C: minor patients starve


Model codeOpen in Quisim
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] }| ms | critical | urgent | minor | overall |
|---|---|---|---|---|
| No Triage | 1626 | 1169 | 1408 (13 treated) | 1479 |
| Triage | 541 | 602 | 3410 (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 ρ 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
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); 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?
Bonus: Decide: routing by a random number
Model codeOpen in Quisim
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
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/λ | 1000 | 500 | 400 | 250 |
|---|---|---|---|---|
| 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, 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, ρ
- 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
- Pick a system with priorities: a clinic, an IT help desk, airport security, a print queue
- Name the classes and guess their 1/λ and the mean service time
- Put the numbers into Triage and No Triage (or your own model) and fill in the per-class table
- 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
Links
Previous: Week 2: Statistical thinking — replications and confidence intervals
Course page: A 3-week course with Quisim