Case Study: Bottlenecks in an Aluminum Extrusion Line
Learning Objectives
- Model a multi-stage production line with batch processes
- Find the bottleneck from utilizations and queue lengths
- Model machine breakdowns with a state token that switches the machine's capacity
- Model sequence-independent setups (die changes) as campaigns with a shared press token
- Compare configurations and explain the results with Little's law
Problem Description
A plant produces aluminum profiles. Billets (cylindrical aluminum blanks) pass through seven stations:
- Heating furnace – heats each billet to extrusion temperature
- Extrusion press – presses the billet through a die into a profile
- Cooling table – the profile cools down
- Stretcher – straightens the profile
- Saw – cuts the profile to length
- Aging oven – hardens the profiles in racks (batch process)
- Packaging
The press needs a die change whenever the profile changes, and press and saw break down from time to time.
Objective: Find the bottleneck, quantify how breakdowns and die changes affect the lead time, and choose a campaign size (billets per die) for the press.
All data is virtual and chosen for teaching.
Data
Billets arrive with exponential inter-arrival times, mean 5 min (12 billets/h). Variations (±) are standard deviations of normal distributions.
| # | Station | Capacity | Time | Utilization |
|---|---|---|---|---|
| 1 | Heating furnace | 1 | 3 ± 0.3 min | 60% |
| 2 | Extrusion press | 1 | 3.5 ± 0.35 min | 70% |
| 3 | Cooling table | 6 places | 10 min | 33% |
| 4 | Stretcher | 1 | 2 ± 0.2 min | 40% |
| 5 | Saw | 1 | 3 ± 0.5 min | 60% |
| 6 | Aging oven | 2 ovens | 5 h per rack of 40 billets | 75% |
| 7 | Packaging | 2 workers | 2 ± 0.3 min | 20% |
| Disturbance | Data |
|---|---|
| Die change at the press | 30 ± 5 min, once per campaign of 40 billets |
| Press breakdowns | MTBF 8 h, MTTR 30 min (both exponential) |
| Saw breakdowns | MTBF 4 h, MTTR 15 min (both exponential) |
Time scale. One time unit is 1 ms of animation time, and 1 minute = 1000 time units, so the animation runs at a watchable speed. One run covers one week of 24/7 operation: 10,080 min = 1.008e7. Divide times reported by Quisim by 1000 to get minutes.
All models below run 10 replications of one week each. The line starts empty and no warm-up is removed (see Exercise 4).
Step 1: Base Model Without Disturbances
Each station is a Queue–Perform pair. The aging oven collects 40 billets on a rack with a Pack block (the empty racks come from a Provide block), the two ovens are one Perform with capacity:2, and an Unpack block releases the billets after aging.


Model codeOpen in Quisim
// Aluminum extrusion line, step 1: base model without disturbances
// Time scale: 1 min = 1000 time units. One run = 1 week, 24/7.
Steering { id:steering0; duration:1.008e7; replications:10; seed:1 }
// Billet arrivals: exponential, mean 5 min
RandomExponential { id:rndArrive; mean:5000; seed:11 }
Wire { from:rndArrive.measureRandomVariate; to:arrive0.parameterDuration }
Arrive { id:arrive0; label:"Billets" }
// 1 Heating furnace: 3 min +- 0.3
Pipe { from:arrive0.outlet; to:queueHeat.inlet }
Queue { id:queueHeat; label:"Q Heating" }
Pipe { from:queueHeat.outlet; to:heat.inlet }
RandomNormal { id:rndHeat; mean:3000; stddev:300; seed:12 }
Wire { from:rndHeat.measureRandomVariate; to:heat.parameterDuration }
Perform { id:heat; label:"Heating" }
// 2 Extrusion press: 3.5 min +- 0.35
Pipe { from:heat.outlet; to:queuePress.inlet }
Queue { id:queuePress; label:"Q Press" }
Pipe { from:queuePress.outlet; to:press.inlet }
RandomNormal { id:rndPress; mean:3500; stddev:350; seed:14 }
Wire { from:rndPress.measureRandomVariate; to:press.parameterDuration }
Perform { id:press; label:"Press" }
// 3 Cooling table: 10 min, 6 places
Pipe { from:press.outlet; to:queueCool.inlet }
Queue { id:queueCool; label:"Q Cooling" }
Pipe { from:queueCool.outlet; to:cool.inlet }
Perform { id:cool; label:"Cooling"; duration:10000; capacity:6 }
// 4 Stretcher: 2 min +- 0.2
Pipe { from:cool.outlet; to:queueStretch.inlet }
Queue { id:queueStretch; label:"Q Stretcher" }
Pipe { from:queueStretch.outlet; to:stretch.inlet }
RandomNormal { id:rndStretch; mean:2000; stddev:200; seed:17 }
Wire { from:rndStretch.measureRandomVariate; to:stretch.parameterDuration }
Perform { id:stretch; label:"Stretcher" }
// 5 Saw: 3 min +- 0.5
Pipe { from:stretch.outlet; to:queueSaw.inlet }
Queue { id:queueSaw; label:"Q Saw" }
Pipe { from:queueSaw.outlet; to:saw.inlet }
RandomNormal { id:rndSaw; mean:3000; stddev:500; seed:18 }
Wire { from:rndSaw.measureRandomVariate; to:saw.parameterDuration }
Perform { id:saw; label:"Saw" }
// 6 Aging oven: racks of 40 billets, 2 ovens, 5 h per rack
Pipe { from:saw.outlet; to:packOven.inlet }
Provide { id:provideRack; label:"Racks" }
Pipe { from:provideRack.outlet; to:packOven.inPacket }
Pack { id:packOven; label:"Load rack"; capacity:40 }
Pipe { from:packOven.outlet; to:queueOven.inlet }
Queue { id:queueOven; label:"Q Oven" }
Pipe { from:queueOven.outlet; to:oven.inlet }
Perform { id:oven; label:"Aging oven"; duration:300000; capacity:2 }
Pipe { from:oven.outlet; to:unpackOven.inlet }
Unpack { id:unpackOven; label:"Unload rack" }
Pipe { from:unpackOven.outPack; to:leaveRack.inlet }
Leave { id:leaveRack; label:"Empty racks" }
// 7 Packaging: 2 workers, 2 min +- 0.3
Pipe { from:unpackOven.outlet; to:queuePackaging.inlet }
Queue { id:queuePackaging; label:"Q Packaging" }
Pipe { from:queuePackaging.outlet; to:packaging.inlet }
RandomNormal { id:rndPackaging; mean:2000; stddev:300; seed:21 }
Wire { from:rndPackaging.measureRandomVariate; to:packaging.parameterDuration }
Perform { id:packaging; label:"Packaging"; capacity:2 }
Pipe { from:packaging.outlet; to:leave0.inlet }
Leave { id:leave0; label:"Finished goods" }
// KPIs: busy servers per station (divide by capacity for utilization)
Wire { from:heat.measureLoad; to:statHeat.parameters[0] }
Statistics { id:statHeat; label:"Heating busy"; timeAverage:true; height:250; width:142 }
Wire { from:press.measureLoad; to:statPress.parameters[0] }
Statistics { id:statPress; label:"Press busy"; timeAverage:true; height:250; width:142 }
Wire { from:cool.measureLoad; to:statCool.parameters[0] }
Statistics { id:statCool; label:"Cooling busy"; timeAverage:true; height:250; width:142 }
Wire { from:stretch.measureLoad; to:statStretch.parameters[0] }
Statistics { id:statStretch; label:"Stretcher busy"; timeAverage:true; height:250; width:142 }
Wire { from:saw.measureLoad; to:statSaw.parameters[0] }
Statistics { id:statSaw; label:"Saw busy"; timeAverage:true; height:250; width:142 }
Wire { from:oven.measureLoad; to:statOven.parameters[0] }
Statistics { id:statOven; label:"Ovens busy"; timeAverage:true; height:250; width:142 }
Wire { from:packaging.measureLoad; to:statPackaging.parameters[0] }
Statistics { id:statPackaging; label:"Packaging busy"; timeAverage:true; height:250; width:142 }
// KPIs: queue lengths
Wire { from:queuePress.measureLoad; to:statQPress.parameters[0] }
Statistics { id:statQPress; label:"Q Press"; timeAverage:true; height:250; width:142 }
Wire { from:queueSaw.measureLoad; to:statQSaw.parameters[0] }
Statistics { id:statQSaw; label:"Q Saw"; timeAverage:true; height:250; width:142 }
Wire { from:queuePackaging.measureLoad; to:statQPackaging.parameters[0] }
Statistics { id:statQPackaging; label:"Q Packaging"; timeAverage:true; height:250; width:142 }
// KPIs: output per week and lead time per billet (time units / 1000 = min)
Wire { from:leave0.measureNumPassed; to:statOutput.parameters[0] }
Statistics { id:statOutput; label:"Output"; cumulative:true; height:250; width:142 }
Wire { from:leave0.measureSojourn; to:statLeadTime.parameters[0] }
Statistics { id:statLeadTime; label:"Lead time"; height:250; width:142 }Results
| Station | Busy servers | Utilization | Avg. queue |
|---|---|---|---|
| Heating | 0.60 ± 0.01 | 60% | |
| Press | 0.70 ± 0.01 | 70% | 0.36 ± 0.04 |
| Cooling | 2.01 ± 0.03 | 34% | |
| Stretcher | 0.40 ± 0.01 | 40% | |
| Saw | 0.60 ± 0.01 | 60% | 0.02 ± 0.00 |
| Aging oven | 1.47 ± 0.02 | 74% | |
| Packaging | 0.39 ± 0.01 | 20% | 3.67 ± 0.05 |
- Output: 1943 ± 29 billets per week
- Lead time: 443,594 ± 1,299 time units = 443.6 ± 1.3 min → 7.4 h
The simulated utilizations match the hand calculation from the data table. The press has the highest utilization of the single machines, the aging oven the highest overall.
Where does the lead time come from? The lead time is dominated by the batch process, not by queues:
| Component | Time |
|---|---|
| Aging | 300 min |
| Waiting until the rack holds 40 billets: on average min | ≈ 98 min |
| Processing at the other stations: | 23.5 min |
| Waiting at packaging (Little's law: per min) | ≈ 18 min |
| Sum | ≈ 440 min |
The queue at packaging is not a capacity problem (20% utilization). It appears because the oven releases 40 billets at once.
Step 2: Breakdowns at Press and Saw
Each machine gets a state token: one entity (Provide, limited to one by a Meter with capacity:1) that cycles forever between a "… available" Perform (time to failure) and a "… down" Perform (repair time). The load of "… available" is 1 while the machine works and 0 while it is broken, and it is wired directly to the machine's parameterCapacity. A billet already on the machine is finished when the capacity drops to 0, but no new billet starts. See also the breakdown recipe in the cookbook.
The expected availability of both machines is .


Model codeOpen in Quisim
// Aluminum extrusion line, step 2: breakdowns at press and saw
// Time scale: 1 min = 1000 time units. One run = 1 week, 24/7.
Steering { id:steering0; duration:1.008e7; replications:10; seed:1 }
// Billet arrivals: exponential, mean 5 min
RandomExponential { id:rndArrive; mean:5000; seed:11 }
Wire { from:rndArrive.measureRandomVariate; to:arrive0.parameterDuration }
Arrive { id:arrive0; label:"Billets" }
// 1 Heating furnace: 3 min +- 0.3
Pipe { from:arrive0.outlet; to:queueHeat.inlet }
Queue { id:queueHeat; label:"Q Heating" }
Pipe { from:queueHeat.outlet; to:heat.inlet }
RandomNormal { id:rndHeat; mean:3000; stddev:300; seed:12 }
Wire { from:rndHeat.measureRandomVariate; to:heat.parameterDuration }
Perform { id:heat; label:"Heating" }
// 2 Extrusion press: 3.5 min +- 0.35, capacity 0 while broken down
Pipe { from:heat.outlet; to:queuePress.inlet }
Queue { id:queuePress; label:"Q Press" }
Pipe { from:queuePress.outlet; to:press.inlet }
RandomNormal { id:rndPress; mean:3500; stddev:350; seed:14 }
Wire { from:rndPress.measureRandomVariate; to:press.parameterDuration }
Perform { id:press; label:"Press" }
// Press breakdowns: MTBF 8 h, MTTR 30 min (exponential)
Provide { id:providePressState; label:"Press state" }
Pipe { from:providePressState.outlet; to:meterPressState.inlet }
Meter { id:meterPressState; capacity:1 }
Pipe { from:meterPressState.outlet; to:pressUp.inlet }
RandomExponential { id:rndPressMtbf; mean:480000; seed:15 }
Wire { from:rndPressMtbf.measureRandomVariate; to:pressUp.parameterDuration }
Perform { id:pressUp; label:"Press available" }
Pipe { from:pressUp.outlet; to:pressDown.inlet }
RandomExponential { id:rndPressMttr; mean:30000; seed:16 }
Wire { from:rndPressMttr.measureRandomVariate; to:pressDown.parameterDuration }
Perform { id:pressDown; label:"Press down" }
Pipe { from:pressDown.outlet; to:pressUp.inlet }
Wire { from:pressUp.measureLoad; to:press.parameterCapacity }
// 3 Cooling table: 10 min, 6 places
Pipe { from:press.outlet; to:queueCool.inlet }
Queue { id:queueCool; label:"Q Cooling" }
Pipe { from:queueCool.outlet; to:cool.inlet }
Perform { id:cool; label:"Cooling"; duration:10000; capacity:6 }
// 4 Stretcher: 2 min +- 0.2
Pipe { from:cool.outlet; to:queueStretch.inlet }
Queue { id:queueStretch; label:"Q Stretcher" }
Pipe { from:queueStretch.outlet; to:stretch.inlet }
RandomNormal { id:rndStretch; mean:2000; stddev:200; seed:17 }
Wire { from:rndStretch.measureRandomVariate; to:stretch.parameterDuration }
Perform { id:stretch; label:"Stretcher" }
// 5 Saw: 3 min +- 0.5, capacity 0 while broken down
Pipe { from:stretch.outlet; to:queueSaw.inlet }
Queue { id:queueSaw; label:"Q Saw" }
Pipe { from:queueSaw.outlet; to:saw.inlet }
RandomNormal { id:rndSaw; mean:3000; stddev:500; seed:18 }
Wire { from:rndSaw.measureRandomVariate; to:saw.parameterDuration }
Perform { id:saw; label:"Saw" }
// Saw breakdowns: MTBF 4 h, MTTR 15 min (exponential)
Provide { id:provideSawState; label:"Saw state" }
Pipe { from:provideSawState.outlet; to:meterSawState.inlet }
Meter { id:meterSawState; capacity:1 }
Pipe { from:meterSawState.outlet; to:sawUp.inlet }
RandomExponential { id:rndSawMtbf; mean:240000; seed:19 }
Wire { from:rndSawMtbf.measureRandomVariate; to:sawUp.parameterDuration }
Perform { id:sawUp; label:"Saw available" }
Pipe { from:sawUp.outlet; to:sawDown.inlet }
RandomExponential { id:rndSawMttr; mean:15000; seed:20 }
Wire { from:rndSawMttr.measureRandomVariate; to:sawDown.parameterDuration }
Perform { id:sawDown; label:"Saw down" }
Pipe { from:sawDown.outlet; to:sawUp.inlet }
Wire { from:sawUp.measureLoad; to:saw.parameterCapacity }
// 6 Aging oven: racks of 40 billets, 2 ovens, 5 h per rack
Pipe { from:saw.outlet; to:packOven.inlet }
Provide { id:provideRack; label:"Racks" }
Pipe { from:provideRack.outlet; to:packOven.inPacket }
Pack { id:packOven; label:"Load rack"; capacity:40 }
Pipe { from:packOven.outlet; to:queueOven.inlet }
Queue { id:queueOven; label:"Q Oven" }
Pipe { from:queueOven.outlet; to:oven.inlet }
Perform { id:oven; label:"Aging oven"; duration:300000; capacity:2 }
Pipe { from:oven.outlet; to:unpackOven.inlet }
Unpack { id:unpackOven; label:"Unload rack" }
Pipe { from:unpackOven.outPack; to:leaveRack.inlet }
Leave { id:leaveRack; label:"Empty racks" }
// 7 Packaging: 2 workers, 2 min +- 0.3
Pipe { from:unpackOven.outlet; to:queuePackaging.inlet }
Queue { id:queuePackaging; label:"Q Packaging" }
Pipe { from:queuePackaging.outlet; to:packaging.inlet }
RandomNormal { id:rndPackaging; mean:2000; stddev:300; seed:21 }
Wire { from:rndPackaging.measureRandomVariate; to:packaging.parameterDuration }
Perform { id:packaging; label:"Packaging"; capacity:2 }
Pipe { from:packaging.outlet; to:leave0.inlet }
Leave { id:leave0; label:"Finished goods" }
// KPIs: busy and down shares of press and saw
Wire { from:press.measureLoad; to:statPress.parameters[0] }
Statistics { id:statPress; label:"Press busy"; timeAverage:true; height:250; width:142 }
Wire { from:pressDown.measureLoad; to:statPressDown.parameters[0] }
Statistics { id:statPressDown; label:"Press down"; timeAverage:true; height:250; width:142 }
Wire { from:saw.measureLoad; to:statSaw.parameters[0] }
Statistics { id:statSaw; label:"Saw busy"; timeAverage:true; height:250; width:142 }
Wire { from:sawDown.measureLoad; to:statSawDown.parameters[0] }
Statistics { id:statSawDown; label:"Saw down"; timeAverage:true; height:250; width:142 }
// KPIs: queues in front of press and saw
Wire { from:queuePress.measureLoad; to:statQPress.parameters[0] }
Statistics { id:statQPress; label:"Q Press"; timeAverage:true; height:250; width:142 }
Wire { from:queueSaw.measureLoad; to:statQSaw.parameters[0] }
Statistics { id:statQSaw; label:"Q Saw"; timeAverage:true; height:250; width:142 }
// KPIs: output per week and lead time per billet (time units / 1000 = min)
Wire { from:leave0.measureNumPassed; to:statOutput.parameters[0] }
Statistics { id:statOutput; label:"Output"; cumulative:true; height:250; width:142 }
Wire { from:leave0.measureSojourn; to:statLeadTime.parameters[0] }
Statistics { id:statLeadTime; label:"Lead time"; height:250; width:142 }Results
| KPI | Step 1 | Step 2 |
|---|---|---|
| Press down | – | 0.06 ± 0.01 |
| Saw down | – | 0.06 ± 0.01 |
| Avg. queue at press | 0.36 ± 0.04 | 1.73 ± 0.47 |
| Avg. queue at saw | 0.02 ± 0.00 | 0.54 ± 0.11 |
| Lead time | 7.4 h | 7.6 h |
| Output per week | 1943 ± 29 | 1943 ± 29 |
A capacity loss of only 6% makes the queue at the press five times longer and the queue at the saw grow from almost nothing. During each repair, billets keep arriving and pile up; afterwards the machine needs a long time to work off the backlog. The output does not change: the line still has enough capacity to process every billet within the week.
Step 3: Die Changes as Campaigns
The press produces in campaigns of 40 billets that share one die:
Collect campaign(Pack,capacity:40) groups 40 heated billets into one campaign entity.Start campaign(Lineup) starts a campaign only together with the single press token, so only one campaign at a time is in die change or pressing.Die change(Perform) takes 30 ± 5 min per campaign.Release billets(Unpack) hands the billets to the press one by one.- When the campaign is empty, Unpack sends the empty campaign out of
outPack.End campaign(Lineup) pairs it with the waiting token and sends the token back toStart campaign.


Model codeOpen in Quisim
// Aluminum extrusion line, step 3: die changes and breakdowns
// Time scale: 1 min = 1000 time units. One run = 1 week, 24/7.
Steering { id:steering0; duration:1.008e7; replications:10; seed:1 }
// Billet arrivals: exponential, mean 5 min
RandomExponential { id:rndArrive; mean:5000; seed:11 }
Wire { from:rndArrive.measureRandomVariate; to:arrive0.parameterDuration }
Arrive { id:arrive0; label:"Billets" }
// 1 Heating furnace: 3 min +- 0.3
Pipe { from:arrive0.outlet; to:queueHeat.inlet }
Queue { id:queueHeat; label:"Q Heating" }
Pipe { from:queueHeat.outlet; to:heat.inlet }
RandomNormal { id:rndHeat; mean:3000; stddev:300; seed:12 }
Wire { from:rndHeat.measureRandomVariate; to:heat.parameterDuration }
Perform { id:heat; label:"Heating" }
// 2 Extrusion press with die campaigns of 40 billets
Pipe { from:heat.outlet; to:packCampaign.inlet }
Provide { id:provideCampaign; label:"Campaigns" }
Pipe { from:provideCampaign.outlet; to:packCampaign.inPacket }
Pack { id:packCampaign; label:"Collect campaign"; capacity:40 }
Pipe { from:packCampaign.outlet; to:queueCampaign.inlet }
Queue { id:queueCampaign; label:"Q Campaigns" }
// Press token: only one campaign at a time in die change or pressing
Provide { id:provideToken; label:"Press token" }
Pipe { from:provideToken.outlet; to:meterToken.inlet }
Meter { id:meterToken; capacity:1 }
Pipe { from:meterToken.outlet; to:lineupStart.inlets[1] }
Pipe { from:queueCampaign.outlet; to:lineupStart.inlets[0] }
Lineup { id:lineupStart; label:"Start campaign" }
// Die change: 30 min +- 5
Pipe { from:lineupStart.outlets[0]; to:setup.inlet }
RandomNormal { id:rndSetup; mean:30000; stddev:5000; seed:13 }
Wire { from:rndSetup.measureRandomVariate; to:setup.parameterDuration }
Perform { id:setup; label:"Die change" }
Pipe { from:setup.outlet; to:unpackCampaign.inlet }
Unpack { id:unpackCampaign; label:"Release billets" }
// Press: 3.5 min +- 0.35 per billet, capacity 0 while broken down
Pipe { from:unpackCampaign.outlet; to:press.inlet }
RandomNormal { id:rndPress; mean:3500; stddev:350; seed:14 }
Wire { from:rndPress.measureRandomVariate; to:press.parameterDuration }
Perform { id:press; label:"Press" }
// Campaign finished: return the token, discard the empty campaign
Pipe { from:lineupStart.outlets[1]; to:lineupEnd.inlets[0] }
Pipe { from:unpackCampaign.outPack; to:lineupEnd.inlets[1] }
Lineup { id:lineupEnd; label:"End campaign" }
Pipe { from:lineupEnd.outlets[0]; to:lineupStart.inlets[1] }
Pipe { from:lineupEnd.outlets[1]; to:leaveCampaign.inlet }
Leave { id:leaveCampaign; label:"Empty campaigns" }
// Press breakdowns: MTBF 8 h, MTTR 30 min (exponential)
Provide { id:providePressState; label:"Press state" }
Pipe { from:providePressState.outlet; to:meterPressState.inlet }
Meter { id:meterPressState; capacity:1 }
Pipe { from:meterPressState.outlet; to:pressUp.inlet }
RandomExponential { id:rndPressMtbf; mean:480000; seed:15 }
Wire { from:rndPressMtbf.measureRandomVariate; to:pressUp.parameterDuration }
Perform { id:pressUp; label:"Press available" }
Pipe { from:pressUp.outlet; to:pressDown.inlet }
RandomExponential { id:rndPressMttr; mean:30000; seed:16 }
Wire { from:rndPressMttr.measureRandomVariate; to:pressDown.parameterDuration }
Perform { id:pressDown; label:"Press down" }
Pipe { from:pressDown.outlet; to:pressUp.inlet }
Wire { from:pressUp.measureLoad; to:press.parameterCapacity }
// 3 Cooling table: 10 min, 6 places
Pipe { from:press.outlet; to:queueCool.inlet }
Queue { id:queueCool; label:"Q Cooling" }
Pipe { from:queueCool.outlet; to:cool.inlet }
Perform { id:cool; label:"Cooling"; duration:10000; capacity:6 }
// 4 Stretcher: 2 min +- 0.2
Pipe { from:cool.outlet; to:queueStretch.inlet }
Queue { id:queueStretch; label:"Q Stretcher" }
Pipe { from:queueStretch.outlet; to:stretch.inlet }
RandomNormal { id:rndStretch; mean:2000; stddev:200; seed:17 }
Wire { from:rndStretch.measureRandomVariate; to:stretch.parameterDuration }
Perform { id:stretch; label:"Stretcher" }
// 5 Saw: 3 min +- 0.5, capacity 0 while broken down
Pipe { from:stretch.outlet; to:queueSaw.inlet }
Queue { id:queueSaw; label:"Q Saw" }
Pipe { from:queueSaw.outlet; to:saw.inlet }
RandomNormal { id:rndSaw; mean:3000; stddev:500; seed:18 }
Wire { from:rndSaw.measureRandomVariate; to:saw.parameterDuration }
Perform { id:saw; label:"Saw" }
// Saw breakdowns: MTBF 4 h, MTTR 15 min (exponential)
Provide { id:provideSawState; label:"Saw state" }
Pipe { from:provideSawState.outlet; to:meterSawState.inlet }
Meter { id:meterSawState; capacity:1 }
Pipe { from:meterSawState.outlet; to:sawUp.inlet }
RandomExponential { id:rndSawMtbf; mean:240000; seed:19 }
Wire { from:rndSawMtbf.measureRandomVariate; to:sawUp.parameterDuration }
Perform { id:sawUp; label:"Saw available" }
Pipe { from:sawUp.outlet; to:sawDown.inlet }
RandomExponential { id:rndSawMttr; mean:15000; seed:20 }
Wire { from:rndSawMttr.measureRandomVariate; to:sawDown.parameterDuration }
Perform { id:sawDown; label:"Saw down" }
Pipe { from:sawDown.outlet; to:sawUp.inlet }
Wire { from:sawUp.measureLoad; to:saw.parameterCapacity }
// 6 Aging oven: racks of 40 billets, 2 ovens, 5 h per rack
Pipe { from:saw.outlet; to:packOven.inlet }
Provide { id:provideRack; label:"Racks" }
Pipe { from:provideRack.outlet; to:packOven.inPacket }
Pack { id:packOven; label:"Load rack"; capacity:40 }
Pipe { from:packOven.outlet; to:queueOven.inlet }
Queue { id:queueOven; label:"Q Oven" }
Pipe { from:queueOven.outlet; to:oven.inlet }
Perform { id:oven; label:"Aging oven"; duration:300000; capacity:2 }
Pipe { from:oven.outlet; to:unpackOven.inlet }
Unpack { id:unpackOven; label:"Unload rack" }
Pipe { from:unpackOven.outPack; to:leaveRack.inlet }
Leave { id:leaveRack; label:"Empty racks" }
// 7 Packaging: 2 workers, 2 min +- 0.3
Pipe { from:unpackOven.outlet; to:queuePackaging.inlet }
Queue { id:queuePackaging; label:"Q Packaging" }
Pipe { from:queuePackaging.outlet; to:packaging.inlet }
RandomNormal { id:rndPackaging; mean:2000; stddev:300; seed:21 }
Wire { from:rndPackaging.measureRandomVariate; to:packaging.parameterDuration }
Perform { id:packaging; label:"Packaging"; capacity:2 }
Pipe { from:packaging.outlet; to:leave0.inlet }
Leave { id:leave0; label:"Finished goods" }
// KPIs: busy and down shares of press and saw, die change share, ovens
Wire { from:press.measureLoad; to:statPress.parameters[0] }
Statistics { id:statPress; label:"Press busy"; timeAverage:true; height:250; width:142 }
Wire { from:setup.measureLoad; to:statSetup.parameters[0] }
Statistics { id:statSetup; label:"Die change"; timeAverage:true; height:250; width:142 }
Wire { from:pressDown.measureLoad; to:statPressDown.parameters[0] }
Statistics { id:statPressDown; label:"Press down"; timeAverage:true; height:250; width:142 }
Wire { from:saw.measureLoad; to:statSaw.parameters[0] }
Statistics { id:statSaw; label:"Saw busy"; timeAverage:true; height:250; width:142 }
Wire { from:sawDown.measureLoad; to:statSawDown.parameters[0] }
Statistics { id:statSawDown; label:"Saw down"; timeAverage:true; height:250; width:142 }
Wire { from:oven.measureLoad; to:statOven.parameters[0] }
Statistics { id:statOven; label:"Ovens busy"; timeAverage:true; height:250; width:142 }
// KPIs: billets waiting for the press: filling a campaign, queued campaigns, current campaign
Wire { from:packCampaign.measureLoad; to:statQCollect.parameters[0] }
Statistics { id:statQCollect; label:"Q Collect"; timeAverage:true; height:250; width:142 }
Wire { from:queueCampaign.measureLoad; to:statQCampaign.parameters[0] }
Statistics { id:statQCampaign; label:"Q Campaigns"; timeAverage:true; height:250; width:142 }
Wire { from:unpackCampaign.measureLoad; to:statQPress.parameters[0] }
Statistics { id:statQPress; label:"Q Press"; timeAverage:true; height:250; width:142 }
Wire { from:queueSaw.measureLoad; to:statQSaw.parameters[0] }
Statistics { id:statQSaw; label:"Q Saw"; timeAverage:true; height:250; width:142 }
// KPIs: output per week and lead time per billet (time units / 1000 = min)
Wire { from:leave0.measureNumPassed; to:statOutput.parameters[0] }
Statistics { id:statOutput; label:"Output"; cumulative:true; height:250; width:142 }
Wire { from:leave0.measureSojourn; to:statLeadTime.parameters[0] }
Statistics { id:statLeadTime; label:"Lead time"; height:250; width:142 }Results
| KPI | Value |
|---|---|
| Press busy extruding | 0.69 ± 0.01 |
| Die change | 0.15 ± 0.00 |
| Press down | 0.06 ± 0.01 |
| Press occupied in total | 0.90 |
| Billets collecting for a campaign (Q Collect) | 19.4 ± 0.2 |
| Billets of the current campaign waiting for the press (Q Press) | 14.6 ± 0.4 |
| Lead time | 636.4 ± 7.1 min → 10.6 h |
| Output per week | 1908 ± 27 |
The die changes take 15% of the press time: about 50 campaigns per week × 30 min. Together with extruding and breakdowns, the press is occupied 90% of the time and is now clearly the bottleneck.
The lead time grows by 3 hours compared with step 2. Little's law (, with billets per min) explains where:
- Collecting a campaign: min
- Waiting in the released campaign for the press: min
Together that is 170 min, almost the whole increase of 183 min. The billets wait mainly because of the batching, not because the press is short of capacity.
Step 4: Choosing the Campaign Size
Stability condition. Per billet, the press needs minutes, where is the die change time and the campaign size. With availability , the press can provide min per billet at the arrival rate of one billet per 5 min. So the line is only stable if
Simulation. Change capacity of Collect campaign and the mean (and standard deviation) of rndSetup:
| Campaign size | Die change | Die change share | Lead time | Output per week |
|---|---|---|---|---|
| 20 | 30 min | 0.29 | 12.2 h ± 1.1 h | 1853 ± 20 |
| 30 | 30 min | 0.20 | 10.5 h ± 0.2 h | 1907 ± 27 |
| 40 | 30 min | 0.15 | 10.6 h ± 0.1 h | 1908 ± 27 |
| 80 | 30 min | 0.07 | 13.3 h ± 0.1 h | 1868 ± 27 |
| 20 | 15 min (SMED) | 0.15 | 9.1 h ± 0.1 h | 1918 ± 27 |
- is too small. Extruding (0.67), die changes (0.29) and breakdowns (0.06) add up to more than 100%. Campaigns pile up in front of the press (Q Campaigns 1.1 ± 0.6), the lead time grows from week to week, and the output falls below the demand. Note the wide confidence interval: the system has no steady state.
- is too large. Die changes take only 7% of the time, but every billet waits for 79 others: Q Collect rises to 39 and Q Press to 29 billets.
- to is the sweet spot for a 30 min die change: the lead time is a U-shaped function of the campaign size.
- Faster die changes beat larger campaigns. Halving the die change time (SMED, Single-Minute Exchange of Die) allows campaigns of 20 with the same 15% die change share. The lead time drops by 1.5 hours compared with the best campaign size for a 30 min die change.
Model Simplifications
- Breakdowns follow calendar time: the press can also fail while it is idle or during a die change.
- A breakdown does not interrupt the billet in process. With 3.5 min per billet against 30 min repairs, the error is small.
- The next die change may start while the last billet of the previous campaign is still on the press (overlap ≤ 3.5 min).
- Billets wait for their campaign after the heating furnace. In reality they would cool down; a real plant would heat them just in time.
- No warm-up is removed, and one week is short compared with the 5 h aging time. The confidence intervals cover the replication-to-replication variation of exactly this start-up situation.
Exercises
Exercise 1: Bottleneck by Hand
Using only the data table (no simulation):
- Compute the utilization of every station without disturbances.
- Add die changes and breakdowns to the press utilization. Which station is the bottleneck, and by how much could the arrival rate increase before the line becomes unstable?
- Check your results against the Statistics blocks of steps 1 and 3.
Exercise 2: Campaign Size Curve
- Run the step 3 model for and plot the lead time against .
- Repeat for a die change of 15 min and of 45 min.
- Derive the minimum stable campaign size for each die change time and compare it with your curves.
- Automate the experiment so that each edition runs one campaign size (see How do I find the best value of a parameter?).
Exercise 3: Maintenance Versus Repair
The maintenance department offers two options for the press:
- A: better preventive maintenance doubles the MTBF to 16 h
- B: a repair team on standby halves the MTTR to 15 min
Both options give the same availability. Which one gives the shorter lead time? Explain the result with the queue length at the press during repairs.
Exercise 4: Warm-Up and Run Length
- Add an EnsemblePlot for the lead time and determine the warm-up period of the step 3 model.
- Set
warmupWidthon the Statistics blocks and compare the results with and without warm-up. - Run 4 weeks instead of 1 week. How do the confidence intervals change?
Exercise 5: Aging Oven
- Replace the two ovens by three ovens with racks of 30 billets. How do oven utilization and lead time change?
- Try the take what is there rule from the batching recipe: set
sufficiencyonLoad rack(e.g. 20) and pipe the rack directly into the ovens. How do lead time and oven utilization change, and when does the rule pay off?