Skip to content

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:

  1. Heating furnace – heats each billet to extrusion temperature
  2. Extrusion press – presses the billet through a die into a profile
  3. Cooling table – the profile cools down
  4. Stretcher – straightens the profile
  5. Saw – cuts the profile to length
  6. Aging oven – hardens the profiles in racks (batch process)
  7. Packaging

The seven stations of the aluminum extrusion lineThe seven stations of the aluminum extrusion line

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.

#StationCapacityTimeUtilization ρ=λ⋅t/c\rho = \lambda \cdot t / c
1Heating furnace13 ± 0.3 min60%
2Extrusion press13.5 ± 0.35 min70%
3Cooling table6 places10 min33%
4Stretcher12 ± 0.2 min40%
5Saw13 ± 0.5 min60%
6Aging oven2 ovens5 h per rack of 40 billets75%
7Packaging2 workers2 ± 0.3 min20%
DisturbanceData
Die change at the press30 ± 5 min, once per campaign of 40 billets
Press breakdownsMTBF 8 h, MTTR 30 min (both exponential)
Saw breakdownsMTBF 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.

diagramdiagram
Model codeOpen in Quisim
qml
// 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 ​

StationBusy serversUtilizationAvg. queue
Heating0.60 ± 0.0160%
Press0.70 ± 0.0170%0.36 ± 0.04
Cooling2.01 ± 0.0334%
Stretcher0.40 ± 0.0140%
Saw0.60 ± 0.0160%0.02 ± 0.00
Aging oven1.47 ± 0.0274%
Packaging0.39 ± 0.0120%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 ρ=λt/c\rho = \lambda t / c 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:

ComponentTime
Aging300 min
Waiting until the rack holds 40 billets: on average (40−1)/2⋅5(40-1)/2 \cdot 5 min≈ 98 min
Processing at the other stations: 3+3.5+10+2+3+23 + 3.5 + 10 + 2 + 3 + 223.5 min
Waiting at packaging (Little's law: W=L/λ=3.67/0.2W = L/\lambda = 3.67 / 0.2 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 A=MTBF/(MTBF+MTTR)=480/510=240/255=94.1%A = \text{MTBF} / (\text{MTBF} + \text{MTTR}) = 480/510 = 240/255 = 94.1\%.

diagramdiagram
Model codeOpen in Quisim
qml
// 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 ​

KPIStep 1Step 2
Press down–0.06 ± 0.01
Saw down–0.06 ± 0.01
Avg. queue at press0.36 ± 0.041.73 ± 0.47
Avg. queue at saw0.02 ± 0.000.54 ± 0.11
Lead time7.4 h7.6 h
Output per week1943 ± 291943 ± 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:

  1. Collect campaign (Pack, capacity:40) groups 40 heated billets into one campaign entity.
  2. 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.
  3. Die change (Perform) takes 30 ± 5 min per campaign.
  4. Release billets (Unpack) hands the billets to the press one by one.
  5. 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 to Start campaign.
diagramdiagram
Model codeOpen in Quisim
qml
// 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 ​

KPIValue
Press busy extruding0.69 ± 0.01
Die change0.15 ± 0.00
Press down0.06 ± 0.01
Press occupied in total0.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 time636.4 ± 7.1 min → 10.6 h
Output per week1908 ± 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 (W=L/λW = L / \lambda, with λ=0.2\lambda = 0.2 billets per min) explains where:

  • Collecting a campaign: 19.4/0.2=9719.4 / 0.2 = 97 min
  • Waiting in the released campaign for the press: 14.6/0.2=7314.6 / 0.2 = 73 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 3.5+S/N3.5 + S/N minutes, where SS is the die change time and NN the campaign size. With availability A=94.1%A = 94.1\%, the press can provide 5⋅A=4.715 \cdot A = 4.71 min per billet at the arrival rate of one billet per 5 min. So the line is only stable if

N>S5A−3.5=301.21≈25 billets.N > \frac{S}{5A - 3.5} = \frac{30}{1.21} \approx 25 \text{ billets.}

Simulation. Change capacity of Collect campaign and the mean (and standard deviation) of rndSetup:

Campaign size NNDie changeDie change shareLead timeOutput per week
2030 min0.2912.2 h ± 1.1 h1853 ± 20
3030 min0.2010.5 h ± 0.2 h1907 ± 27
4030 min0.1510.6 h ± 0.1 h1908 ± 27
8030 min0.0713.3 h ± 0.1 h1868 ± 27
2015 min (SMED)0.159.1 h ± 0.1 h1918 ± 27
  • N=20N = 20 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.
  • N=80N = 80 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.
  • N=30N = 30 to 4040 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):

  1. Compute the utilization of every station without disturbances.
  2. 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?
  3. Check your results against the Statistics blocks of steps 1 and 3.

Exercise 2: Campaign Size Curve ​

  1. Run the step 3 model for N=25,30,35,…,80N = 25, 30, 35, \dots, 80 and plot the lead time against NN.
  2. Repeat for a die change of 15 min and of 45 min.
  3. Derive the minimum stable campaign size for each die change time and compare it with your curves.
  4. 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 ​

  1. Add an EnsemblePlot for the lead time and determine the warm-up period of the step 3 model.
  2. Set warmupWidth on the Statistics blocks and compare the results with and without warm-up.
  3. Run 4 weeks instead of 1 week. How do the confidence intervals change?

Exercise 5: Aging Oven ​

  1. Replace the two ovens by three ovens with racks of 30 billets. How do oven utilization and lead time change?
  2. Try the take what is there rule from the batching recipe: set sufficiency on Load 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?