Machine Shop
In this example, we'll learn how to interrupt a process because of more important tasks. The example is adopted from the SimPy Machine Shop Example.
The example covers interrupt and preemptive resources.
The example comprises a workshop with n
identical machines. A stream of jobs (enough to keep the machines busy) arrives. Each machine breaks down periodically. Repairs are carried out by one repairman. The repairman has other, less important tasks to perform, too. Broken machines preempt these tasks. The repairman continues them when he is done with the machine repair. The workshop works continuously.
A machine has two processes:
- working implements the actual behaviour of the machine (producing parts).
- break_machine periodically interrupts the working process to simulate the machine failure.
In kalasim
there can only be one generating process per component. So to model the wear, we use a separate MachineWear
which is interrupt
ing the machine in case of failure.
The repairman’s other job is also a process (implemented by otherJob). The repairman itself is a preemptive resource with a capacity of 1
. The machine repairing has a priority of 1, while the other job has a priority of 2
(the smaller the number, the higher the priority).
////MachineShop.kt
import org.kalasim.*
import kotlin.time.Duration.Companion.days
import kotlin.time.Duration.Companion.minutes
val RANDOM_SEED: Int = 42
val PT_MEAN = 10.minutes // Avg. processing time in minutes
val PT_SIGMA = 2.minutes // Sigma of processing time
val MTTF = 300.minutes // Mean time to failure in minutes
val REPAIR_TIME = 30.minutes // Time it takes to repair a machine in minutes
val JOB_DURATION = 30.minutes // Duration of other jobs in minutes
val NUM_MACHINES: Int = 10 // Number of machines in the machine shop
val SIM_TIME = 28.days // Simulation time
fun main() {
class Machine : Component() {
var madeParts: Int = 0
private set
val timePerPart: DurationDistribution = normal(PT_MEAN, PT_SIGMA)
override fun process(): Sequence<Component> = sequence {
while(true) {
// start working on a new part
log("building a new part")
hold(timePerPart())
log("finished building part")
madeParts++
}
}
}
/** Break the machine every now and then. */
class MachineWear(val machine: Machine, val repairMan: Resource) : Component(
process = MachineWear::breakMachine
) {
fun breakMachine(): Sequence<Component> = sequence {
val timeToFailure = exponential(MTTF)
while(true) {
hold(timeToFailure())
// handle the rare case that the model
if(machine.isInterrupted) continue
machine.interrupt()
request(repairMan)
hold(REPAIR_TIME)
require(!isBumped(repairMan)) { "productive tools must not be bumped" }
release(repairMan)
machine.resume()
require(!machine.isInterrupted) { "machine must not be interrupted at end of wear cycle" }
}
}
}
createSimulation(randomSeed = RANDOM_SEED) {
enableComponentLogger()
val repairMan = Resource("mechanic", preemptive = true)
// create N machines and wear components
val tools = (1..NUM_MACHINES).map {
Machine().also { MachineWear(it, repairMan) }
}
// define the other jobs as object expression
// https://kotlinlang.org/docs/reference/object-declarations.html#object-expressions
object : Component("side jobs") {
override fun process() = sequence {
while(true) {
request(ResourceRequest(repairMan, priority = Priority(-1)))
hold(JOB_DURATION)
if(isBumped(repairMan)) {
log("other job was bumped")
continue
}
release(repairMan)
}
}
}
// Run simulation
run(1000.minutes)
run(SIM_TIME)
// Analysis
tools.forEach { println("${it.name} made ${it.madeParts} parts.") }
}
}