aivika-3.1: examples/MachineBreakdowns.hs
-- Example: Machine Tool with Breakdowns
--
-- It is described in different sources [1, 2]. So, this is chapter 13 of [2] and section 6.12 of [1].
--
-- [1] A. Alan B. Pritsker, Simulation with Visual SLAM and AweSim, 2nd ed.
--
-- [2] Труб И.И., Объектно-ориентированное моделирование на C++: Учебный курс. - СПб.: Питер, 2006
import Control.Monad
import Control.Monad.Trans
import Control.Category
import Data.Monoid
import Data.List
import Simulation.Aivika
import qualified Simulation.Aivika.Queue.Infinite as IQ
-- | The simulation specs.
specs = Specs { spcStartTime = 0.0,
spcStopTime = 500.0,
spcDT = 0.1,
spcMethod = RungeKutta4,
spcGeneratorType = SimpleGenerator }
-- | How often do jobs arrive to a machine tool (exponential)?
jobArrivingMu = 1
-- | A mean of time to process a job (normal).
jobProcessingMu = 0.5
-- | The standard deviation of time to process a job (normal).
jobProcessingSigma = 0.1
-- | The minimum set-up time (uniform).
minSetUpTime = 0.2
-- | The maximum set-up time (uniform).
maxSetUpTime = 0.5
-- | A mean of time between breakdowns (normal).
breakdownMu = 20
-- | The standard deviation of time between breakdowns (normal).
breakdownSigma = 2
-- | A mean of each of the three repair phases (Erlang).
repairMu = 3/4
-- | It defines a job.
data Job = Job { jobProcessingTime :: Double,
-- ^ the job processing time defined when arriving.
jobRemainingTime :: Double
-- ^ the remaining processing time (may differ after return).
}
-- | Return the remaining job after interruption.
remainingJobInput :: ServerInterruption (Arrival Job) -> Arrival Job
remainingJobInput x = a'
where
t1 = serverStartProcessingTime x
t2 = serverInterruptionTime x
dt = t2 - t1
a = serverInterruptedInput x
a' = a { arrivalValue = job' }
job = arrivalValue a
job' = job { jobRemainingTime =
max 0 $ jobRemainingTime job - dt }
model :: Simulation Results
model = do
-- create an input queue
inputQueue <- runEventInStartTime IQ.newPriorityQueue
-- a counter of jobs completed
jobsCompleted <- newArrivalTimer
-- a counter of interrupted jobs but then returned for the further processing
jobsInterrupted <- newRef (0 :: Int)
-- create an input stream
let inputStream =
traceStream Nothing (Just "taking a job from the queue") $
repeatProcess $ IQ.dequeue inputQueue
-- create the setting up phase of processing
machineSettingUp <-
newInterruptibleServer True $ \a ->
do -- set up the machine
setUpTime <-
liftParameter $
randomUniform minSetUpTime maxSetUpTime
traceProcess "setting up the machine" $
holdProcess setUpTime
return a
-- create the processing phase itself
machineProcessing <-
newInterruptibleServer True $ \a ->
do -- process the job
let job = arrivalValue a
traceProcess "processing the job" $
holdProcess $ jobRemainingTime job
-- return the completed job
return a { arrivalValue = job { jobRemainingTime = 0 } }
-- enqueue the interrupted jobs again
runEventInStartTime $
do handleSignal_ (serverTaskInterrupted machineSettingUp) $ \x ->
traceEvent "returning the job..." $
do let a = serverInterruptedInput x
t = arrivalTime a
IQ.enqueueWithStoringPriority inputQueue t a
handleSignal_ (serverTaskInterrupted machineProcessing) $ \x ->
traceEvent "interrupting the job..." $
do let a = remainingJobInput x
t = arrivalTime a
modifyRef jobsInterrupted (+ 1)
IQ.enqueueWithStoringPriority inputQueue t a
let -- launch the machine tool again and again
machineLaunch =
joinProcessor $
do spawnProcess $
do -- breakdown the machine tool in time (a bound child process)
breakdownTime <-
liftParameter $
randomNormal breakdownMu breakdownSigma
when (breakdownTime > 0) $
holdProcess breakdownTime
traceProcess "breakdown" $
cancelProcess
return $
serverProcessor machineSettingUp >>>
serverProcessor machineProcessing
-- repair the machine tool
machineRepair =
do repairTime <- liftParameter $
randomErlang repairMu 3
holdProcess repairTime
-- launch after repairing the machine tool
machineRepairAndLaunch =
joinProcessor $
do machineRepair
traceProcess "repaired" $
return machineLaunch
-- machine loop
machineLoop = machineLaunch : repeat machineRepairAndLaunch
-- the network
network =
traceProcessor Nothing (Just "the job completed") $
failoverProcessor machineLoop >>>
arrivalTimerProcessor jobsCompleted
-- start the machine tool
runProcessInStartTime $
sinkStream $ runProcessor network inputStream
-- model a stream of jobs
let jobs =
traceStream Nothing (Just "a new job") $
randomExponentialStream jobArrivingMu
-- start the processing of jobs by enqueueing them
runProcessInStartTime $
flip consumeStream jobs $ \a ->
liftEvent $ do
-- define the processing time for the job
jobProcessingTime <-
liftParameter $
randomNormal jobProcessingMu jobProcessingSigma
-- enqueue the job
let t0 = arrivalTime a
dt = max 0 jobProcessingTime
IQ.enqueueWithStoringPriority inputQueue t0 $
a { arrivalValue = Job dt dt }
-- return the simulation results in start time
return $
results
[resultSource
"inputQueue" "the queue of jobs"
inputQueue,
--
resultSource
"machineSettingUp" "the machine tool (the setting up phase)"
machineSettingUp,
--
resultSource
"machineProcessing" "the machine tool (the processing phase)"
machineProcessing,
--
resultSource
"jobsInterrupted" "a counter of the interrupted jobs"
jobsInterrupted,
--
resultSource
"jobsCompleted" "a counter of the completed jobs"
jobsCompleted]
main =
printSimulationResultsInStopTime
printResultSourceInEnglish
(fmap resultSummary model) specs