aivika 0.7 → 1.0
raw patch · 53 files changed
+5798/−1419 lines, 53 files
Files
- LICENSE +1/−1
- Simulation/Aivika.hs +66/−0
- Simulation/Aivika/Agent.hs +19/−19
- Simulation/Aivika/Cont.hs +2/−1
- Simulation/Aivika/Dynamics.hs +1/−4
- Simulation/Aivika/Dynamics/Interpolate.hs +2/−18
- Simulation/Aivika/Dynamics/Random.hs +92/−27
- Simulation/Aivika/Event.hs +8/−5
- Simulation/Aivika/Generator.hs +191/−0
- Simulation/Aivika/Internal/Cont.hs +434/−48
- Simulation/Aivika/Internal/Dynamics.hs +14/−20
- Simulation/Aivika/Internal/Event.hs +42/−39
- Simulation/Aivika/Internal/Parameter.hs +251/−0
- Simulation/Aivika/Internal/Process.hs +338/−86
- Simulation/Aivika/Internal/Signal.hs +147/−67
- Simulation/Aivika/Internal/Simulation.hs +42/−77
- Simulation/Aivika/Internal/Specs.hs +7/−3
- Simulation/Aivika/Parameter.hs +25/−49
- Simulation/Aivika/Parameter/Random.hs +122/−23
- Simulation/Aivika/PriorityQueue.hs +0/−0
- Simulation/Aivika/Process.hs +38/−9
- Simulation/Aivika/Processor.hs +414/−0
- Simulation/Aivika/Processor/RoundRobbin.hs +58/−0
- Simulation/Aivika/Queue.hs +863/−129
- Simulation/Aivika/Queue/Infinite.hs +592/−0
- Simulation/Aivika/QueueStrategy.hs +43/−19
- Simulation/Aivika/Random.hs +0/−53
- Simulation/Aivika/Resource.hs +174/−108
- Simulation/Aivika/Server.hs +492/−0
- Simulation/Aivika/Signal.hs +11/−98
- Simulation/Aivika/Simulation.hs +2/−4
- Simulation/Aivika/Statistics.hs +78/−43
- Simulation/Aivika/Stream.hs +438/−0
- Simulation/Aivika/Stream/Random.hs +89/−0
- Simulation/Aivika/SystemDynamics.hs +157/−69
- Simulation/Aivika/Table.hs +64/−0
- Simulation/Aivika/Task.hs +170/−0
- Simulation/Aivika/Unboxed.hs +0/−0
- Simulation/Aivika/Var.hs +5/−12
- Simulation/Aivika/Vector/Unboxed.hs +0/−0
- aivika.cabal +39/−5
- examples/BassDiffusion.hs +16/−22
- examples/ChemicalReaction.hs +3/−4
- examples/FishBank.hs +3/−4
- examples/Furnace.hs +141/−182
- examples/MachRep1.hs +15/−30
- examples/MachRep1EventDriven.hs +12/−17
- examples/MachRep1TimeDriven.hs +17/−24
- examples/MachRep2.hs +17/−33
- examples/MachRep3.hs +21/−29
- examples/README +1/−1
- examples/TimeOut.hs +11/−19
- examples/TimeOutInt.hs +10/−18
LICENSE view
@@ -1,4 +1,4 @@-Copyright (c) 2009, 2010, 2011, 2012, 2013 David Sorokin <david.sorokin@gmail.com>+Copyright (c) 2009, 2010, 2011, 2012, 2013, 2014 David Sorokin <david.sorokin@gmail.com> All rights reserved.
+ Simulation/Aivika.hs view
@@ -0,0 +1,66 @@++-- |+-- Module : Simulation.Aivika+-- Copyright : Copyright (c) 2009-2013, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.6.3+--+-- This module re-exports the most part of the library functionality.+-- But there are modules that must be imported explicitly.+--+module Simulation.Aivika+ (-- * Modules+ module Simulation.Aivika.Agent,+ module Simulation.Aivika.Cont,+ module Simulation.Aivika.Dynamics,+ module Simulation.Aivika.Dynamics.Interpolate,+ module Simulation.Aivika.Dynamics.Memo.Unboxed,+ module Simulation.Aivika.Dynamics.Random,+ module Simulation.Aivika.Event,+ module Simulation.Aivika.Generator,+ module Simulation.Aivika.Parameter,+ module Simulation.Aivika.Parameter.Random,+ module Simulation.Aivika.Process,+ module Simulation.Aivika.Processor,+ module Simulation.Aivika.Processor.RoundRobbin,+ module Simulation.Aivika.QueueStrategy,+ module Simulation.Aivika.Ref,+ module Simulation.Aivika.Resource,+ module Simulation.Aivika.Server,+ module Simulation.Aivika.Signal,+ module Simulation.Aivika.Simulation,+ module Simulation.Aivika.Specs,+ module Simulation.Aivika.Statistics,+ module Simulation.Aivika.Stream,+ module Simulation.Aivika.Stream.Random,+ module Simulation.Aivika.Task,+ module Simulation.Aivika.Var.Unboxed) where++import Simulation.Aivika.Agent+import Simulation.Aivika.Cont+import Simulation.Aivika.Dynamics+import Simulation.Aivika.Dynamics.Interpolate+import Simulation.Aivika.Dynamics.Memo.Unboxed+import Simulation.Aivika.Dynamics.Random+import Simulation.Aivika.Event+import Simulation.Aivika.Generator+import Simulation.Aivika.Parameter+import Simulation.Aivika.Parameter.Random+import Simulation.Aivika.Process+import Simulation.Aivika.Processor+import Simulation.Aivika.Processor.RoundRobbin+import Simulation.Aivika.QueueStrategy+import Simulation.Aivika.Ref+import Simulation.Aivika.Resource+import Simulation.Aivika.Server+import Simulation.Aivika.Signal+import Simulation.Aivika.Simulation+import Simulation.Aivika.Specs+import Simulation.Aivika.Statistics+import Simulation.Aivika.Stream+import Simulation.Aivika.Stream.Random+import Simulation.Aivika.Task+import Simulation.Aivika.Var.Unboxed+
Simulation/Aivika/Agent.hs view
@@ -15,10 +15,10 @@ newAgent, newState, newSubstate,- agentState,- agentStateChanged,- agentStateChanged_,- activateState,+ selectedState,+ selectedStateChanged,+ selectedStateChanged_,+ selectState, stateAgent, stateParent, addTimeout,@@ -190,15 +190,15 @@ agentStateRef = stateRef, agentStateChangedSource = stateChangedSource } --- | Return the selected downmost active state.-agentState :: Agent -> Event (Maybe AgentState)-agentState agent =+-- | Return the selected active state.+selectedState :: Agent -> Event (Maybe AgentState)+selectedState agent = Event $ \p -> readIORef (agentStateRef agent) --- | Select the next downmost active state. The activation is repeated while+-- | Select the state. The activation and selection are repeated while -- there is the transition state defined by 'setStateTransition'.-activateState :: AgentState -> Event ()-activateState st =+selectState :: AgentState -> Event ()+selectState st = Event $ \p -> do let agent = stateAgent st mode <- readIORef (agentModeRef agent)@@ -227,8 +227,8 @@ writeIORef (stateDeactivateRef st) action -- | Set the transition state which will be next and which is used only--- when activating the state directly with help of 'activateState'.--- If the state was activated intermediately, when activating directly+-- when selecting the state directly with help of 'selectState'.+-- If the state was activated intermediately, when selecting -- another state, then this computation is not used. setStateTransition :: AgentState -> Event (Maybe AgentState) -> Simulation () setStateTransition st action =@@ -241,12 +241,12 @@ do st <- readIORef (agentStateRef agent) invokeEvent p $ triggerSignal (agentStateChangedSource agent) st --- | Return a signal that notifies about every change of the state.-agentStateChanged :: Agent -> Signal (Maybe AgentState)-agentStateChanged agent =+-- | Return a signal that notifies about every change of the selected state.+selectedStateChanged :: Agent -> Signal (Maybe AgentState)+selectedStateChanged agent = publishSignal (agentStateChangedSource agent) --- | Return a signal that notifies about every change of the state.-agentStateChanged_ :: Agent -> Signal ()-agentStateChanged_ agent =- mapSignal (const ()) $ agentStateChanged agent+-- | Return a signal that notifies about every change of the selected state.+selectedStateChanged_ :: Agent -> Signal ()+selectedStateChanged_ agent =+ mapSignal (const ()) $ selectedStateChanged agent
Simulation/Aivika/Cont.hs view
@@ -12,7 +12,8 @@ -- the continuations is the 'Event' computation. -- module Simulation.Aivika.Cont- (Cont) where+ (ContCancellation(..),+ Cont) where import Simulation.Aivika.Internal.Event import Simulation.Aivika.Internal.Cont
Simulation/Aivika/Dynamics.hs view
@@ -22,10 +22,7 @@ catchDynamics, finallyDynamics, throwDynamics,- -- * Time parameters- starttime,- stoptime,- dt,+ -- * Simulation Time time, isTimeInteg, integIteration,
Simulation/Aivika/Dynamics/Interpolate.hs view
@@ -8,31 +8,15 @@ -- Tested with: GHC 7.6.3 -- -- This module defines interpolation functions.--- These functions complement the memoization, possibly except for --- the 'initDynamics' function which is useful to get an initial --- value of any dynamic process.+-- These functions complement the memoization. -- module Simulation.Aivika.Dynamics.Interpolate- (initDynamics,- discreteDynamics,+ (discreteDynamics, interpolateDynamics) where import Simulation.Aivika.Internal.Specs import Simulation.Aivika.Internal.Dynamics---- | Return the initial value.-initDynamics :: Dynamics a -> Dynamics a-{-# INLINE initDynamics #-}-initDynamics (Dynamics m) =- Dynamics $ \p ->- if pointIteration p == 0 && pointPhase p == 0 then- m p- else- let sc = pointSpecs p- in m $ p { pointTime = basicTime sc 0 0,- pointIteration = 0,- pointPhase = 0 } -- | Discretize the computation in the integration time points. discreteDynamics :: Dynamics a -> Dynamics a
Simulation/Aivika/Dynamics/Random.hs view
@@ -7,38 +7,103 @@ -- Stability : experimental -- Tested with: GHC 7.6.3 ----- Below are defined random functions that return the 'Dynamics' computations. --- The values are initially defined in the integration time points and then--- they are passed in to the 'memo0Dynamics' function to memoize and then interpolate.+-- This module defines the random parameters of simulation experiments. -- -module Simulation.Aivika.Dynamics.Random - (newRandomDynamics, newNormalDynamics) where+module Simulation.Aivika.Dynamics.Random+ (memoRandomUniformDynamics,+ memoRandomNormalDynamics,+ memoRandomExponentialDynamics,+ memoRandomErlangDynamics,+ memoRandomPoissonDynamics,+ memoRandomBinomialDynamics) where import System.Random-import Data.IORef+ import Control.Monad.Trans -import Simulation.Aivika.Simulation-import Simulation.Aivika.Random-import Simulation.Aivika.Dynamics+import Simulation.Aivika.Generator+import Simulation.Aivika.Internal.Specs+import Simulation.Aivika.Internal.Parameter+import Simulation.Aivika.Internal.Simulation+import Simulation.Aivika.Internal.Dynamics import Simulation.Aivika.Dynamics.Memo.Unboxed --- | Return the uniform random numbers in the integration time points.-newRandomDynamics :: Dynamics Double -- ^ minimum- -> Dynamics Double -- ^ maximum- -> Simulation (Dynamics Double)-newRandomDynamics min max =- memo0Dynamics $ do- x <- liftIO $ getStdRandom random- min + return x * (max - min)- --- | Return the normal random numbers in the integration time points.-newNormalDynamics :: Dynamics Double -- ^ mean- -> Dynamics Double -- ^ variance- -> Simulation (Dynamics Double)-newNormalDynamics mu nu =- do g <- liftIO newNormalGen- memo0Dynamics $ do- x <- liftIO g- mu + return x * nu+-- | Computation that generates random numbers distributed uniformly and+-- memoizes them in the integration time points.+memoRandomUniformDynamics :: Dynamics Double -- ^ minimum+ -> Dynamics Double -- ^ maximum+ -> Simulation (Dynamics Double)+memoRandomUniformDynamics min max =+ memo0Dynamics $+ Dynamics $ \p ->+ do let g = runGenerator $ pointRun p+ min' <- invokeDynamics p min+ max' <- invokeDynamics p max+ generatorUniform g min' max'++-- | Computation that generates random numbers distributed normally and+-- memoizes them in the integration time points.+memoRandomNormalDynamics :: Dynamics Double -- ^ mean+ -> Dynamics Double -- ^ deviation+ -> Simulation (Dynamics Double)+memoRandomNormalDynamics mu nu =+ memo0Dynamics $+ Dynamics $ \p ->+ do let g = runGenerator $ pointRun p+ mu' <- invokeDynamics p mu+ nu' <- invokeDynamics p nu+ generatorNormal g mu' nu'++-- | Computation that generates exponential random numbers with the specified mean+-- (the reciprocal of the rate) and memoizes them in the integration time points.+memoRandomExponentialDynamics :: Dynamics Double+ -- ^ the mean (the reciprocal of the rate)+ -> Simulation (Dynamics Double)+memoRandomExponentialDynamics mu =+ memo0Dynamics $+ Dynamics $ \p ->+ do let g = runGenerator $ pointRun p+ mu' <- invokeDynamics p mu+ generatorExponential g mu'++-- | Computation that generates the Erlang random numbers with the specified scale+-- (the reciprocal of the rate) and integer shape but memoizes them in the integration+-- time points.+memoRandomErlangDynamics :: Dynamics Double+ -- ^ the scale (the reciprocal of the rate)+ -> Dynamics Int+ -- ^ the shape+ -> Simulation (Dynamics Double)+memoRandomErlangDynamics beta m =+ memo0Dynamics $+ Dynamics $ \p ->+ do let g = runGenerator $ pointRun p+ beta' <- invokeDynamics p beta+ m' <- invokeDynamics p m+ generatorErlang g beta' m'++-- | Computation that generats the Poisson random numbers with the specified mean+-- and memoizes them in the integration time points.+memoRandomPoissonDynamics :: Dynamics Double+ -- ^ the mean+ -> Simulation (Dynamics Int)+memoRandomPoissonDynamics mu =+ memo0Dynamics $+ Dynamics $ \p ->+ do let g = runGenerator $ pointRun p+ mu' <- invokeDynamics p mu+ generatorPoisson g mu'++-- | Computation that generates binomial random numbers with the specified+-- probability and trials but memoizes them in the integration time points.+memoRandomBinomialDynamics :: Dynamics Double -- ^ the probability+ -> Dynamics Int -- ^ the number of trials+ -> Simulation (Dynamics Int)+memoRandomBinomialDynamics prob trials =+ memo0Dynamics $+ Dynamics $ \p ->+ do let g = runGenerator $ pointRun p+ prob' <- invokeDynamics p prob+ trials' <- invokeDynamics p trials+ generatorBinomial g prob' trials'
Simulation/Aivika/Event.hs view
@@ -15,7 +15,6 @@ Event, EventLift(..), EventProcessing(..),- EventCancellation(..), runEvent, runEventInStartTime, runEventInStopTime,@@ -24,13 +23,17 @@ enqueueEventWithCancellation, enqueueEventWithTimes, enqueueEventWithIntegTimes,- enqueueEventWithStartTime,- enqueueEventWithStopTime,- enqueueEventWithCurrentTime, eventQueueCount,+ -- * Cancelling Event+ EventCancellation,+ cancelEvent,+ eventCancelled,+ eventFinished, -- * Error Handling catchEvent, finallyEvent,- throwEvent) where+ throwEvent,+ -- * Memoization+ memoEvent) where import Simulation.Aivika.Internal.Event
+ Simulation/Aivika/Generator.hs view
@@ -0,0 +1,191 @@++-- |+-- Module : Simulation.Aivika.Generator+-- Copyright : Copyright (c) 2009-2013, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.6.3+--+-- Below is defined a type class of the random number generator.+--+module Simulation.Aivika.Generator + (Generator(..),+ GeneratorType(..),+ newGenerator,+ newRandomGenerator) where++import System.Random+import Data.IORef++-- | Defines a random number generator.+data Generator =+ Generator { generatorUniform :: Double -> Double -> IO Double,+ -- ^ Generate an uniform random number+ -- with the specified minimum and maximum.+ generatorNormal :: Double -> Double -> IO Double,+ -- ^ Generate the normal random number+ -- with the specified mean and deviation.+ generatorExponential :: Double -> IO Double,+ -- ^ Generate the random number distributed exponentially+ -- with the specified mean (the reciprocal of the rate).+ generatorErlang :: Double -> Int -> IO Double,+ -- ^ Generate the Erlang random number+ -- with the specified scale (the reciprocal of the rate) and integer shape.+ generatorPoisson :: Double -> IO Int,+ -- ^ Generate the Poisson random number+ -- with the specified mean.+ generatorBinomial :: Double -> Int -> IO Int+ -- ^ Generate the binomial random number+ -- with the specified probability and number of trials.+ }++-- | Generate the uniform random number with the specified minimum and maximum.+generateUniform :: IO Double+ -- ^ the generator+ -> Double+ -- ^ minimum+ -> Double+ -- ^ maximum+ -> IO Double+generateUniform g min max =+ do x <- g+ return $ min + x * (max - min)++-- | Create a normal random number generator with mean 0 and variance 1+-- by the specified generator of uniform random numbers from 0 to 1.+newNormalGenerator :: IO Double+ -- ^ the generator+ -> IO (IO Double)+newNormalGenerator g =+ do nextRef <- newIORef 0.0+ flagRef <- newIORef False+ xi1Ref <- newIORef 0.0+ xi2Ref <- newIORef 0.0+ psiRef <- newIORef 0.0+ let loop =+ do psi <- readIORef psiRef+ if (psi >= 1.0) || (psi == 0.0)+ then do g1 <- g+ g2 <- g+ let xi1 = 2.0 * g1 - 1.0+ xi2 = 2.0 * g2 - 1.0+ psi = xi1 * xi1 + xi2 * xi2+ writeIORef xi1Ref xi1+ writeIORef xi2Ref xi2+ writeIORef psiRef psi+ loop+ else writeIORef psiRef $ sqrt (- 2.0 * log psi / psi)+ return $+ do flag <- readIORef flagRef+ if flag+ then do writeIORef flagRef False+ readIORef nextRef+ else do writeIORef xi1Ref 0.0+ writeIORef xi2Ref 0.0+ writeIORef psiRef 0.0+ loop+ xi1 <- readIORef xi1Ref+ xi2 <- readIORef xi2Ref+ psi <- readIORef psiRef+ writeIORef flagRef True+ writeIORef nextRef $ xi2 * psi+ return $ xi1 * psi++-- | Return the exponential random number with the specified mean.+generateExponential :: IO Double+ -- ^ the generator+ -> Double+ -- ^ the mean+ -> IO Double+generateExponential g mu =+ do x <- g+ return (- log x * mu)++-- | Return the Erlang random number.+generateErlang :: IO Double+ -- ^ the generator+ -> Double+ -- ^ the scale+ -> Int+ -- ^ the shape+ -> IO Double+generateErlang g beta m =+ do x <- loop m 1+ return (- log x * beta)+ where loop m acc+ | m < 0 = error "Negative shape: generateErlang."+ | m == 0 = return acc+ | otherwise = do x <- g+ loop (m - 1) (x * acc)++-- | Generate the Poisson random number with the specified mean.+generatePoisson :: IO Double+ -- ^ the generator+ -> Double+ -- ^ the mean+ -> IO Int+generatePoisson g mu =+ do prob0 <- g+ let loop prob prod acc+ | prob <= prod = return acc+ | otherwise = loop+ (prob - prod)+ (prod * mu / fromIntegral (acc + 1))+ (acc + 1)+ loop prob0 (exp (- mu)) 0++-- | Generate a binomial random number with the specified probability and number of trials. +generateBinomial :: IO Double+ -- ^ the generator+ -> Double + -- ^ the probability+ -> Int+ -- ^ the number of trials+ -> IO Int+generateBinomial g prob trials = loop trials 0 where+ loop n acc+ | n < 0 = error "Negative number of trials: generateBinomial."+ | n == 0 = return acc+ | otherwise = do x <- g+ if x <= prob+ then loop (n - 1) (acc + 1)+ else loop (n - 1) acc++-- | Defines a type of the random number generator.+data GeneratorType = SimpleGenerator+ -- ^ The simple random number generator.+ | SimpleGeneratorWithSeed Int+ -- ^ The simple random number generator with the specified seed.+ | CustomGenerator (IO Generator)+ -- ^ The custom random number generator.++-- | Create a new random number generator by the specified type.+newGenerator :: GeneratorType -> IO Generator+newGenerator tp =+ case tp of+ SimpleGenerator ->+ newStdGen >>= newRandomGenerator+ SimpleGeneratorWithSeed x ->+ newRandomGenerator $ mkStdGen x+ CustomGenerator g ->+ g++-- | Create a new random generator by the specified standard generator.+newRandomGenerator :: RandomGen g => g -> IO Generator+newRandomGenerator g =+ do r <- newIORef g+ let g1 = do g <- readIORef r+ let (x, g') = random g+ writeIORef r g'+ return x+ g2 <- newNormalGenerator g1+ let g3 mu nu =+ do x <- g2+ return $ mu + nu * x+ return Generator { generatorUniform = generateUniform g1,+ generatorNormal = g3,+ generatorExponential = generateExponential g1,+ generatorErlang = generateErlang g1,+ generatorPoisson = generatePoisson g1,+ generatorBinomial = generateBinomial g1 }
Simulation/Aivika/Internal/Cont.hs view
@@ -12,17 +12,35 @@ -- the continuations is the 'Event' computation. -- module Simulation.Aivika.Internal.Cont- (Cont(..),+ (ContCancellation(..),+ ContCancellationSource,+ Cont(..), ContParams,+ newContCancellationSource,+ contCancellationInitiated,+ contCancellationInitiate,+ contCancellationInitiating,+ contCancellationBind,+ contCancellationConnect, invokeCont, runCont,+ rerunCont,+ spawnCont,+ contParallel,+ contParallel_, catchCont, finallyCont, throwCont, resumeCont,- contCanceled) where+ resumeECont,+ contCanceled,+ contFreeze,+ contAwait) where import Data.IORef+import Data.Array+import Data.Array.IO.Safe+import Data.Monoid import qualified Control.Exception as C import Control.Exception (IOException, throw)@@ -31,10 +49,117 @@ import Control.Monad.Trans import Simulation.Aivika.Internal.Specs+import Simulation.Aivika.Internal.Parameter import Simulation.Aivika.Internal.Simulation import Simulation.Aivika.Internal.Dynamics import Simulation.Aivika.Internal.Event+import Simulation.Aivika.Internal.Signal +-- | It defines how the parent and child computations should be cancelled.+data ContCancellation = CancelTogether+ -- ^ Cancel the both computations together.+ | CancelChildAfterParent+ -- ^ Cancel the child if its parent is cancelled.+ | CancelParentAfterChild+ -- ^ Cancel the parent if its child is cancelled.+ | CancelInIsolation+ -- ^ Cancel the computations in isolation.++-- | It manages the cancellation process.+data ContCancellationSource =+ ContCancellationSource { contCancellationInitiatedRef :: IORef Bool,+ contCancellationActivatedRef :: IORef Bool,+ contCancellationInitiatingSource :: SignalSource ()+ }++-- | Create the cancellation source.+newContCancellationSource :: Simulation ContCancellationSource+newContCancellationSource =+ Simulation $ \r ->+ do r1 <- newIORef False+ r2 <- newIORef False+ s <- invokeSimulation r newSignalSource+ return ContCancellationSource { contCancellationInitiatedRef = r1,+ contCancellationActivatedRef = r2,+ contCancellationInitiatingSource = s+ }++-- | Signal when the cancellation is intiating.+contCancellationInitiating :: ContCancellationSource -> Signal ()+contCancellationInitiating =+ publishSignal . contCancellationInitiatingSource++-- | Whether the cancellation was initiated.+contCancellationInitiated :: ContCancellationSource -> Event Bool+contCancellationInitiated x =+ Event $ \p -> readIORef (contCancellationInitiatedRef x)++-- | Whether the cancellation was activated.+contCancellationActivated :: ContCancellationSource -> IO Bool+contCancellationActivated =+ readIORef . contCancellationActivatedRef++-- | Deactivate the cancellation.+contCancellationDeactivate :: ContCancellationSource -> IO ()+contCancellationDeactivate x =+ writeIORef (contCancellationActivatedRef x) False++-- | If the main computation is cancelled then all the nested ones will be cancelled too.+contCancellationBind :: ContCancellationSource -> [ContCancellationSource] -> Event (Event ())+contCancellationBind x ys =+ Event $ \p ->+ do hs1 <- forM ys $ \y ->+ invokeEvent p $+ handleSignal (contCancellationInitiating x) $ \_ ->+ contCancellationInitiate y+ hs2 <- forM ys $ \y ->+ invokeEvent p $+ handleSignal (contCancellationInitiating y) $ \_ ->+ contCancellationInitiate x+ return $ do sequence_ hs1+ sequence_ hs2++-- | Connect the parent computation to the child one.+contCancellationConnect :: ContCancellationSource+ -- ^ the parent+ -> ContCancellation+ -- ^ how to connect+ -> ContCancellationSource+ -- ^ the child+ -> Event (Event ())+ -- ^ computation of the disposable handler+contCancellationConnect parent cancellation child =+ Event $ \p ->+ do let m1 =+ handleSignal (contCancellationInitiating parent) $ \_ ->+ contCancellationInitiate child+ m2 =+ handleSignal (contCancellationInitiating child) $ \_ ->+ contCancellationInitiate parent+ h1 <- + case cancellation of+ CancelTogether -> invokeEvent p m1+ CancelChildAfterParent -> invokeEvent p m1+ CancelParentAfterChild -> return $ return ()+ CancelInIsolation -> return $ return ()+ h2 <-+ case cancellation of+ CancelTogether -> invokeEvent p m2+ CancelChildAfterParent -> return $ return ()+ CancelParentAfterChild -> invokeEvent p m2+ CancelInIsolation -> return $ return ()+ return $ h1 >> h2++-- | Initiate the cancellation.+contCancellationInitiate :: ContCancellationSource -> Event ()+contCancellationInitiate x =+ Event $ \p ->+ do f <- readIORef (contCancellationInitiatedRef x)+ unless f $+ do writeIORef (contCancellationInitiatedRef x) True+ writeIORef (contCancellationActivatedRef x) True+ invokeEvent p $ triggerSignal (contCancellationInitiatingSource x) ()+ -- | The 'Cont' type is similar to the standard Cont monad -- and F# async workflow but only the result of applying -- the continuations return the 'Event' computation.@@ -49,13 +174,17 @@ data ContParamsAux = ContParamsAux { contECont :: IOException -> Event (), contCCont :: () -> Event (),- contCancelToken :: IORef Bool,- contCatchFlag :: Bool }+ contCancelSource :: ContCancellationSource,+ contCancelFlag :: IO Bool,+ contCatchFlag :: Bool } instance Monad Cont where return = returnC m >>= k = bindC m k +instance ParameterLift Cont where+ liftParameter = liftPC+ instance SimulationLift Cont where liftSimulation = liftSC @@ -78,7 +207,7 @@ cancelCont :: Point -> ContParams a -> IO () {-# NOINLINE cancelCont #-} cancelCont p c =- do writeIORef (contCancelToken $ contAux c) False+ do contCancellationDeactivate (contCancelSource $ contAux c) invokeEvent p $ (contCCont $ contAux c) () returnC :: a -> Cont a@@ -115,19 +244,19 @@ let cont a = invokeCont c (k a) in c { contCont = cont } --- It is not tail recursive!-bindWithCatch :: Cont a -> (a -> Cont b) -> ContParams b -> Event ()-{-# NOINLINE bindWithCatch #-}-bindWithCatch (Cont m) k c = - Event $ \p ->- do z <- contCanceled c- if z - then cancelCont p c- else invokeEvent p $ m $ - let cont a = catchEvent - (invokeCont c (k a))- (contECont $ contAux c)- in c { contCont = cont }+-- -- It is not tail recursive!+-- bindWithCatch :: Cont a -> (a -> Cont b) -> ContParams b -> Event ()+-- {-# NOINLINE bindWithCatch #-}+-- bindWithCatch (Cont m) k c = +-- Event $ \p ->+-- do z <- contCanceled c+-- if z +-- then cancelCont p c+-- else invokeEvent p $ m $ +-- let cont a = catchEvent +-- (invokeCont c (k a))+-- (contECont $ contAux c)+-- in c { contCont = cont } -- Like "bindWithoutCatch (return a) k" callWithoutCatch :: (a -> Cont b) -> a -> ContParams b -> Event ()@@ -138,26 +267,22 @@ then cancelCont p c else invokeEvent p $ invokeCont c (k a) --- Like "bindWithCatch (return a) k" but it is not tail recursive!-callWithCatch :: (a -> Cont b) -> a -> ContParams b -> Event ()-callWithCatch k a c =- Event $ \p ->- do z <- contCanceled c- if z - then cancelCont p c- else invokeEvent p $ catchEvent - (invokeCont c (k a))- (contECont $ contAux c)+-- -- Like "bindWithCatch (return a) k" but it is not tail recursive!+-- callWithCatch :: (a -> Cont b) -> a -> ContParams b -> Event ()+-- callWithCatch k a c =+-- Event $ \p ->+-- do z <- contCanceled c+-- if z +-- then cancelCont p c+-- else invokeEvent p $ catchEvent +-- (invokeCont c (k a))+-- (contECont $ contAux c) -- | Exception handling within 'Cont' computations. catchCont :: Cont a -> (IOException -> Cont a) -> Cont a catchCont m h = - Cont $ \c -> - if contCatchFlag . contAux $ c- then catchWithCatch m h c- else error $- "To catch exceptions, the process must be created " ++- "with help of newProcessIDWithCatch: catchCont."+ Cont $ \c ->+ catchWithCatch m h (c { contAux = (contAux c) { contCatchFlag = True } }) catchWithCatch :: Cont a -> (IOException -> Cont a) -> ContParams a -> Event () catchWithCatch (Cont m) h c =@@ -174,11 +299,7 @@ finallyCont :: Cont a -> Cont b -> Cont a finallyCont m m' = Cont $ \c -> - if contCatchFlag . contAux $ c- then finallyWithCatch m m' c- else error $- "To finalize computation, the process must be created " ++- "with help of newProcessIdWithCatch: finallyCont."+ finallyWithCatch m m' (c { contAux = (contAux c) { contCatchFlag = True } }) finallyWithCatch :: Cont a -> Cont b -> ContParams a -> Event () finallyWithCatch (Cont m) (Cont m') c =@@ -216,8 +337,8 @@ throwCont :: IOException -> Cont a throwCont e = liftIO $ throw e --- | Run the 'Cont' computation with the specified cancelation token --- and flag indicating whether to catch exceptions.+-- | Run the 'Cont' computation with the specified cancelation source +-- and flag indicating whether to catch exceptions from the beginning. runCont :: Cont a -- ^ the computation to run -> (a -> Event ())@@ -226,19 +347,29 @@ -- ^ the branch for handing exceptions -> (() -> Event ()) -- ^ the branch for cancellation- -> IORef Bool- -- ^ the cancellation token+ -> ContCancellationSource+ -- ^ the cancellation source -> Bool- -- ^ whether to support the exception catching+ -- ^ whether to support the exception handling from the beginning -> Event ()-runCont (Cont m) cont econt ccont cancelToken catchFlag = +runCont (Cont m) cont econt ccont cancelSource catchFlag = m ContParams { contCont = cont, contAux = ContParamsAux { contECont = econt, contCCont = ccont,- contCancelToken = cancelToken, - contCatchFlag = catchFlag } }+ contCancelSource = cancelSource,+ contCancelFlag = contCancellationActivated cancelSource, + contCatchFlag = catchFlag } } +-- | Lift the 'Parameter' computation.+liftPC :: Parameter a -> Cont a+liftPC (Parameter m) = + Cont $ \c ->+ Event $ \p ->+ if contCatchFlag . contAux $ c+ then liftIOWithCatch (m $ pointRun p) p c+ else liftIOWithoutCatch (m $ pointRun p) p c+ -- | Lift the 'Simulation' computation. liftSC :: Simulation a -> Cont a liftSC (Simulation m) = @@ -314,7 +445,262 @@ then cancelCont p c else invokeEvent p $ contCont c a +-- | Resume the exception handling by the specified parameters.+resumeECont :: ContParams a -> IOException -> Event ()+{-# INLINE resumeECont #-}+resumeECont c e = + Event $ \p ->+ do z <- contCanceled c+ if z+ then cancelCont p c+ else invokeEvent p $ (contECont $ contAux c) e+ -- | Test whether the computation is canceled. contCanceled :: ContParams a -> IO Bool {-# INLINE contCanceled #-}-contCanceled c = readIORef $ contCancelToken $ contAux c+contCanceled c = contCancelFlag $ contAux c++-- | Execute the specified computations in parallel within+-- the current computation and return their results. The cancellation+-- of any of the nested computations affects the current computation.+-- The exception raised in any of the nested computations is propogated+-- to the current computation as well (if the exception handling is+-- supported).+--+-- Here word @parallel@ literally means that the computations are+-- actually executed on a single operating system thread but+-- they are processed simultaneously by the event queue.+contParallel :: [(Cont a, ContCancellationSource)]+ -- ^ the list of:+ -- the nested computation,+ -- the cancellation source+ -> Cont [a]+contParallel xs =+ Cont $ \c ->+ Event $ \p ->+ do let n = length xs+ worker =+ do results <- newArray_ (1, n) :: IO (IOArray Int a)+ counter <- newIORef 0+ catchRef <- newIORef Nothing+ hs <- invokeEvent p $+ contCancellationBind (contCancelSource $ contAux c) $+ map snd xs+ let propagate =+ Event $ \p ->+ do n' <- readIORef counter+ when (n' == n) $+ do invokeEvent p hs -- unbind the cancellation sources+ f1 <- contCanceled c+ f2 <- readIORef catchRef+ case (f1, f2) of+ (False, Nothing) ->+ do rs <- getElems results+ invokeEvent p $ resumeCont c rs+ (False, Just e) ->+ invokeEvent p $ resumeECont c e+ (True, _) ->+ cancelCont p c+ cont i a =+ Event $ \p ->+ do modifyIORef counter (+ 1)+ writeArray results i a+ invokeEvent p propagate+ econt e =+ Event $ \p ->+ do modifyIORef counter (+ 1)+ r <- readIORef catchRef+ case r of+ Nothing -> writeIORef catchRef $ Just e+ Just e' -> return () -- ignore the next error+ invokeEvent p propagate+ ccont e =+ Event $ \p ->+ do modifyIORef counter (+ 1)+ -- the main computation was automatically canceled+ invokeEvent p propagate+ forM_ (zip [1..n] xs) $ \(i, (x, cancelSource)) ->+ invokeEvent p $+ runCont x (cont i) econt ccont cancelSource (contCatchFlag $ contAux c)+ z <- contCanceled c+ if z+ then cancelCont p c+ else if n == 0+ then invokeEvent p $ contCont c []+ else worker++-- | A partial case of 'contParallel' when we are not interested in+-- the results but we are interested in the actions to be peformed by+-- the nested computations.+contParallel_ :: [(Cont a, ContCancellationSource)]+ -- ^ the list of:+ -- the nested computation,+ -- the cancellation source+ -> Cont ()+contParallel_ xs =+ Cont $ \c ->+ Event $ \p ->+ do let n = length xs+ worker =+ do counter <- newIORef 0+ catchRef <- newIORef Nothing+ hs <- invokeEvent p $+ contCancellationBind (contCancelSource $ contAux c) $+ map snd xs+ let propagate =+ Event $ \p ->+ do n' <- readIORef counter+ when (n' == n) $+ do invokeEvent p hs -- unbind the cancellation sources+ f1 <- contCanceled c+ f2 <- readIORef catchRef+ case (f1, f2) of+ (False, Nothing) ->+ invokeEvent p $ resumeCont c ()+ (False, Just e) ->+ invokeEvent p $ resumeECont c e+ (True, _) ->+ cancelCont p c+ cont i a =+ Event $ \p ->+ do modifyIORef counter (+ 1)+ -- ignore the result+ invokeEvent p propagate+ econt e =+ Event $ \p ->+ do modifyIORef counter (+ 1)+ r <- readIORef catchRef+ case r of+ Nothing -> writeIORef catchRef $ Just e+ Just e' -> return () -- ignore the next error+ invokeEvent p propagate+ ccont e =+ Event $ \p ->+ do modifyIORef counter (+ 1)+ -- the main computation was automatically canceled+ invokeEvent p propagate+ forM_ (zip [1..n] xs) $ \(i, (x, cancelSource)) ->+ invokeEvent p $+ runCont x (cont i) econt ccont cancelSource (contCatchFlag $ contAux c)+ z <- contCanceled c+ if z+ then cancelCont p c+ else if n == 0+ then invokeEvent p $ contCont c ()+ else worker++-- | Rerun the 'Cont' computation with the specified cancellation source.+rerunCont :: Cont a -> ContCancellationSource -> Cont a+rerunCont x cancelSource =+ Cont $ \c ->+ Event $ \p ->+ do let worker =+ do hs <- invokeEvent p $+ contCancellationBind (contCancelSource $ contAux c) [cancelSource]+ let cont a =+ Event $ \p ->+ do invokeEvent p hs -- unbind the cancellation source+ invokeEvent p $ resumeCont c a+ econt e =+ Event $ \p ->+ do invokeEvent p hs -- unbind the cancellation source+ invokeEvent p $ resumeECont c e+ ccont e =+ Event $ \p ->+ do invokeEvent p hs -- unbind the cancellation source+ cancelCont p c+ invokeEvent p $+ runCont x cont econt ccont cancelSource (contCatchFlag $ contAux c)+ z <- contCanceled c+ if z+ then cancelCont p c+ else worker++-- | Run the 'Cont' computation in parallel but connect the cancellation sources.+spawnCont :: ContCancellation -> Cont () -> ContCancellationSource -> Cont ()+spawnCont cancellation x cancelSource =+ Cont $ \c ->+ Event $ \p ->+ do let worker =+ do hs <- invokeEvent p $+ contCancellationConnect+ (contCancelSource $ contAux c) cancellation cancelSource+ let cont a =+ Event $ \p ->+ do invokeEvent p hs -- unbind the cancellation source+ -- do nothing and it will finish the computation+ econt e =+ Event $ \p ->+ do invokeEvent p hs -- unbind the cancellation source+ invokeEvent p $ throwEvent e -- this is all we can do+ ccont e =+ Event $ \p ->+ do invokeEvent p hs -- unbind the cancellation source+ -- do nothing and it will finish the computation+ invokeEvent p $+ enqueueEvent (pointTime p) $+ runCont x cont econt ccont cancelSource False+ invokeEvent p $+ resumeCont c ()+ z <- contCanceled c+ if z+ then cancelCont p c+ else worker++-- | Freeze the computation parameters temporarily.+contFreeze :: ContParams a -> Event (Event (Maybe (ContParams a)))+contFreeze c =+ Event $ \p ->+ do rh <- newIORef Nothing+ rc <- newIORef $ Just c+ h <- invokeEvent p $+ handleSignal (contCancellationInitiating $+ contCancelSource $+ contAux c) $ \a ->+ Event $ \p ->+ do h <- readIORef rh+ case h of+ Nothing ->+ error "The handler was lost: contFreeze."+ Just h ->+ do invokeEvent p h+ c <- readIORef rc+ case c of+ Nothing -> return ()+ Just c ->+ do writeIORef rc Nothing+ invokeEvent p $+ enqueueEvent (pointTime p) $+ Event $ \p ->+ do z <- contCanceled c+ when z $ cancelCont p c+ writeIORef rh (Just h)+ return $+ Event $ \p ->+ do invokeEvent p h+ c <- readIORef rc+ writeIORef rc Nothing+ return c+ +-- | Await the signal.+contAwait :: Signal a -> Cont a+contAwait signal =+ Cont $ \c ->+ Event $ \p ->+ do c <- invokeEvent p $ contFreeze c+ r <- newIORef Nothing+ h <- invokeEvent p $+ handleSignal signal $ + \a -> Event $ + \p -> do x <- readIORef r+ case x of+ Nothing ->+ error "The signal was lost: awaitSignal."+ Just x ->+ do invokeEvent p x+ c <- invokeEvent p c+ case c of+ Nothing -> return ()+ Just c ->+ invokeEvent p $ resumeCont c a+ writeIORef r $ Just h
Simulation/Aivika/Internal/Dynamics.hs view
@@ -25,10 +25,7 @@ catchDynamics, finallyDynamics, throwDynamics,- -- * Time parameters- starttime,- stoptime,- dt,+ -- * Simulation Time time, isTimeInteg, integIteration,@@ -42,6 +39,7 @@ import Control.Monad.Fix import Simulation.Aivika.Internal.Specs+import Simulation.Aivika.Internal.Parameter import Simulation.Aivika.Internal.Simulation -- | A value in the 'Dynamics' monad represents a polymorphic time varying function.@@ -143,18 +141,26 @@ instance MonadIO Dynamics where liftIO m = Dynamics $ const m +instance ParameterLift Dynamics where+ liftParameter = liftDP+ instance SimulationLift Dynamics where liftSimulation = liftDS +liftDP :: Parameter a -> Dynamics a+{-# INLINE liftDP #-}+liftDP (Parameter m) =+ Dynamics $ \p -> m $ pointRun p+ liftDS :: Simulation a -> Dynamics a {-# INLINE liftDS #-} liftDS (Simulation m) = Dynamics $ \p -> m $ pointRun p --- | A type class to lift the 'Dynamics' computations to other monads.-class Monad m => DynamicsLift m where+-- | A type class to lift the 'Dynamics' computations to other computations.+class DynamicsLift m where - -- | Lift the specified 'Dynamics' computation to another monad.+ -- | Lift the specified 'Dynamics' computation to another computation. liftDynamics :: Dynamics a -> m a instance DynamicsLift Dynamics where@@ -187,19 +193,7 @@ Dynamics $ \p -> do { rec { a <- invokeDynamics p (f a) }; return a } --- | Return the start simulation time.-starttime :: Dynamics Double-starttime = Dynamics $ return . spcStartTime . pointSpecs---- | Return the stop simulation time.-stoptime :: Dynamics Double-stoptime = Dynamics $ return . spcStopTime . pointSpecs---- | Return the integration time step.-dt :: Dynamics Double-dt = Dynamics $ return . spcDT . pointSpecs---- | Return the current simulation time.+-- | Computation that returns the current simulation time. time :: Dynamics Double time = Dynamics $ return . pointTime
Simulation/Aivika/Internal/Event.hs view
@@ -17,7 +17,6 @@ Event(..), EventLift(..), EventProcessing(..),- EventCancellation(..), invokeEvent, runEvent, runEventInStartTime,@@ -28,14 +27,18 @@ enqueueEventWithTimes, enqueueEventWithPoints, enqueueEventWithIntegTimes,- enqueueEventWithStartTime,- enqueueEventWithStopTime,- enqueueEventWithCurrentTime, eventQueueCount,+ -- * Cancelling Event+ EventCancellation,+ cancelEvent,+ eventCancelled,+ eventFinished, -- * Error Handling catchEvent, finallyEvent,- throwEvent) where+ throwEvent,+ -- * Memoization+ memoEvent) where import Data.IORef @@ -49,6 +52,7 @@ import qualified Simulation.Aivika.PriorityQueue as PQ import Simulation.Aivika.Internal.Specs+import Simulation.Aivika.Internal.Parameter import Simulation.Aivika.Internal.Simulation import Simulation.Aivika.Internal.Dynamics @@ -83,12 +87,20 @@ instance MonadIO Event where liftIO m = Event $ const m +instance ParameterLift Event where+ liftParameter = liftPS+ instance SimulationLift Event where liftSimulation = liftES instance DynamicsLift Event where liftDynamics = liftDS +liftPS :: Parameter a -> Event a+{-# INLINE liftPS #-}+liftPS (Parameter m) =+ Event $ \p -> m $ pointRun p+ liftES :: Simulation a -> Event a {-# INLINE liftES #-} liftES (Simulation m) =@@ -99,10 +111,10 @@ liftDS (Dynamics m) = Event m --- | A type class to lift the 'Event' computation to other monads.-class Monad m => EventLift m where+-- | A type class to lift the 'Event' computation to other computations.+class EventLift m where - -- | Lift the specified 'Event' computation to another monad.+ -- | Lift the specified 'Event' computation to another computation. liftEvent :: Event a -> m a instance EventLift Event where@@ -287,35 +299,12 @@ let points = integPoints $ pointRun p in invokeEvent p $ enqueueEventWithPoints points e --- | Actuate the event handler in the start time.-enqueueEventWithStartTime :: Event () -> Event ()-enqueueEventWithStartTime e =- Event $ \p ->- let point = integStartPoint $ pointRun p- in invokeEvent p $ enqueueEventWithPoints [point] e---- | Actuate the event handler in the stop time.-enqueueEventWithStopTime :: Event () -> Event ()-enqueueEventWithStopTime e =- Event $ \p ->- let point = integStopPoint $ pointRun p- in invokeEvent p $ enqueueEventWithPoints [point] e---- | Actuate the event handler in the current time but --- through the event queue, which allows continuing the --- current tasks and then calling the handler after the --- tasks are finished. The simulation time will be the same.-enqueueEventWithCurrentTime :: Event () -> Event ()-enqueueEventWithCurrentTime e =- Event $ \p ->- invokeEvent p $ enqueueEvent (pointTime p) e- -- | It allows cancelling the event. data EventCancellation = EventCancellation { cancelEvent :: Event (), -- ^ Cancel the event.- eventCanceled :: Event Bool,- -- ^ Test whether the event was canceled.+ eventCancelled :: Event Bool,+ -- ^ Test whether the event was cancelled. eventFinished :: Event Bool -- ^ Test whether the event was processed and finished. }@@ -324,26 +313,40 @@ enqueueEventWithCancellation :: Double -> Event () -> Event EventCancellation enqueueEventWithCancellation t e = Event $ \p ->- do canceledRef <- newIORef False+ do cancelledRef <- newIORef False cancellableRef <- newIORef True finishedRef <- newIORef False let cancel = Event $ \p -> do x <- readIORef cancellableRef when x $- writeIORef canceledRef True- canceled =- Event $ \p -> readIORef canceledRef+ writeIORef cancelledRef True+ cancelled =+ Event $ \p -> readIORef cancelledRef finished = Event $ \p -> readIORef finishedRef invokeEvent p $ enqueueEvent t $ Event $ \p -> do writeIORef cancellableRef False- x <- readIORef canceledRef+ x <- readIORef cancelledRef unless x $ do invokeEvent p e writeIORef finishedRef True return EventCancellation { cancelEvent = cancel,- eventCanceled = canceled,+ eventCancelled = cancelled, eventFinished = finished }++-- | Memoize the 'Event' computation, always returning the same value+-- within a simulation run.+memoEvent :: Event a -> Simulation (Event a)+memoEvent m =+ do ref <- liftIO $ newIORef Nothing+ return $ Event $ \p ->+ do x <- readIORef ref+ case x of+ Just v -> return v+ Nothing ->+ do v <- invokeEvent p m+ writeIORef ref (Just v)+ return v
+ Simulation/Aivika/Internal/Parameter.hs view
@@ -0,0 +1,251 @@++{-# LANGUAGE RecursiveDo #-}++-- |+-- Module : Simulation.Aivika.Internal.Parameter+-- Copyright : Copyright (c) 2009-2013, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.6.3+--+-- The module defines the 'Parameter' monad that allows representing the model+-- parameters. For example, they can be used when running the Monte-Carlo simulation.+-- +module Simulation.Aivika.Internal.Parameter+ (-- * Parameter+ Parameter(..),+ ParameterLift(..),+ invokeParameter,+ runParameter,+ runParameters,+ -- * Error Handling+ catchParameter,+ finallyParameter,+ throwParameter,+ -- * Predefined Parameters+ simulationIndex,+ simulationCount,+ simulationSpecs,+ starttime,+ stoptime,+ dt,+ generatorParameter,+ -- * Memoization+ memoParameter,+ -- * Utilities+ tableParameter) where++import qualified Control.Exception as C+import Control.Exception (IOException, throw, finally)+import Control.Concurrent.MVar++import Control.Monad+import Control.Monad.Trans+import Control.Monad.Fix++import Data.IORef+import qualified Data.Map as M+import Data.Array++import Simulation.Aivika.Generator+import Simulation.Aivika.Internal.Specs++-- | The 'Parameter' monad that allows specifying the model parameters.+-- For example, they can be used when running the Monte-Carlo simulation.+newtype Parameter a = Parameter (Run -> IO a)++instance Monad Parameter where+ return = returnP+ m >>= k = bindP m k++returnP :: a -> Parameter a+{-# INLINE returnP #-}+returnP a = Parameter (\r -> return a)++bindP :: Parameter a -> (a -> Parameter b) -> Parameter b+{-# INLINE bindP #-}+bindP (Parameter m) k = + Parameter $ \r -> + do a <- m r+ let Parameter m' = k a+ m' r++-- | Run the parameter using the specified specs.+runParameter :: Parameter a -> Specs -> IO a+runParameter (Parameter m) sc =+ do q <- newEventQueue sc+ g <- newGenerator $ spcGeneratorType sc+ m Run { runSpecs = sc,+ runIndex = 1,+ runCount = 1,+ runEventQueue = q,+ runGenerator = g }++-- | Run the given number of parameters using the specified specs, +-- where each parameter is distinguished by its index 'parameterIndex'.+runParameters :: Parameter a -> Specs -> Int -> [IO a]+runParameters (Parameter m) sc runs = map f [1 .. runs]+ where f i = do q <- newEventQueue sc+ g <- newGenerator $ spcGeneratorType sc+ m Run { runSpecs = sc,+ runIndex = i,+ runCount = runs,+ runEventQueue = q,+ runGenerator = g }++-- | Return the run index for the current simulation.+simulationIndex :: Parameter Int+simulationIndex = Parameter $ return . runIndex++-- | Return the number of simulations currently run.+simulationCount :: Parameter Int+simulationCount = Parameter $ return . runCount++-- | Return the simulation specs.+simulationSpecs :: Parameter Specs+simulationSpecs = Parameter $ return . runSpecs++-- | Return the random number generator for the simulation run.+generatorParameter :: Parameter Generator+generatorParameter = Parameter $ return . runGenerator++instance Functor Parameter where+ fmap = liftMP++instance Eq (Parameter a) where+ x == y = error "Can't compare parameters." ++instance Show (Parameter a) where+ showsPrec _ x = showString "<< Parameter >>"++liftMP :: (a -> b) -> Parameter a -> Parameter b+{-# INLINE liftMP #-}+liftMP f (Parameter x) =+ Parameter $ \r -> do { a <- x r; return $ f a }++liftM2P :: (a -> b -> c) -> Parameter a -> Parameter b -> Parameter c+{-# INLINE liftM2P #-}+liftM2P f (Parameter x) (Parameter y) =+ Parameter $ \r -> do { a <- x r; b <- y r; return $ f a b }++instance (Num a) => Num (Parameter a) where+ x + y = liftM2P (+) x y+ x - y = liftM2P (-) x y+ x * y = liftM2P (*) x y+ negate = liftMP negate+ abs = liftMP abs+ signum = liftMP signum+ fromInteger i = return $ fromInteger i++instance (Fractional a) => Fractional (Parameter a) where+ x / y = liftM2P (/) x y+ recip = liftMP recip+ fromRational t = return $ fromRational t++instance (Floating a) => Floating (Parameter a) where+ pi = return pi+ exp = liftMP exp+ log = liftMP log+ sqrt = liftMP sqrt+ x ** y = liftM2P (**) x y+ sin = liftMP sin+ cos = liftMP cos+ tan = liftMP tan+ asin = liftMP asin+ acos = liftMP acos+ atan = liftMP atan+ sinh = liftMP sinh+ cosh = liftMP cosh+ tanh = liftMP tanh+ asinh = liftMP asinh+ acosh = liftMP acosh+ atanh = liftMP atanh++instance MonadIO Parameter where+ liftIO m = Parameter $ const m++-- | A type class to lift the parameters to other computations.+class ParameterLift m where+ + -- | Lift the specified 'Parameter' computation to another computation.+ liftParameter :: Parameter a -> m a++instance ParameterLift Parameter where+ liftParameter = id+ +-- | Exception handling within 'Parameter' computations.+catchParameter :: Parameter a -> (IOException -> Parameter a) -> Parameter a+catchParameter (Parameter m) h =+ Parameter $ \r -> + C.catch (m r) $ \e ->+ let Parameter m' = h e in m' r+ +-- | A computation with finalization part like the 'finally' function.+finallyParameter :: Parameter a -> Parameter b -> Parameter a+finallyParameter (Parameter m) (Parameter m') =+ Parameter $ \r ->+ C.finally (m r) (m' r)++-- | Like the standard 'throw' function.+throwParameter :: IOException -> Parameter a+throwParameter = throw++-- | Invoke the 'Parameter' computation.+invokeParameter :: Run -> Parameter a -> IO a+{-# INLINE invokeParameter #-}+invokeParameter r (Parameter m) = m r++instance MonadFix Parameter where+ mfix f = + Parameter $ \r ->+ do { rec { a <- invokeParameter r (f a) }; return a } ++-- | Memoize the 'Parameter' computation, always returning the same value+-- within a simulation run. However, the value will be recalculated for other+-- simulation runs. Also it is thread-safe when different simulation runs+-- are executed in parallel on physically different operating system threads.+memoParameter :: Parameter a -> IO (Parameter a)+memoParameter x = + do lock <- newMVar ()+ dict <- newIORef M.empty+ return $ Parameter $ \r ->+ do let i = runIndex r+ m <- readIORef dict+ if M.member i m+ then do let Just v = M.lookup i m+ return v+ else withMVar lock $ + \() -> do { m <- readIORef dict;+ if M.member i m+ then do let Just v = M.lookup i m+ return v+ else do v <- invokeParameter r x+ writeIORef dict $ M.insert i v m+ return v }++-- | Return a parameter which value is taken consequently from the specified table+-- based on the run index of the current simulation starting from zero. After all+-- values from the table are used, it takes again the first value of the table,+-- then the second one and so on.+tableParameter :: Array Int a -> Parameter a+tableParameter t =+ do i <- simulationIndex+ return $ t ! (((i - i1) `mod` n) + i1)+ where (i1, i2) = bounds t+ n = i2 - i1 + 1++-- | Computation that returns the start simulation time.+starttime :: Parameter Double+starttime =+ Parameter $ return . spcStartTime . runSpecs++-- | Computation that returns the final simulation time.+stoptime :: Parameter Double+stoptime =+ Parameter $ return . spcStopTime . runSpecs++-- | Computation that returns the integration time step.+dt :: Parameter Double+dt =+ Parameter $ return . spcDT . runSpecs
Simulation/Aivika/Internal/Process.hs view
@@ -18,29 +18,58 @@ -- A value of the 'ProcessId' type is just an identifier of such a process. -- module Simulation.Aivika.Internal.Process- (ProcessId,+ (-- * Process Monad+ ProcessId, Process(..),+ ProcessLift(..), invokeProcess,+ -- * Running Process runProcess,+ runProcessUsingId, runProcessInStartTime,+ runProcessInStartTimeUsingId, runProcessInStopTime,+ runProcessInStopTimeUsingId,+ -- * Spawning Processes+ spawnProcess,+ spawnProcessUsingId,+ -- * Enqueuing Process enqueueProcess,- enqueueProcessWithStartTime,- enqueueProcessWithStopTime,+ enqueueProcessUsingId,+ -- * Creating Process Identifier newProcessId,- newProcessIdWithCatch,+ processId,+ processUsingId,+ -- * Holding, Interrupting, Passivating and Canceling Process holdProcess, interruptProcess, processInterrupted, passivateProcess, processPassive, reactivateProcess,- processId,+ cancelProcessUsingId, cancelProcess,- processCanceled,+ processCancelled,+ -- * Awaiting Signal+ processAwait,+ -- * Process Timeout+ timeoutProcess,+ timeoutProcessUsingId,+ -- * Parallelizing Processes+ processParallel,+ processParallelUsingIds,+ processParallel_,+ processParallelUsingIds_,+ -- * Exception Handling catchProcess, finallyProcess,- throwProcess) where+ throwProcess,+ -- * Utilities+ zipProcessParallel,+ zip3ProcessParallel,+ unzipProcess,+ -- * Memoizing Process+ memoProcess) where import Data.Maybe import Data.IORef@@ -49,18 +78,18 @@ import Control.Monad.Trans import Simulation.Aivika.Internal.Specs+import Simulation.Aivika.Internal.Parameter import Simulation.Aivika.Internal.Simulation import Simulation.Aivika.Internal.Dynamics import Simulation.Aivika.Internal.Event import Simulation.Aivika.Internal.Cont+import Simulation.Aivika.Internal.Signal -- | Represents a process identifier. data ProcessId = ProcessId { processStarted :: IORef Bool,- processCatchFlag :: Bool, processReactCont :: IORef (Maybe (ContParams ())), - processCancelRef :: IORef Bool, - processCancelToken :: IORef Bool,+ processCancelSource :: ContCancellationSource, processInterruptRef :: IORef Bool, processInterruptCont :: IORef (Maybe (ContParams ())), processInterruptVersion :: IORef Int }@@ -69,6 +98,15 @@ -- and then resume later. newtype Process a = Process (ProcessId -> Cont a) +-- | A type class to lift the 'Process' computation to other computations.+class ProcessLift m where+ + -- | Lift the specified 'Process' computation to another computation.+ liftProcess :: Process a -> m a++instance ProcessLift Process where+ liftProcess = id+ -- | Invoke the process computation. invokeProcess :: ProcessId -> Process a -> Cont a {-# INLINE invokeProcess #-}@@ -123,7 +161,7 @@ a <- readIORef x case a of Nothing -> writeIORef x $ Just c- Just _ -> error "Cannot passivate the process twice: passivate"+ Just _ -> error "Cannot passivate the process twice: passivateProcess" -- | Test whether the process with the specified identifier is passivated. processPassive :: ProcessId -> Event Bool@@ -146,111 +184,113 @@ do writeIORef x Nothing invokeEvent p $ enqueueEvent (pointTime p) $ resumeCont c () --- | Start immediately the process with the specified identifier.+-- | Prepare the processes identifier for running.+processIdPrepare :: ProcessId -> Event ()+processIdPrepare pid =+ Event $ \p ->+ do y <- readIORef (processStarted pid)+ if y+ then error $+ "Another process with the specified identifier " +++ "has been started already: processIdPrepare"+ else writeIORef (processStarted pid) True+ let signal = (contCancellationInitiating $ processCancelSource pid)+ invokeEvent p $+ handleSignal_ signal $ \_ ->+ do interruptProcess pid+ reactivateProcess pid++-- | Run immediately the process. A new 'ProcessId' identifier will be+-- assigned to the process. -- -- To run the process at the specified time, you can use -- the 'enqueueProcess' function.-runProcess :: ProcessId -> Process () -> Event ()-runProcess pid p =- runCont m cont econt ccont (processCancelToken pid) (processCatchFlag pid)- where cont = return- econt = throwEvent- ccont = return- m = do y <- liftIO $ readIORef (processStarted pid)- if y - then error $- "Another process with this identifier " ++- "has been started already: runProcess"- else liftIO $ writeIORef (processStarted pid) True- invokeProcess pid p+runProcess :: Process () -> Event ()+runProcess p =+ do pid <- liftSimulation newProcessId+ runProcessUsingId pid p+ +-- | Run immediately the process with the specified identifier.+-- It will be more efficient than as you would specify the process identifier+-- with help of the 'processUsingId' combinator and then would call 'runProcess'.+-- +-- To run the process at the specified time, you can use+-- the 'enqueueProcessUsingId' function.+runProcessUsingId :: ProcessId -> Process () -> Event ()+runProcessUsingId pid p =+ do processIdPrepare pid+ runCont m cont econt ccont (processCancelSource pid) False+ where cont = return+ econt = throwEvent+ ccont = return+ m = invokeProcess pid p --- | Start the process in the start time immediately.-runProcessInStartTime :: EventProcessing -> ProcessId -> Process () -> Simulation ()-runProcessInStartTime processing pid p =- runEventInStartTime processing $ runProcess pid p+-- | Run the process in the start time immediately.+runProcessInStartTime :: EventProcessing -> Process () -> Simulation ()+runProcessInStartTime processing p =+ runEventInStartTime processing $ runProcess p --- | Start the process in the stop time immediately.-runProcessInStopTime :: EventProcessing -> ProcessId -> Process () -> Simulation ()-runProcessInStopTime processing pid p =- runEventInStopTime processing $ runProcess pid p+-- | Run the process in the start time immediately using the specified identifier.+runProcessInStartTimeUsingId :: EventProcessing -> ProcessId -> Process () -> Simulation ()+runProcessInStartTimeUsingId processing pid p =+ runEventInStartTime processing $ runProcessUsingId pid p --- | Enqueue the process that will be then started at the specified time--- from the event queue.-enqueueProcess :: Double -> ProcessId -> Process () -> Event ()-enqueueProcess t pid p =- enqueueEvent t $ runProcess pid p+-- | Run the process in the final simulation time immediately.+runProcessInStopTime :: EventProcessing -> Process () -> Simulation ()+runProcessInStopTime processing p =+ runEventInStopTime processing $ runProcess p --- | Enqueue the process that will be then started in the start time+-- | Run the process in the final simulation time immediately using the specified identifier.+runProcessInStopTimeUsingId :: EventProcessing -> ProcessId -> Process () -> Simulation ()+runProcessInStopTimeUsingId processing pid p =+ runEventInStopTime processing $ runProcessUsingId pid p++-- | Enqueue the process that will be then started at the specified time -- from the event queue.-enqueueProcessWithStartTime :: ProcessId -> Process () -> Event ()-enqueueProcessWithStartTime pid p =- enqueueEventWithStartTime $ runProcess pid p+enqueueProcess :: Double -> Process () -> Event ()+enqueueProcess t p =+ enqueueEvent t $ runProcess p --- | Enqueue the process that will be then started in the stop time+-- | Enqueue the process that will be then started at the specified time -- from the event queue.-enqueueProcessWithStopTime :: ProcessId -> Process () -> Event ()-enqueueProcessWithStopTime pid p =- enqueueEventWithStopTime $ runProcess pid p+enqueueProcessUsingId :: Double -> ProcessId -> Process () -> Event ()+enqueueProcessUsingId t pid p =+ enqueueEvent t $ runProcessUsingId pid p -- | Return the current process identifier. processId :: Process ProcessId processId = Process return --- | Create a new process identifier without exception handling.+-- | Create a new process identifier. newProcessId :: Simulation ProcessId newProcessId = do x <- liftIO $ newIORef Nothing y <- liftIO $ newIORef False- c <- liftIO $ newIORef False- t <- liftIO $ newIORef False+ c <- newContCancellationSource i <- liftIO $ newIORef False z <- liftIO $ newIORef Nothing v <- liftIO $ newIORef 0 return ProcessId { processStarted = y,- processCatchFlag = False, processReactCont = x, - processCancelRef = c, - processCancelToken = t,+ processCancelSource = c, processInterruptRef = i, processInterruptCont = z, processInterruptVersion = v } --- | Create a new process identifier with capabilities of catching --- the 'IOError' exceptions and finalizing the computation. --- The corresponded process will be slower than that one--- which identifier is created with help of 'newProcessId'.-newProcessIdWithCatch :: Simulation ProcessId-newProcessIdWithCatch =- do x <- liftIO $ newIORef Nothing- y <- liftIO $ newIORef False- c <- liftIO $ newIORef False- t <- liftIO $ newIORef False- i <- liftIO $ newIORef False- z <- liftIO $ newIORef Nothing- v <- liftIO $ newIORef 0- return ProcessId { processStarted = y,- processCatchFlag = True,- processReactCont = x, - processCancelRef = c, - processCancelToken = t,- processInterruptRef = i,- processInterruptCont = z, - processInterruptVersion = v }+-- | Cancel a process with the specified identifier, interrupting it if needed.+cancelProcessUsingId :: ProcessId -> Event ()+cancelProcessUsingId pid = contCancellationInitiate (processCancelSource pid) --- | Cancel a process with the specified identifier.-cancelProcess :: ProcessId -> Event ()-cancelProcess pid =- Event $ \p ->- do z <- readIORef (processCancelRef pid) - unless z $- do writeIORef (processCancelRef pid) True- writeIORef (processCancelToken pid) True+-- | The process cancels itself.+cancelProcess :: Process a+cancelProcess =+ do pid <- processId+ liftEvent $ cancelProcessUsingId pid+ throwProcess $ error "The process must be cancelled already: cancelProcessItself." --- | Test whether the process with the specified identifier was canceled.-processCanceled :: ProcessId -> Event Bool-processCanceled pid =- Event $ \p ->- readIORef (processCancelRef pid)+-- | Test whether the process with the specified identifier was cancelled.+processCancelled :: ProcessId -> Event Bool+processCancelled pid = contCancellationInitiated (processCancelSource pid) instance Eq ProcessId where x == y = processReactCont x == processReactCont y -- for the references are unique@@ -262,6 +302,9 @@ instance Functor Process where fmap = liftM +instance ParameterLift Process where+ liftParameter = liftPP+ instance SimulationLift Process where liftSimulation = liftSP @@ -286,6 +329,10 @@ let Process m' = k a m' pid +liftPP :: Parameter a -> Process a+{-# INLINE liftPP #-}+liftPP m = Process $ \pid -> liftParameter m+ liftSP :: Simulation a -> Process a {-# INLINE liftSP #-} liftSP m = Process $ \pid -> liftSimulation m@@ -323,3 +370,208 @@ throwProcess :: IOException -> Process a throwProcess = liftIO . throw +-- | Execute the specified computations in parallel within+-- the current computation and return their results. The cancellation+-- of any of the nested computations affects the current computation.+-- The exception raised in any of the nested computations is propogated+-- to the current computation as well.+--+-- Here word @parallel@ literally means that the computations are+-- actually executed on a single operating system thread but+-- they are processed simultaneously by the event queue.+--+-- New 'ProcessId' identifiers will be assigned to the started processes.+processParallel :: [Process a] -> Process [a]+processParallel xs =+ liftSimulation (processParallelCreateIds xs) >>= processParallelUsingIds ++-- | Like 'processParallel' but allows specifying the process identifiers.+-- It will be more efficient than as you would specify the process identifiers+-- with help of the 'processUsingId' combinator and then would call 'processParallel'.+processParallelUsingIds :: [(ProcessId, Process a)] -> Process [a]+processParallelUsingIds xs =+ Process $ \pid ->+ do liftEvent $ processParallelPrepare xs+ contParallel $+ flip map xs $ \(pid, m) ->+ (invokeProcess pid m, processCancelSource pid)++-- | Like 'processParallel' but ignores the result.+processParallel_ :: [Process a] -> Process ()+processParallel_ xs =+ liftSimulation (processParallelCreateIds xs) >>= processParallelUsingIds_ ++-- | Like 'processParallelUsingIds' but ignores the result.+processParallelUsingIds_ :: [(ProcessId, Process a)] -> Process ()+processParallelUsingIds_ xs =+ Process $ \pid ->+ do liftEvent $ processParallelPrepare xs+ contParallel_ $+ flip map xs $ \(pid, m) ->+ (invokeProcess pid m, processCancelSource pid)++-- | Create the new process identifiers.+processParallelCreateIds :: [Process a] -> Simulation [(ProcessId, Process a)]+processParallelCreateIds xs =+ do pids <- liftSimulation $ forM xs $ const newProcessId+ return $ zip pids xs++-- | Prepare the processes for parallel execution.+processParallelPrepare :: [(ProcessId, Process a)] -> Event ()+processParallelPrepare xs =+ Event $ \p ->+ forM_ xs $ invokeEvent p . processIdPrepare . fst++-- | Allow calling the process with the specified identifier.+-- It creates a nested process when canceling any of two, or raising an+-- @IO@ exception in any of the both, affects the 'Process' computation.+--+-- At the same time, the interruption has no such effect as it requires+-- explicit specifying the 'ProcessId' identifier of the nested process itself,+-- that is the nested process cannot be interrupted using only the parent+-- process identifier.+processUsingId :: ProcessId -> Process a -> Process a+processUsingId pid x =+ Process $ \pid' ->+ do liftEvent $ processIdPrepare pid+ rerunCont (invokeProcess pid x) (processCancelSource pid)++-- | Spawn the child process specifying how the child and parent processes+-- should be cancelled in case of need.+spawnProcess :: ContCancellation -> Process () -> Process ()+spawnProcess cancellation x =+ do pid <- liftSimulation $ newProcessId+ spawnProcessUsingId cancellation pid x++-- | Spawn the child process specifying how the child and parent processes+-- should be cancelled in case of need.+spawnProcessUsingId :: ContCancellation -> ProcessId -> Process () -> Process ()+spawnProcessUsingId cancellation pid x =+ Process $ \pid' ->+ do liftEvent $ processIdPrepare pid+ spawnCont cancellation (invokeProcess pid x) (processCancelSource pid)++-- | Await the signal.+processAwait :: Signal a -> Process a+processAwait signal =+ Process $ \pid -> contAwait signal++-- | The result of memoization.+data MemoResult a = MemoComputed a+ | MemoError IOException+ | MemoCancelled++-- | Memoize the process so that it would always return the same value+-- within the simulation run.+memoProcess :: Process a -> Simulation (Process a)+memoProcess x =+ do started <- liftIO $ newIORef False+ computed <- newSignalSource+ value <- liftIO $ newIORef Nothing+ let result =+ do Just x <- liftIO $ readIORef value+ case x of+ MemoComputed a -> return a+ MemoError e -> throwProcess e+ MemoCancelled -> cancelProcess+ return $+ do v <- liftIO $ readIORef value+ case v of+ Just _ -> result+ Nothing ->+ do f <- liftIO $ readIORef started+ case f of+ True ->+ do processAwait $ publishSignal computed+ result+ False ->+ do liftIO $ writeIORef started True+ r <- liftIO $ newIORef MemoCancelled+ finallyProcess+ (catchProcess+ (do a <- x -- compute only once!+ liftIO $ writeIORef r (MemoComputed a))+ (\e ->+ liftIO $ writeIORef r (MemoError e)))+ (liftEvent $+ do liftIO $+ do x <- readIORef r+ writeIORef value (Just x)+ triggerSignal computed ())+ result++-- | Zip two parallel processes waiting for the both.+zipProcessParallel :: Process a -> Process b -> Process (a, b)+zipProcessParallel x y =+ do [Left a, Right b] <- processParallel [fmap Left x, fmap Right y]+ return (a, b)++-- | Zip three parallel processes waiting for their results.+zip3ProcessParallel :: Process a -> Process b -> Process c -> Process (a, b, c)+zip3ProcessParallel x y z =+ do [Left a,+ Right (Left b),+ Right (Right c)] <-+ processParallel [fmap Left x,+ fmap (Right . Left) y,+ fmap (Right . Right) z]+ return (a, b, c)++-- | Unzip the process using memoization so that the both returned+-- processes could be applied independently, although they will refer+-- to the same pair of values.+unzipProcess :: Process (a, b) -> Simulation (Process a, Process b)+unzipProcess xy =+ do xy' <- memoProcess xy+ return (fmap fst xy', fmap snd xy')++-- | Try to run the child process within the specified timeout.+-- If the process will finish successfully within this time interval then+-- the result wrapped in 'Just' will be returned; otherwise, the child process+-- will be cancelled and 'Nothing' will be returned.+--+-- If an exception is raised in the child process then it is propagated to+-- the parent computation as well.+--+-- A cancellation of the child process doesn't lead to cancelling the parent process.+-- Then 'Nothing' is returned within the computation.+timeoutProcess :: Double -> Process a -> Process (Maybe a)+timeoutProcess timeout p =+ do pid <- liftSimulation newProcessId+ timeoutProcessUsingId timeout pid p++-- | Try to run the child process with the given identifier within the specified timeout.+-- If the process will finish successfully within this time interval then+-- the result wrapped in 'Just' will be returned; otherwise, the child process+-- will be cancelled and 'Nothing' will be returned.+--+-- If an exception is raised in the child process then it is propagated to+-- the parent computation as well.+--+-- A cancellation of the child process doesn't lead to cancelling the parent process.+-- Then 'Nothing' is returned within the computation.+timeoutProcessUsingId :: Double -> ProcessId -> Process a -> Process (Maybe a)+timeoutProcessUsingId timeout pid p =+ do s <- liftSimulation newSignalSource+ timeoutPid <- liftSimulation newProcessId+ spawnProcessUsingId CancelChildAfterParent timeoutPid $+ finallyProcess+ (holdProcess timeout)+ (liftEvent $+ cancelProcessUsingId pid)+ spawnProcessUsingId CancelChildAfterParent pid $+ do r <- liftIO $ newIORef Nothing+ finallyProcess+ (catchProcess+ (do a <- p+ liftIO $ writeIORef r $ Just (Right a))+ (\e ->+ liftIO $ writeIORef r $ Just (Left e)))+ (liftEvent $+ do x <- liftIO $ readIORef r+ triggerSignal s x)+ x <- processAwait $ publishSignal s+ case x of+ Nothing -> return Nothing+ Just (Right a) -> return (Just a)+ Just (Left e) -> throwProcess e
Simulation/Aivika/Internal/Signal.hs view
@@ -14,10 +14,14 @@ -- module Simulation.Aivika.Internal.Signal- (Signal(..),- SignalSource(..),- newSignalSource,+ (-- * Handling and Triggering Signal+ Signal(..), handleSignal_,+ SignalSource,+ newSignalSource,+ publishSignal,+ triggerSignal,+ -- * Useful Combinators mapSignal, mapSignalM, apSignal,@@ -27,18 +31,39 @@ merge2Signals, merge3Signals, merge4Signals,- merge5Signals) where+ merge5Signals,+ -- * Creating Signal in Time Points+ newSignalInTimes,+ newSignalInIntegTimes,+ newSignalInStartTime,+ newSignalInStopTime,+ -- * Signal History+ SignalHistory,+ signalHistorySignal,+ newSignalHistory,+ readSignalHistory,+ -- * Signalable Computations+ Signalable(..),+ signalableChanged,+ emptySignalable,+ appendSignalable) where import Data.IORef import Data.Monoid+import Data.List+import Data.Array import Control.Monad import Control.Monad.Trans import Simulation.Aivika.Internal.Specs+import Simulation.Aivika.Internal.Parameter import Simulation.Aivika.Internal.Simulation import Simulation.Aivika.Internal.Event +import qualified Simulation.Aivika.Vector as V+import qualified Simulation.Aivika.Vector.Unboxed as UV+ -- | The signal source that can publish its signal. data SignalSource a = SignalSource { publishSignal :: Signal a,@@ -60,15 +85,16 @@ -- | The queue of signal handlers. data SignalHandlerQueue a =- SignalHandlerQueue { queueStart :: IORef (Maybe (SignalHandler a)),- queueEnd :: IORef (Maybe (SignalHandler a)) }+ SignalHandlerQueue { queueList :: IORef [SignalHandler a] } -- | It contains the information about the disposable queue handler. data SignalHandler a = SignalHandler { handlerComp :: a -> Event (),- handlerPrev :: IORef (Maybe (SignalHandler a)),- handlerNext :: IORef (Maybe (SignalHandler a)) }+ handlerRef :: IORef () } +instance Eq (SignalHandler a) where+ x == y = (handlerRef x) == (handlerRef y)+ -- | Subscribe the handler to the specified signal. -- To subscribe the disposable handlers, use function 'handleSignal'. handleSignal_ :: Signal a -> (a -> Event ()) -> Event ()@@ -80,10 +106,8 @@ newSignalSource :: Simulation (SignalSource a) newSignalSource = Simulation $ \r ->- do start <- newIORef Nothing- end <- newIORef Nothing- let queue = SignalHandlerQueue { queueStart = start,- queueEnd = end }+ do list <- newIORef []+ let queue = SignalHandlerQueue { queueList = list } signal = Signal { handleSignal = handle } source = SignalSource { publishSignal = signal, triggerSignal = trigger }@@ -93,73 +117,32 @@ return $ Event $ \p -> dequeueSignalHandler queue x trigger a =- Event $ \p ->- let h = queueStart queue- in triggerSignalHandlers h a p+ Event $ \p -> triggerSignalHandlers queue a p return source -- | Trigger all next signal handlers.-triggerSignalHandlers :: IORef (Maybe (SignalHandler a)) -> a -> Point -> IO ()+triggerSignalHandlers :: SignalHandlerQueue a -> a -> Point -> IO () {-# INLINE triggerSignalHandlers #-}-triggerSignalHandlers r a p =- do x <- readIORef r- case x of- Nothing -> return ()- Just h ->- do invokeEvent p $ handlerComp h a- triggerSignalHandlers (handlerNext h) a p+triggerSignalHandlers q a p =+ do hs <- readIORef (queueList q)+ forM_ hs $ \h ->+ invokeEvent p $ handlerComp h a -- | Enqueue the handler and return its representative in the queue. enqueueSignalHandler :: SignalHandlerQueue a -> (a -> Event ()) -> IO (SignalHandler a)+{-# INLINE enqueueSignalHandler #-} enqueueSignalHandler q h = - do tail <- readIORef (queueEnd q)- case tail of- Nothing ->- do prev <- newIORef Nothing- next <- newIORef Nothing- let handler = SignalHandler { handlerComp = h,- handlerPrev = prev,- handlerNext = next }- writeIORef (queueStart q) (Just handler)- writeIORef (queueEnd q) (Just handler)- return handler- Just x ->- do prev <- newIORef tail- next <- newIORef Nothing- let handler = SignalHandler { handlerComp = h,- handlerPrev = prev,- handlerNext = next }- writeIORef (handlerNext x) (Just handler)- writeIORef (queueEnd q) (Just handler)- return handler+ do r <- newIORef ()+ let handler = SignalHandler { handlerComp = h,+ handlerRef = r }+ modifyIORef (queueList q) (handler :)+ return handler -- | Dequeue the handler representative. dequeueSignalHandler :: SignalHandlerQueue a -> SignalHandler a -> IO ()+{-# INLINE dequeueSignalHandler #-} dequeueSignalHandler q h = - do prev <- readIORef (handlerPrev h)- case prev of- Nothing ->- do next <- readIORef (handlerNext h)- case next of- Nothing ->- do writeIORef (queueStart q) Nothing- writeIORef (queueEnd q) Nothing- Just y ->- do writeIORef (handlerPrev y) Nothing- writeIORef (handlerNext h) Nothing- writeIORef (queueStart q) next- Just x ->- do next <- readIORef (handlerNext h)- case next of- Nothing ->- do writeIORef (handlerPrev h) Nothing- writeIORef (handlerNext x) Nothing- writeIORef (queueEnd q) prev- Just y ->- do writeIORef (handlerPrev h) Nothing- writeIORef (handlerNext h) Nothing- writeIORef (handlerPrev y) prev- writeIORef (handlerNext x) next+ modifyIORef (queueList q) (delete h) instance Functor Signal where fmap = mapSignal@@ -258,3 +241,100 @@ emptySignal :: Signal a emptySignal = Signal { handleSignal = \h -> return $ return () }+ +-- | Represents the history of the signal values.+data SignalHistory a =+ SignalHistory { signalHistorySignal :: Signal a, + -- ^ The signal for which the history is created.+ signalHistoryTimes :: UV.Vector Double,+ signalHistoryValues :: V.Vector a }++-- | Create a history of the signal values.+newSignalHistory :: Signal a -> Event (SignalHistory a)+newSignalHistory signal =+ do ts <- liftIO UV.newVector+ xs <- liftIO V.newVector+ handleSignal_ signal $ \a ->+ Event $ \p ->+ do liftIO $ UV.appendVector ts (pointTime p)+ liftIO $ V.appendVector xs a+ return SignalHistory { signalHistorySignal = signal,+ signalHistoryTimes = ts,+ signalHistoryValues = xs }+ +-- | Read the history of signal values.+readSignalHistory :: SignalHistory a -> Event (Array Int Double, Array Int a)+readSignalHistory history =+ do xs <- liftIO $ UV.freezeVector (signalHistoryTimes history)+ ys <- liftIO $ V.freezeVector (signalHistoryValues history)+ return (xs, ys) + +-- | Trigger the signal with the current time.+triggerSignalWithCurrentTime :: SignalSource Double -> Event ()+triggerSignalWithCurrentTime s =+ Event $ \p -> invokeEvent p $ triggerSignal s (pointTime p)++-- | Return a signal that is triggered in the specified time points.+newSignalInTimes :: [Double] -> Event (Signal Double)+newSignalInTimes xs =+ do s <- liftSimulation newSignalSource+ enqueueEventWithTimes xs $ triggerSignalWithCurrentTime s+ return $ publishSignal s+ +-- | Return a signal that is triggered in the integration time points.+-- It should be called with help of 'runEventInStartTime'.+newSignalInIntegTimes :: Event (Signal Double)+newSignalInIntegTimes =+ do s <- liftSimulation newSignalSource+ enqueueEventWithIntegTimes $ triggerSignalWithCurrentTime s+ return $ publishSignal s+ +-- | Return a signal that is triggered in the start time.+-- It should be called with help of 'runEventInStartTime'.+newSignalInStartTime :: Event (Signal Double)+newSignalInStartTime =+ do s <- liftSimulation newSignalSource+ t <- liftParameter starttime+ enqueueEvent t $ triggerSignalWithCurrentTime s+ return $ publishSignal s++-- | Return a signal that is triggered in the final time.+newSignalInStopTime :: Event (Signal Double)+newSignalInStopTime =+ do s <- liftSimulation newSignalSource+ t <- liftParameter stoptime+ enqueueEvent t $ triggerSignalWithCurrentTime s+ return $ publishSignal s++-- | Describes a computation that also signals when changing its value.+data Signalable a =+ Signalable { readSignalable :: Event a,+ -- ^ Return a computation of the value.+ signalableChanged_ :: Signal ()+ -- ^ Return a signal notifying that the value has changed+ -- but without providing the information about the changed value.+ }++-- | Return a signal notifying that the value has changed.+signalableChanged :: Signalable a -> Signal a+signalableChanged x = mapSignalM (const $ readSignalable x) $ signalableChanged_ x++instance Functor Signalable where+ fmap f x = x { readSignalable = fmap f (readSignalable x) }++instance Monoid a => Monoid (Signalable a) where++ mempty = emptySignalable+ mappend = appendSignalable++-- | Return an identity.+emptySignalable :: Monoid a => Signalable a+emptySignalable =+ Signalable { readSignalable = return mempty,+ signalableChanged_ = mempty }++-- | An associative operation.+appendSignalable :: Monoid a => Signalable a -> Signalable a -> Signalable a+appendSignalable m1 m2 =+ Signalable { readSignalable = liftM2 (<>) (readSignalable m1) (readSignalable m2),+ signalableChanged_ = (signalableChanged_ m1) <> (signalableChanged_ m2) }
Simulation/Aivika/Internal/Simulation.hs view
@@ -9,7 +9,8 @@ -- Stability : experimental -- Tested with: GHC 7.6.3 ----- The module defines the 'Simulation' monad that represents a simulation run.+-- The module defines the 'Simulation' monad that represents a computation+-- within the simulation run. -- module Simulation.Aivika.Internal.Simulation (-- * Simulation@@ -23,10 +24,9 @@ finallySimulation, throwSimulation, -- * Utilities- simulationIndex,- simulationCount,- simulationSpecs,- simulationEventQueue) where+ simulationEventQueue,+ -- * Memoization+ memoSimulation) where import qualified Control.Exception as C import Control.Exception (IOException, throw, finally)@@ -35,19 +35,14 @@ import Control.Monad.Trans import Control.Monad.Fix +import Data.IORef++import Simulation.Aivika.Generator import Simulation.Aivika.Internal.Specs+import Simulation.Aivika.Internal.Parameter --- | A value in the 'Simulation' monad represents something that--- doesn't change within the simulation run but may change for--- other runs.------ This monad is ideal for representing the external--- parameters for the model, when the Monte-Carlo simulation--- is used. Also this monad is useful for defining some--- actions that should occur only once within the simulation run,--- for example, setting of the integral with help of recursive--- equations.---+-- | A value in the 'Simulation' monad represents a computation+-- within the simulation run. newtype Simulation a = Simulation (Run -> IO a) instance Monad Simulation where@@ -70,32 +65,24 @@ runSimulation :: Simulation a -> Specs -> IO a runSimulation (Simulation m) sc = do q <- newEventQueue sc+ g <- newGenerator $ spcGeneratorType sc m Run { runSpecs = sc, runIndex = 1, runCount = 1,- runEventQueue = q }+ runEventQueue = q,+ runGenerator = g } -- | Run the given number of simulations using the specified specs, -- where each simulation is distinguished by its index 'simulationIndex'. runSimulations :: Simulation a -> Specs -> Int -> [IO a] runSimulations (Simulation m) sc runs = map f [1 .. runs] where f i = do q <- newEventQueue sc+ g <- newGenerator $ spcGeneratorType sc m Run { runSpecs = sc, runIndex = i, runCount = runs,- runEventQueue = q }---- | Return the run index for the current simulation.-simulationIndex :: Simulation Int-simulationIndex = Simulation $ return . runIndex---- | Return the number of simulations currently run.-simulationCount :: Simulation Int-simulationCount = Simulation $ return . runCount---- | Return the simulation specs.-simulationSpecs :: Simulation Specs-simulationSpecs = Simulation $ return . runSpecs+ runEventQueue = q,+ runGenerator = g } -- | Return the event queue. simulationEventQueue :: Simulation EventQueue@@ -104,66 +91,30 @@ instance Functor Simulation where fmap = liftMS -instance Eq (Simulation a) where- x == y = error "Can't compare simulation runs." --instance Show (Simulation a) where- showsPrec _ x = showString "<< Simulation >>"- liftMS :: (a -> b) -> Simulation a -> Simulation b {-# INLINE liftMS #-} liftMS f (Simulation x) = Simulation $ \r -> do { a <- x r; return $ f a } -liftM2S :: (a -> b -> c) -> Simulation a -> Simulation b -> Simulation c-{-# INLINE liftM2S #-}-liftM2S f (Simulation x) (Simulation y) =- Simulation $ \r -> do { a <- x r; b <- y r; return $ f a b }--instance (Num a) => Num (Simulation a) where- x + y = liftM2S (+) x y- x - y = liftM2S (-) x y- x * y = liftM2S (*) x y- negate = liftMS negate- abs = liftMS abs- signum = liftMS signum- fromInteger i = return $ fromInteger i--instance (Fractional a) => Fractional (Simulation a) where- x / y = liftM2S (/) x y- recip = liftMS recip- fromRational t = return $ fromRational t--instance (Floating a) => Floating (Simulation a) where- pi = return pi- exp = liftMS exp- log = liftMS log- sqrt = liftMS sqrt- x ** y = liftM2S (**) x y- sin = liftMS sin- cos = liftMS cos- tan = liftMS tan- asin = liftMS asin- acos = liftMS acos- atan = liftMS atan- sinh = liftMS sinh- cosh = liftMS cosh- tanh = liftMS tanh- asinh = liftMS asinh- acosh = liftMS acosh- atanh = liftMS atanh- instance MonadIO Simulation where liftIO m = Simulation $ const m --- | A type class to lift the simulation computations to other monads.-class Monad m => SimulationLift m where+-- | A type class to lift the simulation computations to other computations.+class SimulationLift m where - -- | Lift the specified 'Simulation' computation to another monad.+ -- | Lift the specified 'Simulation' computation to another computation. liftSimulation :: Simulation a -> m a instance SimulationLift Simulation where liftSimulation = id++instance ParameterLift Simulation where+ liftParameter = liftPS++liftPS :: Parameter a -> Simulation a+{-# INLINE liftPS #-}+liftPS (Parameter x) =+ Simulation x -- | Exception handling within 'Simulation' computations. catchSimulation :: Simulation a -> (IOException -> Simulation a) -> Simulation a@@ -191,3 +142,17 @@ mfix f = Simulation $ \r -> do { rec { a <- invokeSimulation r (f a) }; return a } ++-- | Memoize the 'Simulation' computation, always returning the same value+-- within a simulation run.+memoSimulation :: Simulation a -> Simulation (Simulation a)+memoSimulation m =+ do ref <- liftIO $ newIORef Nothing+ return $ Simulation $ \r ->+ do x <- readIORef ref+ case x of+ Just v -> return v+ Nothing ->+ do v <- invokeSimulation r m+ writeIORef ref (Just v)+ return v
Simulation/Aivika/Internal/Specs.hs view
@@ -30,14 +30,17 @@ import Data.IORef +import Simulation.Aivika.Generator import qualified Simulation.Aivika.PriorityQueue as PQ -- | It defines the simulation specs. data Specs = Specs { spcStartTime :: Double, -- ^ the start time spcStopTime :: Double, -- ^ the stop time spcDT :: Double, -- ^ the integration time step- spcMethod :: Method -- ^ the integration method- } deriving (Eq, Ord, Show)+ spcMethod :: Method, -- ^ the integration method+ spcGeneratorType :: GeneratorType+ -- ^ the type of the random number generator+ } -- | It defines the integration method. data Method = Euler -- ^ Euler's method@@ -49,7 +52,8 @@ data Run = Run { runSpecs :: Specs, -- ^ the simulation specs runIndex :: Int, -- ^ the current simulation run index runCount :: Int, -- ^ the total number of runs in this experiment- runEventQueue :: EventQueue -- ^ the event queue+ runEventQueue :: EventQueue, -- ^ the event queue+ runGenerator :: Generator -- ^ the random number generator } -- | It defines the simulation point appended with the additional information.
Simulation/Aivika/Parameter.hs view
@@ -1,4 +1,3 @@- -- | -- Module : Simulation.Aivika.Parameter -- Copyright : Copyright (c) 2009-2013, David Sorokin <david.sorokin@gmail.com>@@ -7,53 +6,30 @@ -- Stability : experimental -- Tested with: GHC 7.6.3 ----- This module defines the parameters of simulation experiments.----+-- The module defines the 'Parameter' monad that allows representing the model+-- parameters. For example, they can be used when running the Monte-Carlo simulation.+-- module Simulation.Aivika.Parameter- (newParameter,- newTableParameter,- newIndexedParameter) where--import Data.Array-import Data.IORef-import qualified Data.Map as M-import Control.Concurrent.MVar--import Simulation.Aivika.Internal.Specs-import Simulation.Aivika.Internal.Simulation---- | Create a thread-safe parameter that returns always the same value within the simulation run, --- where the value is recalculated for each new run.-newParameter :: IO a -> IO (Simulation a)-newParameter a = newIndexedParameter $ \_ -> a---- | Create a thread-safe parameter that returns always the same value within the simulation run,--- where the value is taken consequently from the specified table based on the number of the --- current run starting from zero. After all values from the table are used, it takes the first --- value of the table, then the second one and so on.-newTableParameter :: Array Int a -> IO (Simulation a)-newTableParameter t = newIndexedParameter (\i -> return $ t ! (((i - i1) `mod` n) + i1))- where (i1, i2) = bounds t- n = i2 - i1 + 1+ (-- * Parameter+ Parameter,+ ParameterLift(..),+ runParameter,+ runParameters,+ -- * Error Handling+ catchParameter,+ finallyParameter,+ throwParameter,+ -- * Predefined Parameters+ simulationIndex,+ simulationCount,+ simulationSpecs,+ generatorParameter,+ starttime,+ stoptime,+ dt,+ -- * Memoization+ memoParameter,+ -- * Utilities+ tableParameter) where --- | Create a thread-safe parameter that returns always the same value within the simulation run, --- where the value depends on the number of this run starting from zero.-newIndexedParameter :: (Int -> IO a) -> IO (Simulation a)-newIndexedParameter f = - do lock <- newMVar ()- dict <- newIORef M.empty- return $ Simulation $ \r ->- do let i = runIndex r- m <- readIORef dict- if M.member i m- then do let Just v = M.lookup i m- return v- else withMVar lock $ - \() -> do { m <- readIORef dict;- if M.member i m- then do let Just v = M.lookup i m- return v- else do v <- f i- writeIORef dict $ M.insert i v m- return v }+import Simulation.Aivika.Internal.Parameter
Simulation/Aivika/Parameter/Random.hs view
@@ -11,31 +11,130 @@ -- module Simulation.Aivika.Parameter.Random- (newRandomParameter,- newNormalParameter) where+ (randomUniform,+ randomNormal,+ randomExponential,+ randomErlang,+ randomPoisson,+ randomBinomial) where import System.Random -import Simulation.Aivika.Simulation-import Simulation.Aivika.Random-import Simulation.Aivika.Parameter+import Control.Monad.Trans --- | Create a new random parameter distributed uniformly.--- The value doesn't change within the simulation run but--- then the value is recalculated for each new run.-newRandomParameter :: Simulation Double -- ^ minimum- -> Simulation Double -- ^ maximum- -> IO (Simulation Double)-newRandomParameter min max =- do x <- newParameter $ getStdRandom random- return $ min + x * (max - min)+import Simulation.Aivika.Generator+import Simulation.Aivika.Internal.Specs+import Simulation.Aivika.Internal.Parameter --- | Create a new random parameter distributed normally.--- The value doesn't change within the simulation run but--- then the value is recalculated for each new run.-newNormalParameter :: Simulation Double -- ^ mean- -> Simulation Double -- ^ variance- -> IO (Simulation Double)-newNormalParameter mu nu =- do x <- newNormalGen >>= newParameter- return $ mu + x * nu+-- | Computation that generates a new random number distributed uniformly.+--+-- To create a parameter that would return the same value within the simulation run,+-- you should memoize the computation, which is important for the Monte-Carlo simulation.+--+-- To create a random function that would return the same values in the integration+-- time points within the simulation run, you should either lift the computation to+-- the @Dynamics@ computation and then memoize it too but using the corresponded+-- function for that computation, or just take the predefined function that does+-- namely this.+randomUniform :: Double -- ^ minimum+ -> Double -- ^ maximum+ -> Parameter Double+randomUniform min max =+ Parameter $ \r ->+ let g = runGenerator r+ in generatorUniform g min max++-- | Computation that generates a new random number distributed normally.+--+-- To create a parameter that would return the same value within the simulation run,+-- you should memoize the computation, which is important for the Monte-Carlo simulation.+--+-- To create a random function that would return the same values in the integration+-- time points within the simulation run, you should either lift the computation to+-- the @Dynamics@ computation and then memoize it too but using the corresponded+-- function for that computation, or just take the predefined function that does+-- namely this.+randomNormal :: Double -- ^ mean+ -> Double -- ^ deviation+ -> Parameter Double+randomNormal mu nu =+ Parameter $ \r ->+ let g = runGenerator r+ in generatorNormal g mu nu++-- | Computation that returns a new exponential random number with the specified mean+-- (the reciprocal of the rate).+--+-- To create a parameter that would return the same value within the simulation run,+-- you should memoize the computation, which is important for the Monte-Carlo simulation.+--+-- To create a random function that would return the same values in the integration+-- time points within the simulation run, you should either lift the computation to+-- the @Dynamics@ computation and then memoize it too but using the corresponded+-- function for that computation, or just take the predefined function that does+-- namely this.+randomExponential :: Double+ -- ^ the mean (the reciprocal of the rate)+ -> Parameter Double+randomExponential mu =+ Parameter $ \r ->+ let g = runGenerator r+ in generatorExponential g mu++-- | Computation that returns a new Erlang random number with the specified scale+-- (the reciprocal of the rate) and integer shape.+--+-- To create a parameter that would return the same value within the simulation run,+-- you should memoize the computation, which is important for the Monte-Carlo simulation.+--+-- To create a random function that would return the same values in the integration+-- time points within the simulation run, you should either lift the computation to+-- the @Dynamics@ computation and then memoize it too but using the corresponded+-- function for that computation, or just take the predefined function that does+-- namely this.+randomErlang :: Double+ -- ^ the scale (the reciprocal of the rate)+ -> Int+ -- ^ the shape+ -> Parameter Double+randomErlang beta m =+ Parameter $ \r ->+ let g = runGenerator r+ in generatorErlang g beta m++-- | Computation that returns a new Poisson random number with the specified mean.+--+-- To create a parameter that would return the same value within the simulation run,+-- you should memoize the computation, which is important for the Monte-Carlo simulation.+--+-- To create a random function that would return the same values in the integration+-- time points within the simulation run, you should either lift the computation to+-- the @Dynamics@ computation and then memoize it too but using the corresponded+-- function for that computation, or just take the predefined function that does+-- namely this.+randomPoisson :: Double+ -- ^ the mean+ -> Parameter Int+randomPoisson mu =+ Parameter $ \r ->+ let g = runGenerator r+ in generatorPoisson g mu++-- | Computation that returns a new binomial random number with the specified+-- probability and trials.+--+-- To create a parameter that would return the same value within the simulation run,+-- you should memoize the computation, which is important for the Monte-Carlo simulation.+--+-- To create a random function that would return the same values in the integration+-- time points within the simulation run, you should either lift the computation to+-- the @Dynamics@ computation and then memoize it too but using the corresponded+-- function for that computation, or just take the predefined function that does+-- namely this.+randomBinomial :: Double -- ^ the probability+ -> Int -- ^ the number of trials+ -> Parameter Int+randomBinomial prob trials =+ Parameter $ \r ->+ let g = runGenerator r+ in generatorBinomial g prob trials
Simulation/Aivika/PriorityQueue.hs view
Simulation/Aivika/Process.hs view
@@ -17,31 +17,60 @@ -- -- A value of the 'ProcessId' type is just an identifier of such a process. --+-- The characteristic property of the @Process@ type is function 'holdProcess'+-- that suspends the current process for the specified time interval.+-- module Simulation.Aivika.Process- (ProcessId,+ (-- * Process Monad+ ProcessId, Process,+ ProcessLift(..),+ -- * Running Process runProcess,+ runProcessUsingId, runProcessInStartTime,+ runProcessInStartTimeUsingId, runProcessInStopTime,+ runProcessInStopTimeUsingId,+ -- * Spawning Processes+ spawnProcess,+ spawnProcessUsingId,+ -- * Enqueuing Process enqueueProcess,- enqueueProcessWithStartTime,- enqueueProcessWithStopTime,+ enqueueProcessUsingId,+ -- * Creating Process Identifier newProcessId,- newProcessIdWithCatch, processId,+ processUsingId,+ -- * Holding, Interrupting, Passivating and Canceling Process holdProcess, interruptProcess, processInterrupted, passivateProcess, processPassive, reactivateProcess,+ cancelProcessUsingId, cancelProcess,- processCanceled,+ processCancelled,+ -- * Awaiting Signal+ processAwait,+ -- * Process Timeout+ timeoutProcess,+ timeoutProcessUsingId,+ -- * Parallelizing Processes+ processParallel,+ processParallelUsingIds,+ processParallel_,+ processParallelUsingIds_,+ -- * Exception Handling catchProcess, finallyProcess,- throwProcess) where+ throwProcess,+ -- * Utilities+ zipProcessParallel,+ zip3ProcessParallel,+ unzipProcess,+ -- * Memoizing Process+ memoProcess) where -import Simulation.Aivika.Internal.Simulation-import Simulation.Aivika.Internal.Dynamics-import Simulation.Aivika.Internal.Event import Simulation.Aivika.Internal.Process
+ Simulation/Aivika/Processor.hs view
@@ -0,0 +1,414 @@++-- |+-- Module : Simulation.Aivika.Processor+-- Copyright : Copyright (c) 2009-2013, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.6.3+--+-- The processor of simulation data.+--+module Simulation.Aivika.Processor+ (-- * Processor Type+ Processor(..),+ -- * Creating Simple Processor+ simpleProcessor,+ statefulProcessor,+ -- * Specifying Identifier+ processorUsingId,+ -- * Buffer Processor+ bufferProcessor,+ bufferProcessorLoop,+ -- * Processing Queues+ queueProcessor,+ queueProcessorLoopMerging,+ queueProcessorLoopSeq,+ queueProcessorLoopParallel,+ -- * Parallelizing Processors+ processorParallel,+ processorQueuedParallel,+ processorPrioritisingOutputParallel,+ processorPrioritisingInputParallel,+ processorPrioritisingInputOutputParallel) where++import qualified Control.Category as C+import Control.Arrow++import Simulation.Aivika.Simulation+import Simulation.Aivika.Dynamics+import Simulation.Aivika.Event+import Simulation.Aivika.Cont+import Simulation.Aivika.Process+import Simulation.Aivika.Stream+import Simulation.Aivika.QueueStrategy++-- | Represents a processor of simulation data.+newtype Processor a b =+ Processor { runProcessor :: Stream a -> Stream b+ -- ^ Run the processor.+ }++instance C.Category Processor where++ id = Processor id++ Processor x . Processor y = Processor (x . y)++-- The implementation is based on article+-- A New Notation for Arrows by Ross Paterson,+-- although my streams are different and they+-- already depend on the Process monad,+-- while the pure streams were considered in the+-- mentioned article.+instance Arrow Processor where++ arr = Processor . mapStream++ first (Processor f) =+ Processor $ \xys ->+ Cons $+ do (xs, ys) <- liftSimulation $ unzipStream xys+ runStream $ zipStreamSeq (f xs) ys++ second (Processor f) =+ Processor $ \xys ->+ Cons $+ do (xs, ys) <- liftSimulation $ unzipStream xys+ runStream $ zipStreamSeq xs (f ys)++ Processor f *** Processor g =+ Processor $ \xys ->+ Cons $+ do (xs, ys) <- liftSimulation $ unzipStream xys+ runStream $ zipStreamSeq (f xs) (g ys)++-- N.B.+-- Very probably, Processor is not ArrowLoop,+-- which would be natural as Process is not MonadFix,+-- for the discontinuous process is not irreversible+-- and the time flows in one direction only.+--+-- -- The implementation is based on article+-- -- A New Notation for Arrows by Ross Paterson,+-- -- although my streams are different and they+-- -- already depend on the Process monad,+-- -- while the pure streams were considered in the+-- -- mentioned article.+-- instance ArrowLoop Processor where+-- +-- loop (Processor f) =+-- Processor $ \xs ->+-- Cons $+-- do Cons zs <- liftSimulation $+-- simulationLoop (\(xs, ys) ->+-- unzipStream $ f $ zipStreamSeq xs ys) xs+-- zs+-- +-- simulationLoop :: ((b, d) -> Simulation (c, d)) -> b -> Simulation c+-- simulationLoop f b =+-- mdo (c, d) <- f (b, d)+-- return c++-- The implementation is based on article+-- A New Notation for Arrows by Ross Paterson,+-- although my streams are different and they+-- already depend on the Process monad,+-- while the pure streams were considered in the+-- mentioned article.+instance ArrowChoice Processor where++ left (Processor f) =+ Processor $ \xs ->+ Cons $+ do ys <- liftSimulation $ memoStream xs+ runStream $ replaceLeftStream ys (f $ leftStream ys)++ right (Processor f) =+ Processor $ \xs ->+ Cons $+ do ys <- liftSimulation $ memoStream xs+ runStream $ replaceRightStream ys (f $ rightStream ys)++instance ArrowZero Processor where++ zeroArrow = Processor $ const emptyStream++instance ArrowPlus Processor where++ (Processor f) <+> (Processor g) =+ Processor $ \xs ->+ Cons $+ do [xs1, xs2] <- liftSimulation $ splitStream 2 xs+ runStream $ mergeStreams (f xs1) (g xs2)++-- These instances are meaningless:+-- +-- instance SimulationLift (Processor a) where+-- liftSimulation = Processor . mapStreamM . const . liftSimulation+-- +-- instance DynamicsLift (Processor a) where+-- liftDynamics = Processor . mapStreamM . const . liftDynamics+-- +-- instance EventLift (Processor a) where+-- liftEvent = Processor . mapStreamM . const . liftEvent+-- +-- instance ProcessLift (Processor a) where+-- liftProcess = Processor . mapStreamM . const -- data first!++-- | Create a simple processor by the specified handling function+-- that runs the discontinuous process for each input value to get the output.+simpleProcessor :: (a -> Process b) -> Processor a b+simpleProcessor = Processor . mapStreamM++-- | Like 'simpleProcessor' but allows creating a processor that has a state+-- which is passed in to every new iteration.+statefulProcessor :: s -> ((s, a) -> Process (s, b)) -> Processor a b+statefulProcessor s f =+ Processor $ \xs -> Cons $ loop s xs where+ loop s xs =+ do (a, xs') <- runStream xs+ (s', b) <- f (s, a)+ return (b, Cons $ loop s' xs')++-- | Create a processor that will use the specified process identifier.+-- It can be useful to refer to the underlying 'Process' computation which+-- can be passivated, interrupted, canceled and so on. See also the+-- 'processUsingId' function for more details.+processorUsingId :: ProcessId -> Processor a b -> Processor a b+processorUsingId pid (Processor f) =+ Processor $ Cons . processUsingId pid . runStream . f++-- | Launches the specified processors in parallel consuming the same input+-- stream and producing a combined output stream.+--+-- If you don't know what the enqueue strategies to apply, then+-- you will probably need 'FCFS' for the both parameters, or+-- function 'processorParallel' that does namely this.+processorQueuedParallel :: (EnqueueStrategy si qi,+ EnqueueStrategy so qo)+ => si+ -- ^ the strategy applied for enqueuing the input data+ -> so+ -- ^ the strategy applied for enqueuing the output data+ -> [Processor a b]+ -- ^ the processors to parallelize+ -> Processor a b+ -- ^ the parallelized processor+processorQueuedParallel si so ps =+ Processor $ \xs ->+ Cons $+ do let n = length ps+ input <- liftSimulation $ splitStreamQueuing si n xs+ let results = flip map (zip input ps) $ \(input, p) ->+ runProcessor p input+ output = concatQueuedStreams so results+ runStream output++-- | Launches the specified processors in parallel using priorities for combining the output.+processorPrioritisingOutputParallel :: (EnqueueStrategy si qi,+ PriorityQueueStrategy so qo po)+ => si+ -- ^ the strategy applied for enqueuing the input data+ -> so+ -- ^ the strategy applied for enqueuing the output data+ -> [Processor a (po, b)]+ -- ^ the processors to parallelize+ -> Processor a b+ -- ^ the parallelized processor+processorPrioritisingOutputParallel si so ps =+ Processor $ \xs ->+ Cons $+ do let n = length ps+ input <- liftSimulation $ splitStreamQueuing si n xs+ let results = flip map (zip input ps) $ \(input, p) ->+ runProcessor p input+ output = concatPriorityStreams so results+ runStream output++-- | Launches the specified processors in parallel using priorities for consuming the intput.+processorPrioritisingInputParallel :: (PriorityQueueStrategy si qi pi,+ EnqueueStrategy so qo)+ => si+ -- ^ the strategy applied for enqueuing the input data+ -> so+ -- ^ the strategy applied for enqueuing the output data+ -> [(Stream pi, Processor a b)]+ -- ^ the streams of input priorities and the processors+ -- to parallelize+ -> Processor a b+ -- ^ the parallelized processor+processorPrioritisingInputParallel si so ps =+ Processor $ \xs ->+ Cons $+ do input <- liftSimulation $ splitStreamPrioritising si (map fst ps) xs+ let results = flip map (zip input ps) $ \(input, (_, p)) ->+ runProcessor p input+ output = concatQueuedStreams so results+ runStream output++-- | Launches the specified processors in parallel using priorities for consuming+-- the input and combining the output.+processorPrioritisingInputOutputParallel :: (PriorityQueueStrategy si qi pi,+ PriorityQueueStrategy so qo po)+ => si+ -- ^ the strategy applied for enqueuing the input data+ -> so+ -- ^ the strategy applied for enqueuing the output data+ -> [(Stream pi, Processor a (po, b))]+ -- ^ the streams of input priorities and the processors+ -- to parallelize+ -> Processor a b+ -- ^ the parallelized processor+processorPrioritisingInputOutputParallel si so ps =+ Processor $ \xs ->+ Cons $+ do input <- liftSimulation $ splitStreamPrioritising si (map fst ps) xs+ let results = flip map (zip input ps) $ \(input, (_, p)) ->+ runProcessor p input+ output = concatPriorityStreams so results+ runStream output++-- | Launches the processors in parallel consuming the same input stream and producing+-- a combined output stream. This version applies the 'FCFS' strategy both for input+-- and output, which suits the most part of uses cases.+processorParallel :: [Processor a b] -> Processor a b+processorParallel = processorQueuedParallel FCFS FCFS++-- | Create a buffer processor, where the process from the first argument+-- consumes the input stream but the stream passed in as the second argument+-- and produced usually by some other process is returned as an output.+-- This kind of processor is very useful for modeling the queues.+bufferProcessor :: (Stream a -> Process ())+ -- ^ a separate process to consume the input + -> Stream b+ -- ^ the resulting stream of data+ -> Processor a b+bufferProcessor consume output =+ Processor $ \xs ->+ Cons $+ do spawnProcess CancelTogether (consume xs)+ runStream output++-- | Like 'bufferProcessor' but allows creating a loop when some items+-- can be returned for processing them again. It is very useful for+-- modeling the processors with queues and loop-backs.+bufferProcessorLoop :: (Stream a -> Stream c -> Process ())+ -- ^ consume two streams: the input values of type @a@+ -- and the values of type @c@ returned by the loop+ -> Stream d+ -- ^ the stream of data that may become results+ -> Processor d (Either c b)+ -- ^ process and then decide what values of type @c@+ -- should be processed again+ -> Processor a b+bufferProcessorLoop consume preoutput filter =+ Processor $ \xs ->+ Cons $+ do (reverted, output) <-+ liftSimulation $+ partitionEitherStream $+ runProcessor filter preoutput+ spawnProcess CancelTogether (consume xs reverted)+ runStream output++-- | Return a processor with help of which we can model the queue.+--+-- Although the function doesn't refer to the queue directly, its main use case+-- is namely a processing of the queue. The first argument should be the enqueueing+-- operation, while the second argument should be the opposite dequeueing operation.+--+-- The reason is as follows. There are many possible combinations how the queues+-- can be modeled. There is no sense to enumerate all them creating a separate function+-- for each case. We can just use combinators to define exactly what we need.+--+-- So, the queue can lose the input items if the queue is full, or the input process+-- can suspend while the queue is full, or we can use priorities for enqueueing,+-- storing and dequeueing the items in different combinations. There are so many use+-- cases!+--+-- There is a hope that this function along with other similar functions from this+-- module is sufficient to cover the most important cases. Even if it is not sufficient+-- then you can use a more generic function 'bufferProcessor' which this function is+-- based on. In case of need, you can even write your own function from scratch. It is+-- quite easy actually.+queueProcessor :: (a -> Process ())+ -- ^ enqueue the input item and wait+ -- while the queue is full if required+ -- so that there was no hanging items+ -> Process b+ -- ^ dequeue an output item+ -> Processor a b+ -- ^ the buffering processor+queueProcessor enqueue dequeue =+ bufferProcessor+ (consumeStream enqueue)+ (repeatProcess dequeue)++-- | Like 'queueProcessor' creates a queue processor but allows creating+-- a loop when some items can be returned and added to the queue again.+-- Also it allows specifying how two input streams of data can be merged.+queueProcessorLoopMerging :: (Stream a -> Stream d -> Stream e)+ -- ^ merge two streams: the input values of type @a@+ -- and the values of type @d@ returned by the loop+ -> (e -> Process ())+ -- ^ enqueue the input item and wait+ -- while the queue is full if required+ -- so that there was no hanging items+ -> Process c+ -- ^ dequeue an item for the further processing+ -> Processor c (Either d b)+ -- ^ process and then decide what values of type @d@+ -- should be processed again+ -> Processor a b+ -- ^ the buffering processor+queueProcessorLoopMerging merge enqueue dequeue =+ bufferProcessorLoop+ (\bs cs ->+ consumeStream enqueue $+ merge bs cs)+ (repeatProcess dequeue)++-- | Like 'queueProcessorLoopMerging' creates a queue processor and allows+-- creating a loop when some items can be returned and added to the queue again.+-- Only it sequentially merges two input streams of data: one stream+-- that come from the external source and another stream of data returned+-- by the loop. The first stream has a priority over the second one.+queueProcessorLoopSeq :: (a -> Process ())+ -- ^ enqueue the input item and wait+ -- while the queue is full if required+ -- so that there was no hanging items+ -> Process c+ -- ^ dequeue an item for the further processing+ -> Processor c (Either a b)+ -- ^ process and then decide what values of type @a@+ -- should be processed again+ -> Processor a b+ -- ^ the buffering processor+queueProcessorLoopSeq =+ queueProcessorLoopMerging mergeStreams++-- | Like 'queueProcessorLoopMerging' creates a queue processor and allows+-- creating a loop when some items can be returned and added to the queue again.+-- Only it runs two simultaneous processes to enqueue the input streams of data:+-- one stream that come from the external source and another stream of data returned+-- by the loop.+queueProcessorLoopParallel :: (a -> Process ())+ -- ^ enqueue the input item and wait+ -- while the queue is full if required+ -- so that there was no hanging items+ -> Process c+ -- ^ dequeue an item for the further processing+ -> Processor c (Either a b)+ -- ^ process and then decide what values of type @a@+ -- should be processed again+ -> Processor a b+ -- ^ the buffering processor+queueProcessorLoopParallel enqueue dequeue =+ bufferProcessorLoop+ (\bs cs ->+ do spawnProcess CancelTogether $+ consumeStream enqueue bs+ spawnProcess CancelTogether $+ consumeStream enqueue cs)+ (repeatProcess dequeue)
+ Simulation/Aivika/Processor/RoundRobbin.hs view
@@ -0,0 +1,58 @@++-- |+-- Module : Simulation.Aivika.Processor.RoundRobbin+-- Copyright : Copyright (c) 2009-2013, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.6.3+--+-- The module defines the Round-Robbin processor.+--+module Simulation.Aivika.Processor.RoundRobbin+ (roundRobbinProcessor,+ roundRobbinProcessorUsingIds) where++import Control.Monad++import Simulation.Aivika.Simulation+import Simulation.Aivika.Event+import Simulation.Aivika.Process+import Simulation.Aivika.Processor+import Simulation.Aivika.Stream+import Simulation.Aivika.Queue.Infinite++-- | Represents the Round-Robbin processor that tries to perform the task within+-- the specified timeout. If the task times out, then it is canceled and returned+-- to the processor again; otherwise, the successful result is redirected to output.+roundRobbinProcessor :: Processor (Process Double, Process a) a+roundRobbinProcessor =+ Processor $+ runProcessor roundRobbinProcessorUsingIds . mapStreamM f where+ f (timeout, p) =+ let x = do timeout' <- timeout+ pid <- liftSimulation newProcessId+ return (timeout', pid)+ in return (x, p)++-- | Like 'roundRobbinProcessor' but allows specifying the process identifiers which+-- must be unique for every new attemp to perform the task even if the task is the same.+roundRobbinProcessorUsingIds :: Processor (Process (Double, ProcessId), Process a) a+roundRobbinProcessorUsingIds =+ Processor $ \xs ->+ Cons $+ do q <- liftSimulation newFCFSQueue+ let process =+ do t@(x, p) <- dequeue q+ (timeout, pid) <- x+ result <- timeoutProcessUsingId timeout pid p+ case result of+ Just a -> return a+ Nothing ->+ do liftEvent $ enqueue q t + process+ processor =+ bufferProcessor+ (consumeStream $ liftEvent . enqueue q)+ (repeatProcess process)+ runStream $ runProcessor processor xs
Simulation/Aivika/Queue.hs view
@@ -9,44 +9,140 @@ -- -- This module defines a queue that can use the specified strategies. So, having only -- the 'FCFS', 'LCFS', 'SIRO' and 'StaticPriorities' strategies, you can build--- 4 x 3 x 4 = 48 different types of the queue, each of them will have its own--- behavior (below @StaticPriorities@ can be used for input and output only).+-- 4 x 4 x 4 = 64 different types of the queue, each of them will have its own+-- behaviour. -- module Simulation.Aivika.Queue- (Queue,+ (-- * Queue Types+ FCFSQueue,+ LCFSQueue,+ SIROQueue,+ PriorityQueue,+ Queue,+ -- * Creating Queue+ newFCFSQueue,+ newLCFSQueue,+ newSIROQueue,+ newPriorityQueue,+ newQueue,+ -- * Queue Properties and Activities+ queueInputStrategy,+ queueStoringStrategy,+ queueOutputStrategy, queueNull, queueFull, queueMaxCount, queueCount, queueLostCount,- enqueued,- dequeued,- enqueuedButLost,- newQueue,+ queueInputCount,+ queueStoreCount,+ queueOutputRequestCount,+ queueOutputCount,+ queueLoadFactor,+ queueInputRate,+ queueStoreRate,+ queueOutputRequestRate,+ queueOutputRate,+ queueWaitTime,+ queueTotalWaitTime,+ queueInputWaitTime,+ queueOutputWaitTime,+ -- * Dequeuing and Enqueuing dequeue,- dequeueWithPriority,- dequeueWithDynamicPriority,+ dequeueWithOutputPriority, tryDequeue, enqueue,- enqueueWithPriority,- enqueueWithDynamicPriority,+ enqueueWithInputPriority,+ enqueueWithStoringPriority,+ enqueueWithInputStoringPriorities, tryEnqueue,+ tryEnqueueWithStoringPriority, enqueueOrLost,- enqueueOrLost_) where+ enqueueOrLost_,+ enqueueWithStoringPriorityOrLost,+ enqueueWithStoringPriorityOrLost_,+ -- * Awaiting+ waitWhileFullQueue,+ -- * Summary+ queueSummary,+ -- * Derived Signals for Properties+ queueNullChanged,+ queueNullChanged_,+ queueFullChanged,+ queueFullChanged_,+ queueCountChanged,+ queueCountChanged_,+ queueLostCountChanged,+ queueLostCountChanged_,+ queueInputCountChanged,+ queueInputCountChanged_,+ queueStoreCountChanged,+ queueStoreCountChanged_,+ queueOutputRequestCountChanged,+ queueOutputRequestCountChanged_,+ queueOutputCountChanged,+ queueOutputCountChanged_,+ queueLoadFactorChanged,+ queueLoadFactorChanged_,+ queueWaitTimeChanged,+ queueWaitTimeChanged_,+ queueTotalWaitTimeChanged,+ queueTotalWaitTimeChanged_,+ queueInputWaitTimeChanged,+ queueInputWaitTimeChanged_,+ queueOutputWaitTimeChanged,+ queueOutputWaitTimeChanged_,+ -- * Basic Signals+ enqueueInitiated,+ enqueueStored,+ enqueueLost,+ dequeueRequested,+ dequeueExtracted,+ -- * Overall Signal+ queueChanged_) where import Data.IORef+import Data.Monoid import Control.Monad import Control.Monad.Trans +import Simulation.Aivika.Internal.Specs import Simulation.Aivika.Internal.Simulation+import Simulation.Aivika.Internal.Dynamics import Simulation.Aivika.Internal.Event import Simulation.Aivika.Internal.Process import Simulation.Aivika.Internal.Signal import Simulation.Aivika.Signal import Simulation.Aivika.Resource import Simulation.Aivika.QueueStrategy+import Simulation.Aivika.Statistics+import Simulation.Aivika.Stream+import Simulation.Aivika.Processor +import qualified Simulation.Aivika.DoubleLinkedList as DLL +import qualified Simulation.Aivika.Vector as V+import qualified Simulation.Aivika.PriorityQueue as PQ++-- | A type synonym for the ordinary FIFO queue also known as the FCFS+-- (First Come - First Serviced) queue.+type FCFSQueue a =+ Queue FCFS DLL.DoubleLinkedList FCFS DLL.DoubleLinkedList FCFS DLL.DoubleLinkedList a++-- | A type synonym for the ordinary LIFO queue also known as the LCFS+-- (Last Come - First Serviced) queue.+type LCFSQueue a =+ Queue FCFS DLL.DoubleLinkedList LCFS DLL.DoubleLinkedList FCFS DLL.DoubleLinkedList a++-- | A type synonym for the SIRO (Serviced in Random Order) queue.+type SIROQueue a =+ Queue FCFS DLL.DoubleLinkedList SIRO V.Vector FCFS DLL.DoubleLinkedList a++-- | A type synonym for the queue with static priorities applied when+-- storing the elements in the queue.+type PriorityQueue a =+ Queue FCFS DLL.DoubleLinkedList StaticPriorities PQ.PriorityQueue FCFS DLL.DoubleLinkedList a+ -- | Represents the queue using the specified strategies for input @si@, -- internal storing (in memory) @sm@ and output @so@, where @a@ denotes -- the type of items stored in the queue. Types @qi@, @qm@ and @qo@ are@@ -54,20 +150,61 @@ -- are dependent types. data Queue si qi sm qm so qo a = Queue { queueMaxCount :: Int,- -- ^ The maximum available number of items.+ -- ^ The queue capacity. queueInputStrategy :: si,- queueMemoryStrategy :: sm,+ -- ^ The strategy applied to the input (enqueuing) process.+ queueStoringStrategy :: sm,+ -- ^ The strategy applied when storing (in memory) items in the queue. queueOutputStrategy :: so,+ -- ^ The strategy applied to the output (dequeuing) process. queueInputRes :: Resource si qi,- queueMemory :: qm a,+ queueStore :: qm (QueueItem a), queueOutputRes :: Resource so qo, queueCountRef :: IORef Int, queueLostCountRef :: IORef Int,- enqueuedSource :: SignalSource a,- enqueuedButLostSource :: SignalSource a,- dequeuedSource :: SignalSource a }+ queueInputCountRef :: IORef Int,+ queueStoreCountRef :: IORef Int,+ queueOutputRequestCountRef :: IORef Int,+ queueOutputCountRef :: IORef Int,+ queueWaitTimeRef :: IORef (SamplingStats Double),+ queueTotalWaitTimeRef :: IORef (SamplingStats Double),+ queueInputWaitTimeRef :: IORef (SamplingStats Double),+ queueOutputWaitTimeRef :: IORef (SamplingStats Double),+ enqueueInitiatedSource :: SignalSource a,+ enqueueLostSource :: SignalSource a,+ enqueueStoredSource :: SignalSource a,+ dequeueRequestedSource :: SignalSource (),+ dequeueExtractedSource :: SignalSource a }++-- | Stores the item and a time of its enqueuing. +data QueueItem a =+ QueueItem { itemValue :: a,+ -- ^ Return the item value.+ itemInputTime :: Double,+ -- ^ Return the time of enqueuing the item.+ itemStoringTime :: Double+ -- ^ Return the time of storing in the queue, or+ -- @itemInputTime@ before the actual storing when+ -- the item was just enqueued.+ } --- | Create a new queue with the specified strategies and maximum available number of items. +-- | Create a new FCFS queue with the specified capacity. +newFCFSQueue :: Int -> Simulation (FCFSQueue a) +newFCFSQueue = newQueue FCFS FCFS FCFS+ +-- | Create a new LCFS queue with the specified capacity. +newLCFSQueue :: Int -> Simulation (LCFSQueue a) +newLCFSQueue = newQueue FCFS LCFS FCFS+ +-- | Create a new SIRO queue with the specified capacity. +newSIROQueue :: Int -> Simulation (SIROQueue a) +newSIROQueue = newQueue FCFS SIRO FCFS+ +-- | Create a new priority queue with the specified capacity. +newPriorityQueue :: Int -> Simulation (PriorityQueue a) +newPriorityQueue = newQueue FCFS StaticPriorities FCFS+ +-- | Create a new queue with the specified strategies and capacity. newQueue :: (QueueStrategy si qi, QueueStrategy sm qm, QueueStrategy so qo) =>@@ -78,54 +215,327 @@ -> so -- ^ the strategy applied to the output (dequeuing) process -> Int- -- ^ the maximum available number of items+ -- ^ the queue capacity -> Simulation (Queue si qi sm qm so qo a) newQueue si sm so count = do i <- liftIO $ newIORef 0 l <- liftIO $ newIORef 0- ri <- newResourceWithCount si count count+ ci <- liftIO $ newIORef 0+ cm <- liftIO $ newIORef 0+ cr <- liftIO $ newIORef 0+ co <- liftIO $ newIORef 0+ ri <- newResourceWithMaxCount si count (Just count) qm <- newStrategyQueue sm- ro <- newResourceWithCount so count 0+ ro <- newResourceWithMaxCount so 0 (Just count)+ w <- liftIO $ newIORef mempty+ wt <- liftIO $ newIORef mempty+ wi <- liftIO $ newIORef mempty+ wo <- liftIO $ newIORef mempty s1 <- newSignalSource s2 <- newSignalSource s3 <- newSignalSource+ s4 <- newSignalSource+ s5 <- newSignalSource return Queue { queueMaxCount = count, queueInputStrategy = si,- queueMemoryStrategy = sm,+ queueStoringStrategy = sm, queueOutputStrategy = so, queueInputRes = ri,- queueMemory = qm,+ queueStore = qm, queueOutputRes = ro, queueCountRef = i, queueLostCountRef = l,- enqueuedSource = s1,- enqueuedButLostSource = s2,- dequeuedSource = s3 }+ queueInputCountRef = ci,+ queueStoreCountRef = cm,+ queueOutputRequestCountRef = cr,+ queueOutputCountRef = co,+ queueWaitTimeRef = w,+ queueTotalWaitTimeRef = wt,+ queueInputWaitTimeRef = wi,+ queueOutputWaitTimeRef = wo,+ enqueueInitiatedSource = s1,+ enqueueLostSource = s2,+ enqueueStoredSource = s3,+ dequeueRequestedSource = s4,+ dequeueExtractedSource = s5 } -- | Test whether the queue is empty.+--+-- See also 'queueNullChanged' and 'queueNullChanged_'. queueNull :: Queue si qi sm qm so qo a -> Event Bool queueNull q = Event $ \p -> do n <- readIORef (queueCountRef q) return (n == 0)+ +-- | Signal when the 'queueNull' property value has changed.+queueNullChanged :: Queue si qi sm qm so qo a -> Signal Bool+queueNullChanged q =+ mapSignalM (const $ queueNull q) (queueNullChanged_ q)+ +-- | Signal when the 'queueNull' property value has changed.+queueNullChanged_ :: Queue si qi sm qm so qo a -> Signal ()+queueNullChanged_ = queueCountChanged_ -- | Test whether the queue is full.+--+-- See also 'queueFullChanged' and 'queueFullChanged_'. queueFull :: Queue si qi sm qm so qo a -> Event Bool queueFull q = Event $ \p -> do n <- readIORef (queueCountRef q) return (n == queueMaxCount q)+ +-- | Signal when the 'queueFull' property value has changed.+queueFullChanged :: Queue si qi sm qm so qo a -> Signal Bool+queueFullChanged q =+ mapSignalM (const $ queueFull q) (queueFullChanged_ q)+ +-- | Signal when the 'queueFull' property value has changed.+queueFullChanged_ :: Queue si qi sm qm so qo a -> Signal ()+queueFullChanged_ = queueCountChanged_ -- | Return the queue size.+--+-- See also 'queueCountChanged' and 'queueCountChanged_'. queueCount :: Queue si qi sm qm so qo a -> Event Int queueCount q = Event $ \p -> readIORef (queueCountRef q) +-- | Signal when the 'queueCount' property value has changed.+queueCountChanged :: Queue si qi sm qm so qo a -> Signal Int+queueCountChanged q =+ mapSignalM (const $ queueCount q) (queueCountChanged_ q)+ +-- | Signal when the 'queueCount' property value has changed.+queueCountChanged_ :: Queue si qi sm qm so qo a -> Signal ()+queueCountChanged_ q =+ mapSignal (const ()) (enqueueStored q) <>+ mapSignal (const ()) (dequeueExtracted q)+ -- | Return the number of lost items.+--+-- See also 'queueLostCountChanged' and 'queueLostCountChanged_'. queueLostCount :: Queue si qi sm qm so qo a -> Event Int queueLostCount q = Event $ \p -> readIORef (queueLostCountRef q) +-- | Signal when the 'queueLostCount' property value has changed.+queueLostCountChanged :: Queue si qi sm qm so qo a -> Signal Int+queueLostCountChanged q =+ mapSignalM (const $ queueLostCount q) (queueLostCountChanged_ q)+ +-- | Signal when the 'queueLostCount' property value has changed.+queueLostCountChanged_ :: Queue si qi sm qm so qo a -> Signal ()+queueLostCountChanged_ q =+ mapSignal (const ()) (enqueueLost q)++-- | Return the total number of input items that were enqueued.+--+-- See also 'queueInputCountChanged' and 'queueInputCountChanged_'.+queueInputCount :: Queue si qi sm qm so qo a -> Event Int+queueInputCount q =+ Event $ \p -> readIORef (queueInputCountRef q)+ +-- | Signal when the 'queueInputCount' property value has changed.+queueInputCountChanged :: Queue si qi sm qm so qo a -> Signal Int+queueInputCountChanged q =+ mapSignalM (const $ queueInputCount q) (queueInputCountChanged_ q)+ +-- | Signal when the 'queueInputCount' property value has changed.+queueInputCountChanged_ :: Queue si qi sm qm so qo a -> Signal ()+queueInputCountChanged_ q =+ mapSignal (const ()) (enqueueInitiated q)+ +-- | Return the total number of input items that were stored.+--+-- See also 'queueStoreCountChanged' and 'queueStoreCountChanged_'.+queueStoreCount :: Queue si qi sm qm so qo a -> Event Int+queueStoreCount q =+ Event $ \p -> readIORef (queueStoreCountRef q)+ +-- | Signal when the 'queueStoreCount' property value has changed.+queueStoreCountChanged :: Queue si qi sm qm so qo a -> Signal Int+queueStoreCountChanged q =+ mapSignalM (const $ queueStoreCount q) (queueStoreCountChanged_ q)+ +-- | Signal when the 'queueStoreCount' property value has changed.+queueStoreCountChanged_ :: Queue si qi sm qm so qo a -> Signal ()+queueStoreCountChanged_ q =+ mapSignal (const ()) (enqueueStored q)+ +-- | Return the total number of requests for dequeueing the items,+-- not taking into account the attempts to dequeue immediately+-- without suspension.+--+-- See also 'queueOutputRequestCountChanged' and 'queueOutputRequestCountChanged_'.+queueOutputRequestCount :: Queue si qi sm qm so qo a -> Event Int+queueOutputRequestCount q =+ Event $ \p -> readIORef (queueOutputRequestCountRef q)+ +-- | Signal when the 'queueOutputRequestCount' property value has changed.+queueOutputRequestCountChanged :: Queue si qi sm qm so qo a -> Signal Int+queueOutputRequestCountChanged q =+ mapSignalM (const $ queueOutputRequestCount q) (queueOutputRequestCountChanged_ q)+ +-- | Signal when the 'queueOutputRequestCount' property value has changed.+queueOutputRequestCountChanged_ :: Queue si qi sm qm so qo a -> Signal ()+queueOutputRequestCountChanged_ q =+ mapSignal (const ()) (dequeueRequested q)+ +-- | Return the total number of output items that were dequeued.+--+-- See also 'queueOutputCountChanged' and 'queueOutputCountChanged_'.+queueOutputCount :: Queue si qi sm qm so qo a -> Event Int+queueOutputCount q =+ Event $ \p -> readIORef (queueOutputCountRef q)+ +-- | Signal when the 'queueOutputCount' property value has changed.+queueOutputCountChanged :: Queue si qi sm qm so qo a -> Signal Int+queueOutputCountChanged q =+ mapSignalM (const $ queueOutputCount q) (queueOutputCountChanged_ q)+ +-- | Signal when the 'queueOutputCount' property value has changed.+queueOutputCountChanged_ :: Queue si qi sm qm so qo a -> Signal ()+queueOutputCountChanged_ q =+ mapSignal (const ()) (dequeueExtracted q)++-- | Return the load factor: the queue size divided by its maximum size.+--+-- See also 'queueLoadFactorChanged' and 'queueLoadFactorChanged_'.+queueLoadFactor :: Queue si qi sm qm so qo a -> Event Double+queueLoadFactor q =+ Event $ \p ->+ do x <- readIORef (queueCountRef q)+ let y = queueMaxCount q+ return (fromIntegral x / fromIntegral y)+ +-- | Signal when the 'queueLoadFactor' property value has changed.+queueLoadFactorChanged :: Queue si qi sm qm so qo a -> Signal Double+queueLoadFactorChanged q =+ mapSignalM (const $ queueLoadFactor q) (queueLoadFactorChanged_ q)+ +-- | Signal when the 'queueLoadFactor' property value has changed.+queueLoadFactorChanged_ :: Queue si qi sm qm so qo a -> Signal ()+queueLoadFactorChanged_ q =+ mapSignal (const ()) (enqueueStored q) <>+ mapSignal (const ()) (dequeueExtracted q)+ +-- | Return the rate of the input items that were enqueued: how many items+-- per time.+queueInputRate :: Queue si qi sm qm so qo a -> Event Double+queueInputRate q =+ Event $ \p ->+ do x <- readIORef (queueInputCountRef q)+ let t0 = spcStartTime $ pointSpecs p+ t = pointTime p+ return (fromIntegral x / (t - t0))+ +-- | Return the rate of the items that were stored: how many items+-- per time.+queueStoreRate :: Queue si qi sm qm so qo a -> Event Double+queueStoreRate q =+ Event $ \p ->+ do x <- readIORef (queueStoreCountRef q)+ let t0 = spcStartTime $ pointSpecs p+ t = pointTime p+ return (fromIntegral x / (t - t0))+ +-- | Return the rate of the requests for dequeueing the items: how many requests+-- per time. It does not include the attempts to dequeue immediately+-- without suspension.+queueOutputRequestRate :: Queue si qi sm qm so qo a -> Event Double+queueOutputRequestRate q =+ Event $ \p ->+ do x <- readIORef (queueOutputRequestCountRef q)+ let t0 = spcStartTime $ pointSpecs p+ t = pointTime p+ return (fromIntegral x / (t - t0))+ +-- | Return the rate of the output items that were dequeued: how many items+-- per time.+queueOutputRate :: Queue si qi sm qm so qo a -> Event Double+queueOutputRate q =+ Event $ \p ->+ do x <- readIORef (queueOutputCountRef q)+ let t0 = spcStartTime $ pointSpecs p+ t = pointTime p+ return (fromIntegral x / (t - t0))+ +-- | Return the wait time from the time at which the item was stored in the queue to+-- the time at which it was dequeued.+--+-- See also 'queueWaitTimeChanged' and 'queueWaitTimeChanged_'.+queueWaitTime :: Queue si qi sm qm so qo a -> Event (SamplingStats Double)+queueWaitTime q =+ Event $ \p -> readIORef (queueWaitTimeRef q)+ +-- | Signal when the 'queueWaitTime' property value has changed.+queueWaitTimeChanged :: Queue si qi sm qm so qo a -> Signal (SamplingStats Double)+queueWaitTimeChanged q =+ mapSignalM (const $ queueWaitTime q) (queueWaitTimeChanged_ q)+ +-- | Signal when the 'queueWaitTime' property value has changed.+queueWaitTimeChanged_ :: Queue si qi sm qm so qo a -> Signal ()+queueWaitTimeChanged_ q =+ mapSignal (const ()) (dequeueExtracted q)+ +-- | Return the total wait time from the time at which the enqueueing operation+-- was initiated to the time at which the item was dequeued.+--+-- In some sense, @queueTotalWaitTime == queueInputWaitTime + queueWaitTime@.+--+-- See also 'queueTotalWaitTimeChanged' and 'queueTotalWaitTimeChanged_'.+queueTotalWaitTime :: Queue si qi sm qm so qo a -> Event (SamplingStats Double)+queueTotalWaitTime q =+ Event $ \p -> readIORef (queueTotalWaitTimeRef q)+ +-- | Signal when the 'queueTotalWaitTime' property value has changed.+queueTotalWaitTimeChanged :: Queue si qi sm qm so qo a -> Signal (SamplingStats Double)+queueTotalWaitTimeChanged q =+ mapSignalM (const $ queueTotalWaitTime q) (queueTotalWaitTimeChanged_ q)+ +-- | Signal when the 'queueTotalWaitTime' property value has changed.+queueTotalWaitTimeChanged_ :: Queue si qi sm qm so qo a -> Signal ()+queueTotalWaitTimeChanged_ q =+ mapSignal (const ()) (dequeueExtracted q)+ +-- | Return the input wait time from the time at which the enqueueing operation+-- was initiated to the time at which the item was stored in the queue.+--+-- See also 'queueInputWaitTimeChanged' and 'queueInputWaitTimeChanged_'.+queueInputWaitTime :: Queue si qi sm qm so qo a -> Event (SamplingStats Double)+queueInputWaitTime q =+ Event $ \p -> readIORef (queueInputWaitTimeRef q)+ +-- | Signal when the 'queueInputWaitTime' property value has changed.+queueInputWaitTimeChanged :: Queue si qi sm qm so qo a -> Signal (SamplingStats Double)+queueInputWaitTimeChanged q =+ mapSignalM (const $ queueInputWaitTime q) (queueInputWaitTimeChanged_ q)+ +-- | Signal when the 'queueInputWaitTime' property value has changed.+queueInputWaitTimeChanged_ :: Queue si qi sm qm so qo a -> Signal ()+queueInputWaitTimeChanged_ q =+ mapSignal (const ()) (enqueueStored q)+ +-- | Return the output wait time from the time at which the item was requested+-- for dequeueing to the time at which it was actually dequeued.+--+-- See also 'queueOutputWaitTimeChanged' and 'queueOutputWaitTimeChanged_'.+queueOutputWaitTime :: Queue si qi sm qm so qo a -> Event (SamplingStats Double)+queueOutputWaitTime q =+ Event $ \p -> readIORef (queueOutputWaitTimeRef q)+ +-- | Signal when the 'queueOutputWaitTime' property value has changed.+queueOutputWaitTimeChanged :: Queue si qi sm qm so qo a -> Signal (SamplingStats Double)+queueOutputWaitTimeChanged q =+ mapSignalM (const $ queueOutputWaitTime q) (queueOutputWaitTimeChanged_ q)+ +-- | Signal when the 'queueOutputWaitTime' property value has changed.+queueOutputWaitTimeChanged_ :: Queue si qi sm qm so qo a -> Signal ()+queueOutputWaitTimeChanged_ q =+ mapSignal (const ()) (dequeueExtracted q)+ -- | Dequeue suspending the process if the queue is empty. dequeue :: (DequeueStrategy si qi, DequeueStrategy sm qm,@@ -135,53 +545,26 @@ -> Process a -- ^ the dequeued value dequeue q =- do requestResource (queueOutputRes q)- a <- liftEvent $- strategyDequeue (queueMemoryStrategy q) (queueMemory q)- releaseResource (queueInputRes q)- liftEvent $- triggerSignal (dequeuedSource q) a- return a- --- | Dequeue with the priority suspending the process if the queue is empty.-dequeueWithPriority :: (DequeueStrategy si qi,- DequeueStrategy sm qm,- PriorityQueueStrategy so qo)- => Queue si qi sm qm so qo a- -- ^ the queue- -> Double- -- ^ the priority- -> Process a- -- ^ the dequeued value-dequeueWithPriority q priority =- do requestResourceWithPriority (queueOutputRes q) priority- a <- liftEvent $- strategyDequeue (queueMemoryStrategy q) (queueMemory q)- releaseResource (queueInputRes q)- liftEvent $- triggerSignal (dequeuedSource q) a- return a+ do t <- liftEvent $ dequeueRequest q+ requestResource (queueOutputRes q)+ liftEvent $ dequeueExtract q t --- | Dequeue with the dynamic priority suspending the process if the queue is empty.-dequeueWithDynamicPriority :: (DequeueStrategy si qi,- DequeueStrategy sm qm,- DynamicPriorityQueueStrategy so qo)- => Queue si qi sm qm so qo a- -- ^ the queue- -> Event Double- -- ^ the dynamic priority- -> Process a- -- ^ the dequeued value-dequeueWithDynamicPriority q priority =- do requestResourceWithDynamicPriority (queueOutputRes q) priority- a <- liftEvent $- strategyDequeue (queueMemoryStrategy q) (queueMemory q)- releaseResource (queueInputRes q)- liftEvent $- triggerSignal (dequeuedSource q) a- return a+-- | Dequeue with the output priority suspending the process if the queue is empty.+dequeueWithOutputPriority :: (DequeueStrategy si qi,+ DequeueStrategy sm qm,+ PriorityQueueStrategy so qo po)+ => Queue si qi sm qm so qo a+ -- ^ the queue+ -> po+ -- ^ the priority for output+ -> Process a+ -- ^ the dequeued value+dequeueWithOutputPriority q po =+ do t <- liftEvent $ dequeueRequest q+ requestResourceWithPriority (queueOutputRes q) po+ liftEvent $ dequeueExtract q t --- | Try to dequeue from the queue immediately. +-- | Try to dequeue immediately. tryDequeue :: (DequeueStrategy si qi, DequeueStrategy sm qm) => Queue si qi sm qm so qo a@@ -191,10 +574,8 @@ tryDequeue q = do x <- tryRequestResourceWithinEvent (queueOutputRes q) if x - then do a <- strategyDequeue (queueMemoryStrategy q) (queueMemory q)- releaseResourceWithinEvent (queueInputRes q)- triggerSignal (dequeuedSource q) a- return $ Just a+ then do t <- dequeueRequest q+ fmap Just $ dequeueExtract q t else return Nothing -- | Enqueue the item suspending the process if the queue is full. @@ -207,51 +588,60 @@ -- ^ the item to enqueue -> Process () enqueue q a =- do requestResource (queueInputRes q)- liftEvent $- strategyEnqueue (queueMemoryStrategy q) (queueMemory q) a- releaseResource (queueOutputRes q)- liftEvent $- triggerSignal (enqueuedSource q) a+ do i <- liftEvent $ enqueueInitiate q a+ requestResource (queueInputRes q)+ liftEvent $ enqueueStore q i --- | Enqueue with the priority the item suspending the process if the queue is full. -enqueueWithPriority :: (PriorityQueueStrategy si qi,- EnqueueStrategy sm qm,- DequeueStrategy so qo)- => Queue si qi sm qm so qo a- -- ^ the queue- -> Double- -- ^ the priority- -> a- -- ^ the item to enqueue- -> Process ()-enqueueWithPriority q priority a =- do requestResourceWithPriority (queueInputRes q) priority- liftEvent $- strategyEnqueue (queueMemoryStrategy q) (queueMemory q) a- releaseResource (queueOutputRes q)- liftEvent $- triggerSignal (enqueuedSource q) a+-- | Enqueue with the input priority the item suspending the process if the queue is full. +enqueueWithInputPriority :: (PriorityQueueStrategy si qi pi,+ EnqueueStrategy sm qm,+ DequeueStrategy so qo)+ => Queue si qi sm qm so qo a+ -- ^ the queue+ -> pi+ -- ^ the priority for input+ -> a+ -- ^ the item to enqueue+ -> Process ()+enqueueWithInputPriority q pi a =+ do i <- liftEvent $ enqueueInitiate q a+ requestResourceWithPriority (queueInputRes q) pi+ liftEvent $ enqueueStore q i --- | Enqueue with the dynamic priority the item suspending the process if the queue is full. -enqueueWithDynamicPriority :: (DynamicPriorityQueueStrategy si qi,- EnqueueStrategy sm qm,+-- | Enqueue with the storing priority the item suspending the process if the queue is full. +enqueueWithStoringPriority :: (EnqueueStrategy si qi,+ PriorityQueueStrategy sm qm pm, DequeueStrategy so qo) => Queue si qi sm qm so qo a -- ^ the queue- -> Event Double- -- ^ the dynamic priority+ -> pm+ -- ^ the priority for storing -> a -- ^ the item to enqueue -> Process ()-enqueueWithDynamicPriority q priority a =- do requestResourceWithDynamicPriority (queueInputRes q) priority- liftEvent $- strategyEnqueue (queueMemoryStrategy q) (queueMemory q) a- releaseResource (queueOutputRes q)- liftEvent $- triggerSignal (enqueuedSource q) a+enqueueWithStoringPriority q pm a =+ do i <- liftEvent $ enqueueInitiate q a+ requestResource (queueInputRes q)+ liftEvent $ enqueueStoreWithPriority q pm i +-- | Enqueue with the input and storing priorities the item suspending the process if the queue is full. +enqueueWithInputStoringPriorities :: (PriorityQueueStrategy si qi pi,+ PriorityQueueStrategy sm qm pm,+ DequeueStrategy so qo)+ => Queue si qi sm qm so qo a+ -- ^ the queue+ -> pi+ -- ^ the priority for input+ -> pm+ -- ^ the priority for storing+ -> a+ -- ^ the item to enqueue+ -> Process ()+enqueueWithInputStoringPriorities q pi pm a =+ do i <- liftEvent $ enqueueInitiate q a+ requestResourceWithPriority (queueInputRes q) pi+ liftEvent $ enqueueStoreWithPriority q pm i+ -- | Try to enqueue the item. Return 'False' in the monad if the queue is full. tryEnqueue :: (EnqueueStrategy sm qm, DequeueStrategy so qo)@@ -263,12 +653,28 @@ tryEnqueue q a = do x <- tryRequestResourceWithinEvent (queueInputRes q) if x - then do strategyEnqueue (queueMemoryStrategy q) (queueMemory q) a- releaseResourceWithinEvent (queueOutputRes q)- triggerSignal (enqueuedSource q) a+ then do enqueueInitiate q a >>= enqueueStore q return True else return False +-- | Try to enqueue with the storing priority the item. Return 'False' in+-- the monad if the queue is full.+tryEnqueueWithStoringPriority :: (PriorityQueueStrategy sm qm pm,+ DequeueStrategy so qo)+ => Queue si qi sm qm so qo a+ -- ^ the queue+ -> pm+ -- ^ the priority for storing+ -> a+ -- ^ the item which we try to enqueue+ -> Event Bool+tryEnqueueWithStoringPriority q pm a =+ do x <- tryRequestResourceWithinEvent (queueInputRes q)+ if x + then do enqueueInitiate q a >>= enqueueStoreWithPriority q pm+ return True+ else return False+ -- | Try to enqueue the item. If the queue is full then the item will be lost -- and 'False' will be returned. enqueueOrLost :: (EnqueueStrategy sm qm,@@ -281,14 +687,30 @@ enqueueOrLost q a = do x <- tryRequestResourceWithinEvent (queueInputRes q) if x- then do strategyEnqueue (queueMemoryStrategy q) (queueMemory q) a- releaseResourceWithinEvent (queueOutputRes q)- triggerSignal (enqueuedSource q) a+ then do enqueueInitiate q a >>= enqueueStore q return True- else do liftIO $ modifyIORef (queueLostCountRef q) $ (+) 1- triggerSignal (enqueuedButLostSource q) a+ else do enqueueDeny q a return False +-- | Try to enqueue with the storing priority the item. If the queue is full+-- then the item will be lost and 'False' will be returned.+enqueueWithStoringPriorityOrLost :: (PriorityQueueStrategy sm qm pm,+ DequeueStrategy so qo)+ => Queue si qi sm qm so qo a+ -- ^ the queue+ -> pm+ -- ^ the priority for storing+ -> a+ -- ^ the item which we try to enqueue+ -> Event Bool+enqueueWithStoringPriorityOrLost q pm a =+ do x <- tryRequestResourceWithinEvent (queueInputRes q)+ if x+ then do enqueueInitiate q a >>= enqueueStoreWithPriority q pm+ return True+ else do enqueueDeny q a+ return False+ -- | Try to enqueue the item. If the queue is full then the item will be lost. enqueueOrLost_ :: (EnqueueStrategy sm qm, DequeueStrategy so qo)@@ -301,16 +723,328 @@ do x <- enqueueOrLost q a return () --- | Return a signal that notifies when any item is enqueued.-enqueued :: Queue si qi sm qm so qo a -> Signal a-enqueued q = publishSignal (enqueuedSource q)+-- | Try to enqueue with the storing priority the item. If the queue is full+-- then the item will be lost.+enqueueWithStoringPriorityOrLost_ :: (PriorityQueueStrategy sm qm pm,+ DequeueStrategy so qo)+ => Queue si qi sm qm so qo a+ -- ^ the queue+ -> pm+ -- ^ the priority for storing+ -> a+ -- ^ the item which we try to enqueue+ -> Event ()+enqueueWithStoringPriorityOrLost_ q pm a =+ do x <- enqueueWithStoringPriorityOrLost q pm a+ return () +-- | Return a signal that notifies when the enqueuing operation is initiated.+enqueueInitiated :: Queue si qi sm qm so qo a -> Signal a+enqueueInitiated q = publishSignal (enqueueInitiatedSource q)++-- | Return a signal that notifies when the enqueuing operation is completed+-- and the item is stored in the internal memory of the queue.+enqueueStored :: Queue si qi sm qm so qo a -> Signal a+enqueueStored q = publishSignal (enqueueStoredSource q)+ -- | Return a signal which notifies that the item was lost when -- attempting to add it to the full queue with help of--- 'enqueueOrLost' or 'enqueueOrLost_'.-enqueuedButLost :: Queue si qi sm qm so qo a -> Signal a-enqueuedButLost q = publishSignal (enqueuedButLostSource q)+-- 'enqueueOrLost', 'enqueueOrLost_' or similar functions that imply+-- that the element can be lost. All their names are ending with @OrLost@+-- or @OrLost_@.+--+-- In other cases the enqueued items are not lost but the corresponded process+-- can suspend until the internal queue storage is freed. Although there is one+-- exception from this rule. If the process trying to enqueue a new element was+-- suspended but then canceled through 'cancelProcess' from the outside then+-- the item will not be added.+enqueueLost :: Queue si qi sm qm so qo a -> Signal a+enqueueLost q = publishSignal (enqueueLostSource q) --- | Return a signal that notifies when any item is dequeued.-dequeued :: Queue si qi sm qm so qo a -> Signal a-dequeued q = publishSignal (dequeuedSource q)+-- | Return a signal that notifies when the dequeuing operation was requested.+dequeueRequested :: Queue si qi sm qm so qo a -> Signal ()+dequeueRequested q = publishSignal (dequeueRequestedSource q)++-- | Return a signal that notifies when the item was extracted from the internal+-- storage of the queue and prepared for immediate receiving by the dequeuing process.+dequeueExtracted :: Queue si qi sm qm so qo a -> Signal a+dequeueExtracted q = publishSignal (dequeueExtractedSource q)++-- | Initiate the process of enqueuing the item.+enqueueInitiate :: Queue si qi sm qm so qo a+ -- ^ the queue+ -> a+ -- ^ the item to be enqueued+ -> Event (QueueItem a)+enqueueInitiate q a =+ Event $ \p ->+ do let t = pointTime p+ modifyIORef (queueInputCountRef q) (+ 1)+ invokeEvent p $+ triggerSignal (enqueueInitiatedSource q) a+ return QueueItem { itemValue = a,+ itemInputTime = t,+ itemStoringTime = t -- it will be updated soon+ }++-- | Store the item.+enqueueStore :: (EnqueueStrategy sm qm,+ DequeueStrategy so qo)+ => Queue si qi sm qm so qo a+ -- ^ the queue+ -> QueueItem a+ -- ^ the item to be stored+ -> Event ()+enqueueStore q i =+ Event $ \p ->+ do let i' = i { itemStoringTime = pointTime p } -- now we have the actual time of storing+ invokeEvent p $+ strategyEnqueue (queueStoringStrategy q) (queueStore q) i'+ modifyIORef (queueCountRef q) (+ 1)+ modifyIORef (queueStoreCountRef q) (+ 1)+ invokeEvent p $+ enqueueStat q i'+ invokeEvent p $+ releaseResourceWithinEvent (queueOutputRes q)+ invokeEvent p $+ triggerSignal (enqueueStoredSource q) (itemValue i')++-- | Store with the priority the item.+enqueueStoreWithPriority :: (PriorityQueueStrategy sm qm pm,+ DequeueStrategy so qo)+ => Queue si qi sm qm so qo a+ -- ^ the queue+ -> pm+ -- ^ the priority for storing+ -> QueueItem a+ -- ^ the item to be enqueued+ -> Event ()+enqueueStoreWithPriority q pm i =+ Event $ \p ->+ do let i' = i { itemStoringTime = pointTime p } -- now we have the actual time of storing+ invokeEvent p $+ strategyEnqueueWithPriority (queueStoringStrategy q) (queueStore q) pm i'+ modifyIORef (queueCountRef q) (+ 1)+ modifyIORef (queueStoreCountRef q) (+ 1)+ invokeEvent p $+ enqueueStat q i'+ invokeEvent p $+ releaseResourceWithinEvent (queueOutputRes q)+ invokeEvent p $+ triggerSignal (enqueueStoredSource q) (itemValue i')++-- | Deny the enqueuing.+enqueueDeny :: Queue si qi sm qm so qo a+ -- ^ the queue+ -> a+ -- ^ the item to be denied+ -> Event ()+enqueueDeny q a =+ Event $ \p ->+ do modifyIORef (queueLostCountRef q) $ (+) 1+ invokeEvent p $+ triggerSignal (enqueueLostSource q) a++-- | Update the statistics for the input wait time of the enqueuing operation.+enqueueStat :: Queue si qi sm qm so qo a+ -- ^ the queue+ -> QueueItem a+ -- ^ the item and its input time+ -> Event ()+ -- ^ the action of updating the statistics+enqueueStat q i =+ Event $ \p ->+ do let t0 = itemInputTime i+ t1 = itemStoringTime i+ modifyIORef (queueInputWaitTimeRef q) $+ addSamplingStats (t1 - t0)++-- | Accept the dequeuing request and return the current simulation time.+dequeueRequest :: Queue si qi sm qm so qo a+ -- ^ the queue+ -> Event Double+ -- ^ the current time+dequeueRequest q =+ Event $ \p ->+ do modifyIORef (queueOutputRequestCountRef q) (+ 1)+ invokeEvent p $+ triggerSignal (dequeueRequestedSource q) ()+ return $ pointTime p ++-- | Extract an item for the dequeuing request. +dequeueExtract :: (DequeueStrategy si qi,+ DequeueStrategy sm qm)+ => Queue si qi sm qm so qo a+ -- ^ the queue+ -> Double+ -- ^ the time of the dequeuing request+ -> Event a+ -- ^ the dequeued value+dequeueExtract q t' =+ Event $ \p ->+ do i <- invokeEvent p $+ strategyDequeue (queueStoringStrategy q) (queueStore q)+ modifyIORef (queueCountRef q) (+ (- 1))+ modifyIORef (queueOutputCountRef q) (+ 1)+ invokeEvent p $+ dequeueStat q t' i+ invokeEvent p $+ releaseResourceWithinEvent (queueInputRes q)+ invokeEvent p $+ triggerSignal (dequeueExtractedSource q) (itemValue i)+ return $ itemValue i++-- | Update the statistics for the output wait time of the dequeuing operation+-- and the wait time of storing in the queue.+dequeueStat :: Queue si qi sm qm so qo a+ -- ^ the queue+ -> Double+ -- ^ the time of the dequeuing request+ -> QueueItem a+ -- ^ the item and its input time+ -> Event ()+ -- ^ the action of updating the statistics+dequeueStat q t' i =+ Event $ \p ->+ do let t0 = itemInputTime i+ t1 = itemStoringTime i+ t = pointTime p+ modifyIORef (queueOutputWaitTimeRef q) $+ addSamplingStats (t - t')+ modifyIORef (queueTotalWaitTimeRef q) $+ addSamplingStats (t - t0)+ modifyIORef (queueWaitTimeRef q) $+ addSamplingStats (t - t1)++-- | Wait while the queue is full.+waitWhileFullQueue :: Queue si qi sm qm so qo a -> Process ()+waitWhileFullQueue q =+ do x <- liftEvent (queueFull q)+ when x $+ do processAwait (dequeueExtracted q)+ waitWhileFullQueue q++-- | Signal whenever any property of the queue changes.+--+-- The property must have the corresponded signal. There are also characteristics+-- similar to the properties but that have no signals. As a rule, such characteristics+-- already depend on the simulation time and therefore they may change at any+-- time point.+queueChanged_ :: Queue si qi sm qm so qo a -> Signal ()+queueChanged_ q =+ mapSignal (const ()) (enqueueInitiated q) <>+ mapSignal (const ()) (enqueueStored q) <>+ mapSignal (const ()) (enqueueLost q) <>+ dequeueRequested q <>+ mapSignal (const ()) (dequeueExtracted q)++-- | Return the summary for the queue with desciption of its+-- properties and activities using the specified indent.+queueSummary :: (Show si, Show sm, Show so) => Queue si qi sm qm so qo a -> Int -> Event ShowS+queueSummary q indent =+ do let si = queueInputStrategy q+ sm = queueStoringStrategy q+ so = queueOutputStrategy q+ null <- queueNull q+ full <- queueFull q+ let maxCount = queueMaxCount q+ count <- queueCount q+ lostCount <- queueLostCount q+ inputCount <- queueInputCount q+ storeCount <- queueStoreCount q+ outputRequestCount <- queueOutputRequestCount q+ outputCount <- queueOutputCount q+ loadFactor <- queueLoadFactor q+ inputRate <- queueInputRate q+ storeRate <- queueStoreRate q+ outputRequestRate <- queueOutputRequestRate q+ outputRate <- queueOutputRate q+ waitTime <- queueWaitTime q+ totalWaitTime <- queueTotalWaitTime q+ inputWaitTime <- queueInputWaitTime q+ outputWaitTime <- queueOutputWaitTime q+ let tab = replicate indent ' '+ return $+ showString tab .+ showString "the input (enqueueing) strategy = " .+ shows si .+ showString "\n" .+ showString tab .+ showString "the storing (memory) strategy = " .+ shows sm .+ showString "\n" .+ showString tab .+ showString "the output (dequeueing) strategy = " .+ shows so .+ showString "\n" .+ showString tab .+ showString "empty? = " .+ shows null .+ showString "\n" .+ showString tab .+ showString "full? = " .+ shows full .+ showString "\n" .+ showString tab .+ showString "max. capacity = " .+ shows maxCount .+ showString "\n" .+ showString tab .+ showString "size = " .+ shows count .+ showString "\n" .+ showString tab .+ showString "the lost count (number of the lost items) = " .+ shows lostCount .+ showString "\n" .+ showString tab .+ showString "the input count (number of the input items that were enqueued) = " .+ shows inputCount .+ showString "\n" .+ showString tab .+ showString "the store count (number of the input items that were stored) = " .+ shows storeCount .+ showString "\n" .+ showString tab .+ showString "the output request count (number of requests for dequeueing an item) = " .+ shows outputRequestCount .+ showString "\n" .+ showString tab .+ showString "the output count (number of the output items that were dequeued) = " .+ shows outputCount .+ showString "\n" .+ showString tab .+ showString "the load factor (size / max. capacity) = " .+ shows loadFactor .+ showString "\n" .+ showString tab .+ showString "the input rate (how many input items were enqueued per time) = " .+ shows inputRate .+ showString "\n" .+ showString tab .+ showString "the store rate (how many input items were stored per time) = " .+ shows storeRate .+ showString "\n" .+ showString tab .+ showString "the output request rate (how many requests for dequeueing per time) = " .+ shows outputRequestRate .+ showString "\n" .+ showString tab .+ showString "the output rate (how many output items were dequeued per time) = " .+ shows outputRate .+ showString "\n" .+ showString tab .+ showString "the wait time (when was stored -> when was dequeued) = \n\n" .+ samplingStatsSummary waitTime (2 + indent) .+ showString "\n\n" .+ showString tab .+ showString "the total wait time (when the enqueueing was initiated -> when was dequeued) = \n\n" .+ samplingStatsSummary totalWaitTime (2 + indent) .+ showString "\n\n" .+ showString tab .+ showString "the input wait time (when the enqueueing was initiated -> when was stored) = \n\n" .+ samplingStatsSummary inputWaitTime (2 + indent) .+ showString "\n\n" .+ showString tab .+ showString "the output wait time (when was requested for dequeueing -> when was dequeued) = \n\n" .+ samplingStatsSummary outputWaitTime (2 + indent)
+ Simulation/Aivika/Queue/Infinite.hs view
@@ -0,0 +1,592 @@++-- |+-- Module : Simulation.Aivika.Queue.Infinite+-- Copyright : Copyright (c) 2009-2013, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.6.3+--+-- This module defines an infinite queue that can use the specified strategies.+--+module Simulation.Aivika.Queue.Infinite+ (-- * Queue Types+ FCFSQueue,+ LCFSQueue,+ SIROQueue,+ PriorityQueue,+ Queue,+ -- * Creating Queue+ newFCFSQueue,+ newLCFSQueue,+ newSIROQueue,+ newPriorityQueue,+ newQueue,+ -- * Queue Properties and Activities+ queueStoringStrategy,+ queueOutputStrategy,+ queueNull,+ queueCount,+ queueStoreCount,+ queueOutputRequestCount,+ queueOutputCount,+ queueStoreRate,+ queueOutputRequestRate,+ queueOutputRate,+ queueWaitTime,+ queueOutputWaitTime,+ -- * Dequeuing and Enqueuing+ dequeue,+ dequeueWithOutputPriority,+ tryDequeue,+ enqueue,+ enqueueWithStoringPriority,+ -- * Summary+ queueSummary,+ -- * Derived Signals for Properties+ queueNullChanged,+ queueNullChanged_,+ queueCountChanged,+ queueCountChanged_,+ queueStoreCountChanged,+ queueStoreCountChanged_,+ queueOutputRequestCountChanged,+ queueOutputRequestCountChanged_,+ queueOutputCountChanged,+ queueOutputCountChanged_,+ queueWaitTimeChanged,+ queueWaitTimeChanged_,+ queueOutputWaitTimeChanged,+ queueOutputWaitTimeChanged_,+ -- * Basic Signals+ enqueueStored,+ dequeueRequested,+ dequeueExtracted,+ -- * Overall Signal+ queueChanged_) where++import Data.IORef+import Data.Monoid++import Control.Monad+import Control.Monad.Trans++import Simulation.Aivika.Internal.Specs+import Simulation.Aivika.Internal.Simulation+import Simulation.Aivika.Internal.Dynamics+import Simulation.Aivika.Internal.Event+import Simulation.Aivika.Internal.Process+import Simulation.Aivika.Internal.Signal+import Simulation.Aivika.Signal+import Simulation.Aivika.Resource+import Simulation.Aivika.QueueStrategy+import Simulation.Aivika.Statistics+import Simulation.Aivika.Stream+import Simulation.Aivika.Processor++import qualified Simulation.Aivika.DoubleLinkedList as DLL +import qualified Simulation.Aivika.Vector as V+import qualified Simulation.Aivika.PriorityQueue as PQ++-- | A type synonym for the ordinary FIFO queue also known as the FCFS+-- (First Come - First Serviced) queue.+type FCFSQueue a =+ Queue FCFS DLL.DoubleLinkedList FCFS DLL.DoubleLinkedList a++-- | A type synonym for the ordinary LIFO queue also known as the LCFS+-- (Last Come - First Serviced) queue.+type LCFSQueue a =+ Queue LCFS DLL.DoubleLinkedList FCFS DLL.DoubleLinkedList a++-- | A type synonym for the SIRO (Serviced in Random Order) queue.+type SIROQueue a =+ Queue SIRO V.Vector FCFS DLL.DoubleLinkedList a++-- | A type synonym for the queue with static priorities applied when+-- storing the elements in the queue.+type PriorityQueue a =+ Queue StaticPriorities PQ.PriorityQueue FCFS DLL.DoubleLinkedList a++-- | Represents the infinite queue using the specified strategies for+-- internal storing (in memory) @sm@ and output @so@, where @a@ denotes+-- the type of items stored in the queue. Types @qm@ and @qo@ are+-- determined automatically and you should not care about them - they+-- are dependent types.+data Queue sm qm so qo a =+ Queue { queueStoringStrategy :: sm,+ -- ^ The strategy applied when storing (in memory) items in the queue.+ queueOutputStrategy :: so,+ -- ^ The strategy applied to the output (dequeuing) process.+ queueStore :: qm (QueueItem a),+ queueOutputRes :: Resource so qo,+ queueCountRef :: IORef Int,+ queueStoreCountRef :: IORef Int,+ queueOutputRequestCountRef :: IORef Int,+ queueOutputCountRef :: IORef Int,+ queueWaitTimeRef :: IORef (SamplingStats Double),+ queueOutputWaitTimeRef :: IORef (SamplingStats Double),+ enqueueStoredSource :: SignalSource a,+ dequeueRequestedSource :: SignalSource (),+ dequeueExtractedSource :: SignalSource a }++-- | Stores the item and a time of its enqueuing. +data QueueItem a =+ QueueItem { itemValue :: a,+ -- ^ Return the item value.+ itemStoringTime :: Double+ -- ^ Return the time of storing in the queue.+ }+ +-- | Create a new infinite FCFS queue. +newFCFSQueue :: Simulation (FCFSQueue a) +newFCFSQueue = newQueue FCFS FCFS+ +-- | Create a new infinite LCFS queue. +newLCFSQueue :: Simulation (LCFSQueue a) +newLCFSQueue = newQueue LCFS FCFS+ +-- | Create a new infinite SIRO queue. +newSIROQueue :: Simulation (SIROQueue a) +newSIROQueue = newQueue SIRO FCFS+ +-- | Create a new infinite priority queue. +newPriorityQueue :: Simulation (PriorityQueue a) +newPriorityQueue = newQueue StaticPriorities FCFS+ +-- | Create a new infinite queue with the specified strategies. +newQueue :: (QueueStrategy sm qm,+ QueueStrategy so qo) =>+ sm+ -- ^ the strategy applied when storing items in the queue+ -> so+ -- ^ the strategy applied to the output (dequeuing) process+ -> Simulation (Queue sm qm so qo a) +newQueue sm so =+ do i <- liftIO $ newIORef 0+ cm <- liftIO $ newIORef 0+ cr <- liftIO $ newIORef 0+ co <- liftIO $ newIORef 0+ qm <- newStrategyQueue sm+ ro <- newResourceWithMaxCount so 0 Nothing+ w <- liftIO $ newIORef mempty+ wo <- liftIO $ newIORef mempty + s3 <- newSignalSource+ s4 <- newSignalSource+ s5 <- newSignalSource+ return Queue { queueStoringStrategy = sm,+ queueOutputStrategy = so,+ queueStore = qm,+ queueOutputRes = ro,+ queueCountRef = i,+ queueStoreCountRef = cm,+ queueOutputRequestCountRef = cr,+ queueOutputCountRef = co,+ queueWaitTimeRef = w,+ queueOutputWaitTimeRef = wo,+ enqueueStoredSource = s3,+ dequeueRequestedSource = s4,+ dequeueExtractedSource = s5 }++-- | Test whether the queue is empty.+--+-- See also 'queueNullChanged' and 'queueNullChanged_'.+queueNull :: Queue sm qm so qo a -> Event Bool+queueNull q =+ Event $ \p ->+ do n <- readIORef (queueCountRef q)+ return (n == 0)+ +-- | Signal when the 'queueNull' property value has changed.+queueNullChanged :: Queue sm qm so qo a -> Signal Bool+queueNullChanged q =+ mapSignalM (const $ queueNull q) (queueNullChanged_ q)+ +-- | Signal when the 'queueNull' property value has changed.+queueNullChanged_ :: Queue sm qm so qo a -> Signal ()+queueNullChanged_ = queueCountChanged_++-- | Return the queue size.+--+-- See also 'queueCountChanged' and 'queueCountChanged_'.+queueCount :: Queue sm qm so qo a -> Event Int+queueCount q =+ Event $ \p -> readIORef (queueCountRef q)+ +-- | Signal when the 'queueCount' property value has changed.+queueCountChanged :: Queue sm qm so qo a -> Signal Int+queueCountChanged q =+ mapSignalM (const $ queueCount q) (queueCountChanged_ q)+ +-- | Signal when the 'queueCount' property value has changed.+queueCountChanged_ :: Queue sm qm so qo a -> Signal ()+queueCountChanged_ q =+ mapSignal (const ()) (enqueueStored q) <>+ mapSignal (const ()) (dequeueExtracted q)+ +-- | Return the total number of input items that were stored.+--+-- See also 'queueStoreCountChanged' and 'queueStoreCountChanged_'.+queueStoreCount :: Queue sm qm so qo a -> Event Int+queueStoreCount q =+ Event $ \p -> readIORef (queueStoreCountRef q)+ +-- | Signal when the 'queueStoreCount' property value has changed.+queueStoreCountChanged :: Queue sm qm so qo a -> Signal Int+queueStoreCountChanged q =+ mapSignalM (const $ queueStoreCount q) (queueStoreCountChanged_ q)+ +-- | Signal when the 'queueStoreCount' property value has changed.+queueStoreCountChanged_ :: Queue sm qm so qo a -> Signal ()+queueStoreCountChanged_ q =+ mapSignal (const ()) (enqueueStored q)+ +-- | Return the total number of requests for dequeueing the items,+-- not taking into account the attempts to dequeue immediately+-- without suspension.+--+-- See also 'queueOutputRequestCountChanged' and 'queueOutputRequestCountChanged_'.+queueOutputRequestCount :: Queue sm qm so qo a -> Event Int+queueOutputRequestCount q =+ Event $ \p -> readIORef (queueOutputRequestCountRef q)+ +-- | Signal when the 'queueOutputRequestCount' property value has changed.+queueOutputRequestCountChanged :: Queue sm qm so qo a -> Signal Int+queueOutputRequestCountChanged q =+ mapSignalM (const $ queueOutputRequestCount q) (queueOutputRequestCountChanged_ q)+ +-- | Signal when the 'queueOutputRequestCount' property value has changed.+queueOutputRequestCountChanged_ :: Queue sm qm so qo a -> Signal ()+queueOutputRequestCountChanged_ q =+ mapSignal (const ()) (dequeueRequested q)+ +-- | Return the total number of output items that were dequeued.+--+-- See also 'queueOutputCountChanged' and 'queueOutputCountChanged_'.+queueOutputCount :: Queue sm qm so qo a -> Event Int+queueOutputCount q =+ Event $ \p -> readIORef (queueOutputCountRef q)+ +-- | Signal when the 'queueOutputCount' property value has changed.+queueOutputCountChanged :: Queue sm qm so qo a -> Signal Int+queueOutputCountChanged q =+ mapSignalM (const $ queueOutputCount q) (queueOutputCountChanged_ q)+ +-- | Signal when the 'queueOutputCount' property value has changed.+queueOutputCountChanged_ :: Queue sm qm so qo a -> Signal ()+queueOutputCountChanged_ q =+ mapSignal (const ()) (dequeueExtracted q)++-- | Return the rate of the items that were stored: how many items+-- per time.+queueStoreRate :: Queue sm qm so qo a -> Event Double+queueStoreRate q =+ Event $ \p ->+ do x <- readIORef (queueStoreCountRef q)+ let t0 = spcStartTime $ pointSpecs p+ t = pointTime p+ return (fromIntegral x / (t - t0))+ +-- | Return the rate of the requests for dequeueing the items: how many requests+-- per time. It does not include the attempts to dequeue immediately+-- without suspension.+queueOutputRequestRate :: Queue sm qm so qo a -> Event Double+queueOutputRequestRate q =+ Event $ \p ->+ do x <- readIORef (queueOutputRequestCountRef q)+ let t0 = spcStartTime $ pointSpecs p+ t = pointTime p+ return (fromIntegral x / (t - t0))+ +-- | Return the rate of the output items that were dequeued: how many items+-- per time.+queueOutputRate :: Queue sm qm so qo a -> Event Double+queueOutputRate q =+ Event $ \p ->+ do x <- readIORef (queueOutputCountRef q)+ let t0 = spcStartTime $ pointSpecs p+ t = pointTime p+ return (fromIntegral x / (t - t0))+ +-- | Return the wait time from the time at which the item was stored in the queue to+-- the time at which it was dequeued.+--+-- See also 'queueWaitTimeChanged' and 'queueWaitTimeChanged_'.+queueWaitTime :: Queue sm qm so qo a -> Event (SamplingStats Double)+queueWaitTime q =+ Event $ \p -> readIORef (queueWaitTimeRef q)+ +-- | Signal when the 'queueWaitTime' property value has changed.+queueWaitTimeChanged :: Queue sm qm so qo a -> Signal (SamplingStats Double)+queueWaitTimeChanged q =+ mapSignalM (const $ queueWaitTime q) (queueWaitTimeChanged_ q)+ +-- | Signal when the 'queueWaitTime' property value has changed.+queueWaitTimeChanged_ :: Queue sm qm so qo a -> Signal ()+queueWaitTimeChanged_ q =+ mapSignal (const ()) (dequeueExtracted q)+ +-- | Return the output wait time from the time at which the item was requested+-- for dequeueing to the time at which it was actually dequeued.+--+-- See also 'queueOutputWaitTimeChanged' and 'queueOutputWaitTimeChanged_'.+queueOutputWaitTime :: Queue sm qm so qo a -> Event (SamplingStats Double)+queueOutputWaitTime q =+ Event $ \p -> readIORef (queueOutputWaitTimeRef q)+ +-- | Signal when the 'queueOutputWaitTime' property value has changed.+queueOutputWaitTimeChanged :: Queue sm qm so qo a -> Signal (SamplingStats Double)+queueOutputWaitTimeChanged q =+ mapSignalM (const $ queueOutputWaitTime q) (queueOutputWaitTimeChanged_ q)+ +-- | Signal when the 'queueOutputWaitTime' property value has changed.+queueOutputWaitTimeChanged_ :: Queue sm qm so qo a -> Signal ()+queueOutputWaitTimeChanged_ q =+ mapSignal (const ()) (dequeueExtracted q)+ +-- | Dequeue suspending the process if the queue is empty.+dequeue :: (DequeueStrategy sm qm,+ EnqueueStrategy so qo)+ => Queue sm qm so qo a+ -- ^ the queue+ -> Process a+ -- ^ the dequeued value+dequeue q =+ do t <- liftEvent $ dequeueRequest q+ requestResource (queueOutputRes q)+ liftEvent $ dequeueExtract q t+ +-- | Dequeue with the output priority suspending the process if the queue is empty.+dequeueWithOutputPriority :: (DequeueStrategy sm qm,+ PriorityQueueStrategy so qo po)+ => Queue sm qm so qo a+ -- ^ the queue+ -> po+ -- ^ the priority for output+ -> Process a+ -- ^ the dequeued value+dequeueWithOutputPriority q po =+ do t <- liftEvent $ dequeueRequest q+ requestResourceWithPriority (queueOutputRes q) po+ liftEvent $ dequeueExtract q t+ +-- | Try to dequeue immediately.+tryDequeue :: DequeueStrategy sm qm+ => Queue sm qm so qo a+ -- ^ the queue+ -> Event (Maybe a)+ -- ^ the dequeued value of 'Nothing'+tryDequeue q =+ do x <- tryRequestResourceWithinEvent (queueOutputRes q)+ if x + then do t <- dequeueRequest q+ fmap Just $ dequeueExtract q t+ else return Nothing++-- | Enqueue the item. +enqueue :: (EnqueueStrategy sm qm,+ DequeueStrategy so qo)+ => Queue sm qm so qo a+ -- ^ the queue+ -> a+ -- ^ the item to enqueue+ -> Event ()+enqueue = enqueueStore+ +-- | Enqueue with the storing priority the item. +enqueueWithStoringPriority :: (PriorityQueueStrategy sm qm pm,+ DequeueStrategy so qo)+ => Queue sm qm so qo a+ -- ^ the queue+ -> pm+ -- ^ the priority for storing+ -> a+ -- ^ the item to enqueue+ -> Event ()+enqueueWithStoringPriority = enqueueStoreWithPriority++-- | Return a signal that notifies when the enqueued item+-- is stored in the internal memory of the queue.+enqueueStored :: Queue sm qm so qo a -> Signal a+enqueueStored q = publishSignal (enqueueStoredSource q)++-- | Return a signal that notifies when the dequeuing operation was requested.+dequeueRequested :: Queue sm qm so qo a -> Signal ()+dequeueRequested q = publishSignal (dequeueRequestedSource q)++-- | Return a signal that notifies when the item was extracted from the internal+-- storage of the queue and prepared for immediate receiving by the dequeuing process.+dequeueExtracted :: Queue sm qm so qo a -> Signal a+dequeueExtracted q = publishSignal (dequeueExtractedSource q)++-- | Store the item.+enqueueStore :: (EnqueueStrategy sm qm,+ DequeueStrategy so qo)+ => Queue sm qm so qo a+ -- ^ the queue+ -> a+ -- ^ the item to be stored+ -> Event ()+enqueueStore q a =+ Event $ \p ->+ do let i = QueueItem { itemValue = a,+ itemStoringTime = pointTime p }+ invokeEvent p $+ strategyEnqueue (queueStoringStrategy q) (queueStore q) i+ modifyIORef (queueCountRef q) (+ 1)+ modifyIORef (queueStoreCountRef q) (+ 1)+ invokeEvent p $+ releaseResourceWithinEvent (queueOutputRes q)+ invokeEvent p $+ triggerSignal (enqueueStoredSource q) (itemValue i)++-- | Store with the priority the item.+enqueueStoreWithPriority :: (PriorityQueueStrategy sm qm pm,+ DequeueStrategy so qo)+ => Queue sm qm so qo a+ -- ^ the queue+ -> pm+ -- ^ the priority for storing+ -> a+ -- ^ the item to be enqueued+ -> Event ()+enqueueStoreWithPriority q pm a =+ Event $ \p ->+ do let i = QueueItem { itemValue = a,+ itemStoringTime = pointTime p }+ invokeEvent p $+ strategyEnqueueWithPriority (queueStoringStrategy q) (queueStore q) pm i+ modifyIORef (queueCountRef q) (+ 1)+ modifyIORef (queueStoreCountRef q) (+ 1)+ invokeEvent p $+ releaseResourceWithinEvent (queueOutputRes q)+ invokeEvent p $+ triggerSignal (enqueueStoredSource q) (itemValue i)++-- | Accept the dequeuing request and return the current simulation time.+dequeueRequest :: Queue sm qm so qo a+ -- ^ the queue+ -> Event Double+ -- ^ the current time+dequeueRequest q =+ Event $ \p ->+ do modifyIORef (queueOutputRequestCountRef q) (+ 1)+ invokeEvent p $+ triggerSignal (dequeueRequestedSource q) ()+ return $ pointTime p ++-- | Extract an item for the dequeuing request. +dequeueExtract :: DequeueStrategy sm qm+ => Queue sm qm so qo a+ -- ^ the queue+ -> Double+ -- ^ the time of the dequeuing request+ -> Event a+ -- ^ the dequeued value+dequeueExtract q t' =+ Event $ \p ->+ do i <- invokeEvent p $+ strategyDequeue (queueStoringStrategy q) (queueStore q)+ modifyIORef (queueCountRef q) (+ (- 1))+ modifyIORef (queueOutputCountRef q) (+ 1)+ invokeEvent p $+ dequeueStat q t' i+ invokeEvent p $+ triggerSignal (dequeueExtractedSource q) (itemValue i)+ return $ itemValue i++-- | Update the statistics for the output wait time of the dequeuing operation+-- and the wait time of storing in the queue.+dequeueStat :: Queue sm qm so qo a+ -- ^ the queue+ -> Double+ -- ^ the time of the dequeuing request+ -> QueueItem a+ -- ^ the item and its input time+ -> Event ()+ -- ^ the action of updating the statistics+dequeueStat q t' i =+ Event $ \p ->+ do let t1 = itemStoringTime i+ t = pointTime p+ modifyIORef (queueOutputWaitTimeRef q) $+ addSamplingStats (t - t')+ modifyIORef (queueWaitTimeRef q) $+ addSamplingStats (t - t1)++-- | Signal whenever any property of the queue changes.+--+-- The property must have the corresponded signal. There are also characteristics+-- similar to the properties but that have no signals. As a rule, such characteristics+-- already depend on the simulation time and therefore they may change at any+-- time point.+queueChanged_ :: Queue sm qm so qo a -> Signal ()+queueChanged_ q =+ mapSignal (const ()) (enqueueStored q) <>+ dequeueRequested q <>+ mapSignal (const ()) (dequeueExtracted q)++-- | Return the summary for the queue with desciption of its+-- properties and activities using the specified indent.+queueSummary :: (Show sm, Show so) => Queue sm qm so qo a -> Int -> Event ShowS+queueSummary q indent =+ do let sm = queueStoringStrategy q+ so = queueOutputStrategy q+ null <- queueNull q+ count <- queueCount q+ storeCount <- queueStoreCount q+ outputRequestCount <- queueOutputRequestCount q+ outputCount <- queueOutputCount q+ storeRate <- queueStoreRate q+ outputRequestRate <- queueOutputRequestRate q+ outputRate <- queueOutputRate q+ waitTime <- queueWaitTime q+ outputWaitTime <- queueOutputWaitTime q+ let tab = replicate indent ' '+ return $+ showString tab .+ showString "the storing (memory) strategy = " .+ shows sm .+ showString "\n" .+ showString tab .+ showString "the output (dequeueing) strategy = " .+ shows so .+ showString "\n" .+ showString tab .+ showString "empty? = " .+ shows null .+ showString "\n" .+ showString tab .+ showString "size = " .+ shows count .+ showString "\n" .+ showString tab .+ showString "the store count (number of the input items that were stored) = " .+ shows storeCount .+ showString "\n" .+ showString tab .+ showString "the output request count (number of requests for dequeueing an item) = " .+ shows outputRequestCount .+ showString "\n" .+ showString tab .+ showString "the output count (number of the output items that were dequeued) = " .+ shows outputCount .+ showString "\n" .+ showString tab .+ showString "the store rate (how many input items were stored per time) = " .+ shows storeRate .+ showString "\n" .+ showString tab .+ showString "the output request rate (how many requests for dequeueing per time) = " .+ shows outputRequestRate .+ showString "\n" .+ showString tab .+ showString "the output rate (how many output items were dequeued per time) = " .+ shows outputRate .+ showString "\n" .+ showString tab .+ showString "the wait time (when was stored -> when was dequeued) = \n\n" .+ samplingStatsSummary waitTime (2 + indent) .+ showString "\n\n" .+ showString tab .+ showString "the output wait time (when was requested for dequeueing -> when was dequeued) = \n\n" .+ samplingStatsSummary outputWaitTime (2 + indent)
Simulation/Aivika/QueueStrategy.hs view
@@ -12,11 +12,12 @@ -- This module defines the queue strategies. -- module Simulation.Aivika.QueueStrategy- (QueueStrategy(..),+ (-- * Strategy Classes+ QueueStrategy(..), DequeueStrategy(..), EnqueueStrategy(..), PriorityQueueStrategy(..),- DynamicPriorityQueueStrategy(..),+ -- * Strategy Instances FCFS(..), LCFS(..), SIRO(..),@@ -35,46 +36,69 @@ class QueueStrategy s q | s -> q where -- | Create a new queue by the specified strategy.- newStrategyQueue :: s -> Simulation (q i)+ newStrategyQueue :: s+ -- ^ the strategy+ -> Simulation (q i)+ -- ^ a new queue -- | Test whether the queue is empty.- strategyQueueNull :: s -> q i -> Event Bool+ strategyQueueNull :: s+ -- ^ the strategy+ -> q i+ -- ^ the queue+ -> Event Bool+ -- ^ the result of the test -- | Defines a strategy with support of the dequeuing operation. class QueueStrategy s q => DequeueStrategy s q | s -> q where -- | Dequeue the front element and return it.- strategyDequeue :: s -> q i -> Event i+ strategyDequeue :: s+ -- ^ the strategy+ -> q i+ -- ^ the queue+ -> Event i+ -- ^ the dequeued element -- | It defines a strategy when we can enqueue a single element. class DequeueStrategy s q => EnqueueStrategy s q | s -> q where -- | Enqueue an element.- strategyEnqueue :: s -> q i -> i -> Event ()+ strategyEnqueue :: s+ -- ^ the strategy+ -> q i+ -- ^ the queue+ -> i+ -- ^ the element to be enqueued+ -> Event ()+ -- ^ the action of enqueuing -- | It defines a strategy when we can enqueue an element with the specified priority.-class DequeueStrategy s q => PriorityQueueStrategy s q | s -> q where-- -- | Enqueue an element with the specified priority.- strategyEnqueueWithPriority :: s -> q i -> Double -> i -> Event ()---- | It defines a strategy when we can enqueue an element with the dynamic priority.-class DequeueStrategy s q => DynamicPriorityQueueStrategy s q | s -> q where+class DequeueStrategy s q => PriorityQueueStrategy s q p | s -> q, s -> p where -- | Enqueue an element with the specified priority.- strategyEnqueueWithDynamicPriority :: s -> q i -> Event Double -> i -> Event ()+ strategyEnqueueWithPriority :: s+ -- ^ the strategy+ -> q i+ -- ^ the queue+ -> p+ -- ^ the priority+ -> i+ -- ^ the element to be enqueued+ -> Event ()+ -- ^ the action of enqueuing -- | Strategy: First Come - First Served (FCFS).-data FCFS = FCFS+data FCFS = FCFS deriving (Eq, Ord, Show) -- | Strategy: Last Come - First Served (LCFS)-data LCFS = LCFS+data LCFS = LCFS deriving (Eq, Ord, Show) -- | Strategy: Service in Random Order (SIRO).-data SIRO = SIRO+data SIRO = SIRO deriving (Eq, Ord, Show) -- | Strategy: Static Priorities. It uses the priority queue.-data StaticPriorities = StaticPriorities+data StaticPriorities = StaticPriorities deriving (Eq, Ord, Show) instance QueueStrategy FCFS DoubleLinkedList where @@ -126,7 +150,7 @@ PQ.dequeue q return i -instance PriorityQueueStrategy StaticPriorities PQ.PriorityQueue where+instance PriorityQueueStrategy StaticPriorities PQ.PriorityQueue Double where strategyEnqueueWithPriority s q p i = liftIO $ PQ.enqueue q p i
− Simulation/Aivika/Random.hs
@@ -1,53 +0,0 @@---- |--- Module : Simulation.Aivika.Random--- Copyright : Copyright (c) 2009-2013, David Sorokin <david.sorokin@gmail.com>--- License : BSD3--- Maintainer : David Sorokin <david.sorokin@gmail.com>--- Stability : experimental--- Tested with: GHC 7.6.3------ Below are defined some random functions.----module Simulation.Aivika.Random - (newNormalGen) where--import System.Random-import Data.IORef---- | Createa a normal random number generator with mean 0 and variance 1.-newNormalGen :: IO (IO Double)-newNormalGen =- do nextRef <- newIORef 0.0- flagRef <- newIORef False- xi1Ref <- newIORef 0.0- xi2Ref <- newIORef 0.0- psiRef <- newIORef 0.0- let loop =- do psi <- readIORef psiRef- if (psi >= 1.0) || (psi == 0.0)- then do g1 <- getStdRandom random- g2 <- getStdRandom random- let xi1 = 2.0 * g1 - 1.0- xi2 = 2.0 * g2 - 1.0- psi = xi1 * xi1 + xi2 * xi2- writeIORef xi1Ref xi1- writeIORef xi2Ref xi2- writeIORef psiRef psi- loop- else writeIORef psiRef $ sqrt (- 2.0 * log psi / psi)- return $- do flag <- readIORef flagRef- if flag- then do writeIORef flagRef False- readIORef nextRef- else do writeIORef xi1Ref 0.0- writeIORef xi2Ref 0.0- writeIORef psiRef 0.0- loop- xi1 <- readIORef xi1Ref- xi2 <- readIORef xi2Ref- psi <- readIORef psiRef- writeIORef flagRef True- writeIORef nextRef $ xi2 * psi- return $ xi1 * psi
Simulation/Aivika/Resource.hs view
@@ -7,89 +7,199 @@ -- Stability : experimental -- Tested with: GHC 7.6.3 ----- This module defines a limited resource which can be acquired and +-- This module defines the resource which can be acquired and -- then released by the discontinuous process 'Process'.+-- The resource can be either limited by the upper bound+-- (run-time check), or it can have no upper bound. The latter+-- is useful for modeling the infinite queue, for example. -- module Simulation.Aivika.Resource- (Resource,+ (-- * Resource Types+ FCFSResource,+ LCFSResource,+ SIROResource,+ PriorityResource,+ Resource,+ -- * Creating Resource+ newFCFSResource,+ newFCFSResourceWithMaxCount,+ newLCFSResource,+ newLCFSResourceWithMaxCount,+ newSIROResource,+ newSIROResourceWithMaxCount,+ newPriorityResource,+ newPriorityResourceWithMaxCount, newResource,- newResourceWithCount,+ newResourceWithMaxCount,+ -- * Resource Properties+ resourceStrategy, resourceMaxCount, resourceCount,+ -- * Requesting for and Releasing Resource requestResource, requestResourceWithPriority,- requestResourceWithDynamicPriority, tryRequestResourceWithinEvent, releaseResource, releaseResourceWithinEvent, usingResource,- usingResourceWithPriority,- usingResourceWithDynamicPriority) where+ usingResourceWithPriority) where import Data.IORef import Control.Monad import Control.Monad.Trans+import Control.Exception import Simulation.Aivika.Internal.Specs import Simulation.Aivika.Internal.Simulation import Simulation.Aivika.Internal.Event import Simulation.Aivika.Internal.Cont import Simulation.Aivika.Internal.Process- import Simulation.Aivika.QueueStrategy --- | Represents a limited resource.+import qualified Simulation.Aivika.DoubleLinkedList as DLL +import qualified Simulation.Aivika.Vector as V+import qualified Simulation.Aivika.PriorityQueue as PQ++-- | The ordinary FCFS (First Come - First Serviced) resource.+type FCFSResource = Resource FCFS DLL.DoubleLinkedList++-- | The ordinary LCFS (Last Come - First Serviced) resource.+type LCFSResource = Resource LCFS DLL.DoubleLinkedList++-- | The SIRO (Serviced in Random Order) resource.+type SIROResource = Resource SIRO V.Vector++-- | The resource with static priorities.+type PriorityResource = Resource StaticPriorities PQ.PriorityQueue++-- | Represents the resource with strategy @s@ applied for queuing the requests.+-- The @q@ type is dependent and it is usually derived automatically. data Resource s q = Resource { resourceStrategy :: s,- resourceMaxCount :: Int,- -- ^ Return the maximum count of the resource.+ -- ^ Return the strategy applied for queuing the requests.+ resourceMaxCount :: Maybe Int,+ -- ^ Return the maximum count of the resource, where 'Nothing'+ -- means that the resource has no upper bound. resourceCountRef :: IORef Int, - resourceWaitList :: q (ContParams ())}+ resourceWaitList :: q (Event (Maybe (ContParams ()))) } instance Eq (Resource s q) where x == y = resourceCountRef x == resourceCountRef y -- unique references --- | Create a new resource with the specified queue strategy and maximum count.+-- | Create a new FCFS resource with the specified initial count which value becomes+-- the upper bound as well.+newFCFSResource :: Int+ -- ^ the initial count (and maximal count too) of the resource+ -> Simulation FCFSResource+newFCFSResource = newResource FCFS++-- | Create a new FCFS resource with the specified initial and maximum counts,+-- where 'Nothing' means that the resource has no upper bound.+newFCFSResourceWithMaxCount :: Int+ -- ^ the initial count of the resource+ -> Maybe Int+ -- ^ the maximum count of the resource, which can be indefinite+ -> Simulation FCFSResource+newFCFSResourceWithMaxCount = newResourceWithMaxCount FCFS++-- | Create a new LCFS resource with the specified initial count which value becomes+-- the upper bound as well.+newLCFSResource :: Int+ -- ^ the initial count (and maximal count too) of the resource+ -> Simulation LCFSResource+newLCFSResource = newResource LCFS++-- | Create a new LCFS resource with the specified initial and maximum counts,+-- where 'Nothing' means that the resource has no upper bound.+newLCFSResourceWithMaxCount :: Int+ -- ^ the initial count of the resource+ -> Maybe Int+ -- ^ the maximum count of the resource, which can be indefinite+ -> Simulation LCFSResource+newLCFSResourceWithMaxCount = newResourceWithMaxCount LCFS++-- | Create a new SIRO resource with the specified initial count which value becomes+-- the upper bound as well.+newSIROResource :: Int+ -- ^ the initial count (and maximal count too) of the resource+ -> Simulation SIROResource+newSIROResource = newResource SIRO++-- | Create a new SIRO resource with the specified initial and maximum counts,+-- where 'Nothing' means that the resource has no upper bound.+newSIROResourceWithMaxCount :: Int+ -- ^ the initial count of the resource+ -> Maybe Int+ -- ^ the maximum count of the resource, which can be indefinite+ -> Simulation SIROResource+newSIROResourceWithMaxCount = newResourceWithMaxCount SIRO++-- | Create a new priority resource with the specified initial count which value becomes+-- the upper bound as well.+newPriorityResource :: Int+ -- ^ the initial count (and maximal count too) of the resource+ -> Simulation PriorityResource+newPriorityResource = newResource StaticPriorities++-- | Create a new priority resource with the specified initial and maximum counts,+-- where 'Nothing' means that the resource has no upper bound.+newPriorityResourceWithMaxCount :: Int+ -- ^ the initial count of the resource+ -> Maybe Int+ -- ^ the maximum count of the resource, which can be indefinite+ -> Simulation PriorityResource+newPriorityResourceWithMaxCount = newResourceWithMaxCount StaticPriorities++-- | Create a new resource with the specified queue strategy and initial count.+-- The last value becomes the upper bound as well. newResource :: QueueStrategy s q => s -- ^ the strategy for managing the queuing requests -> Int- -- ^ the maximum count of the resource+ -- ^ the initial count (and maximal count too) of the resource -> Simulation (Resource s q)-newResource s maxCount =+newResource s count = Simulation $ \r ->- do countRef <- newIORef maxCount+ do when (count < 0) $+ error $+ "The resource count cannot be negative: " +++ "newResource."+ countRef <- newIORef count waitList <- invokeSimulation r $ newStrategyQueue s return Resource { resourceStrategy = s,- resourceMaxCount = maxCount,+ resourceMaxCount = Just count, resourceCountRef = countRef, resourceWaitList = waitList } --- | Create a new resource with the specified queue strategy, maximum and initial count.-newResourceWithCount :: QueueStrategy s q- => s- -- ^ the strategy for managing the queuing requests- -> Int- -- ^ the maximum count of the resource- -> Int- -- ^ the initial count of the resource- -> Simulation (Resource s q)-newResourceWithCount s maxCount count = do- when (count < 0) $- error $- "The resource count cannot be negative: " ++- "newResourceWithCount."- when (count > maxCount) $- error $- "The resource count cannot be greater than " ++- "its maximum value: newResourceWithCount."+-- | Create a new resource with the specified queue strategy, initial and maximum counts,+-- where 'Nothing' means that the resource has no upper bound.+newResourceWithMaxCount :: QueueStrategy s q+ => s+ -- ^ the strategy for managing the queuing requests+ -> Int+ -- ^ the initial count of the resource+ -> Maybe Int+ -- ^ the maximum count of the resource, which can be indefinite+ -> Simulation (Resource s q)+newResourceWithMaxCount s count maxCount = Simulation $ \r ->- do countRef <- newIORef count- waitList <- invokeSimulation r $ newStrategyQueue s- return Resource { resourceStrategy = s,- resourceMaxCount = maxCount,- resourceCountRef = countRef,- resourceWaitList = waitList }+ do when (count < 0) $+ error $+ "The resource count cannot be negative: " +++ "newResourceWithMaxCount."+ case maxCount of+ Just maxCount | count > maxCount ->+ error $+ "The resource count cannot be greater than " +++ "its maximum value: newResourceWithMaxCount."+ _ ->+ return ()+ countRef <- newIORef count+ waitList <- invokeSimulation r $ newStrategyQueue s+ return Resource { resourceStrategy = s,+ resourceMaxCount = maxCount,+ resourceCountRef = countRef,+ resourceWaitList = waitList } -- | Return the current count of the resource. resourceCount :: Resource s q -> Event Int@@ -109,8 +219,9 @@ Event $ \p -> do a <- readIORef (resourceCountRef r) if a == 0 - then invokeEvent p $- strategyEnqueue (resourceStrategy r) (resourceWaitList r) c+ then do c <- invokeEvent p $ contFreeze c+ invokeEvent p $+ strategyEnqueue (resourceStrategy r) (resourceWaitList r) c else do let a' = a - 1 a' `seq` writeIORef (resourceCountRef r) a' invokeEvent p $ resumeCont c ()@@ -118,10 +229,10 @@ -- | Request with the priority for the resource decreasing its count -- in case of success, otherwise suspending the discontinuous process -- until some other process releases the resource.-requestResourceWithPriority :: PriorityQueueStrategy s q+requestResourceWithPriority :: PriorityQueueStrategy s q p => Resource s q -- ^ the requested resource- -> Double+ -> p -- ^ the priority -> Process () requestResourceWithPriority r priority =@@ -130,29 +241,9 @@ Event $ \p -> do a <- readIORef (resourceCountRef r) if a == 0 - then invokeEvent p $- strategyEnqueueWithPriority (resourceStrategy r) (resourceWaitList r) priority c- else do let a' = a - 1- a' `seq` writeIORef (resourceCountRef r) a'- invokeEvent p $ resumeCont c ()---- | Request with the dynamic priority for the resource decreasing its count--- in case of success, otherwise suspending the discontinuous process--- until some other process releases the resource.-requestResourceWithDynamicPriority :: DynamicPriorityQueueStrategy s q- => Resource s q- -- ^ the requested resource- -> Event Double- -- ^ the dynamic priority- -> Process ()-requestResourceWithDynamicPriority r priority =- Process $ \pid ->- Cont $ \c ->- Event $ \p ->- do a <- readIORef (resourceCountRef r)- if a == 0 - then invokeEvent p $- strategyEnqueueWithDynamicPriority (resourceStrategy r) (resourceWaitList r) priority c+ then do c <- invokeEvent p $ contFreeze c+ invokeEvent p $+ strategyEnqueueWithPriority (resourceStrategy r) (resourceWaitList r) priority c else do let a' = a - 1 a' `seq` writeIORef (resourceCountRef r) a' invokeEvent p $ resumeCont c ()@@ -180,23 +271,25 @@ Event $ \p -> do a <- readIORef (resourceCountRef r) let a' = a + 1- when (a' > resourceMaxCount r) $- error $- "The resource count cannot be greater than " ++- "its maximum value: releaseResourceWithinEvent."+ case resourceMaxCount r of+ Just maxCount | a' > maxCount ->+ error $+ "The resource count cannot be greater than " +++ "its maximum value: releaseResourceWithinEvent."+ _ ->+ return () f <- invokeEvent p $ strategyQueueNull (resourceStrategy r) (resourceWaitList r) if f then a' `seq` writeIORef (resourceCountRef r) a' else do c <- invokeEvent p $ strategyDequeue (resourceStrategy r) (resourceWaitList r)- invokeEvent p $ enqueueEvent (pointTime p) $- Event $ \p ->- do z <- contCanceled c- if z- then do invokeEvent p $ releaseResourceWithinEvent r- invokeEvent p $ resumeCont c ()- else invokeEvent p $ resumeCont c ()+ c <- invokeEvent p c+ case c of+ Nothing ->+ invokeEvent p $ releaseResourceWithinEvent r+ Just c ->+ invokeEvent p $ enqueueEvent (pointTime p) $ resumeCont c () -- | Try to request for the resource decreasing its count in case of success -- and returning 'True' in the 'Event' monad; otherwise, returning 'False'.@@ -214,10 +307,6 @@ -- | Acquire the resource, perform some action and safely release the resource -- in the end, even if the 'IOException' was raised within the action. --- The process identifier must be created with support of exception --- handling, i.e. with help of function 'newProcessIdWithCatch'. Unfortunately,--- such processes are slower than those that are created with help of--- other function 'newProcessId'. usingResource :: EnqueueStrategy s q => Resource s q -- ^ the resource we are going to request for and then release in the end@@ -231,15 +320,12 @@ -- | Acquire the resource with the specified priority, perform some action and -- safely release the resource in the end, even if the 'IOException' was raised--- within the action. The process identifier must be created with support of exception --- handling, i.e. with help of function 'newProcessIdWithCatch'. Unfortunately,--- such processes are slower than those that are created with help of--- other function 'newProcessId'.-usingResourceWithPriority :: PriorityQueueStrategy s q+-- within the action.+usingResourceWithPriority :: PriorityQueueStrategy s q p => Resource s q -- ^ the resource we are going to request for and then -- release in the end- -> Double+ -> p -- ^ the priority -> Process a -- ^ the action we are going to apply having the resource@@ -247,24 +333,4 @@ -- ^ the result of the action usingResourceWithPriority r priority m = do requestResourceWithPriority r priority- finallyProcess m $ releaseResource r---- | Acquire the resource with the dynamic priority, perform some action and--- safely release the resource in the end, even if the 'IOException' was raised--- within the action. The process identifier must be created with support of exception --- handling, i.e. with help of function 'newProcessIdWithCatch'. Unfortunately,--- such processes are slower than those that are created with help of--- other function 'newProcessId'.-usingResourceWithDynamicPriority :: DynamicPriorityQueueStrategy s q- => Resource s q- -- ^ the resource we are going to request for and then- -- release in the end- -> Event Double- -- ^ the dynamic priority- -> Process a- -- ^ the action we are going to apply having the resource- -> Process a- -- ^ the result of the action-usingResourceWithDynamicPriority r priority m =- do requestResourceWithDynamicPriority r priority finallyProcess m $ releaseResource r
+ Simulation/Aivika/Server.hs view
@@ -0,0 +1,492 @@++-- |+-- Module : Simulation.Aivika.Server+-- Copyright : Copyright (c) 2009-2013, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.6.3+--+-- It models the server that prodives a service.+module Simulation.Aivika.Server+ (-- * Server+ Server,+ newServer,+ newServerWithState,+ -- * Processing+ serverProcessor,+ -- * Server Properties and Activities+ serverInitState,+ serverState,+ serverTotalInputTime,+ serverTotalProcessingTime,+ serverTotalOutputTime,+ serverInputTime,+ serverProcessingTime,+ serverOutputTime,+ serverInputTimeFactor,+ serverProcessingTimeFactor,+ serverOutputTimeFactor,+ -- * Summary+ serverSummary,+ -- * Derived Signals for Properties+ serverStateChanged,+ serverStateChanged_,+ serverTotalInputTimeChanged,+ serverTotalInputTimeChanged_,+ serverTotalProcessingTimeChanged,+ serverTotalProcessingTimeChanged_,+ serverTotalOutputTimeChanged,+ serverTotalOutputTimeChanged_,+ serverInputTimeChanged,+ serverInputTimeChanged_,+ serverProcessingTimeChanged,+ serverProcessingTimeChanged_,+ serverOutputTimeChanged,+ serverOutputTimeChanged_,+ serverInputTimeFactorChanged,+ serverInputTimeFactorChanged_,+ serverProcessingTimeFactorChanged,+ serverProcessingTimeFactorChanged_,+ serverOutputTimeFactorChanged,+ serverOutputTimeFactorChanged_,+ -- * Basic Signals+ serverInputReceived,+ serverTaskProcessed,+ serverOutputProvided,+ -- * Overall Signal+ serverChanged_) where++import Data.IORef+import Data.Monoid+import Control.Monad.Trans++import Simulation.Aivika.Simulation+import Simulation.Aivika.Dynamics+import Simulation.Aivika.Internal.Event+import Simulation.Aivika.Internal.Signal+import Simulation.Aivika.Resource+import Simulation.Aivika.Cont+import Simulation.Aivika.Process+import Simulation.Aivika.Processor+import Simulation.Aivika.Stream+import Simulation.Aivika.Statistics++-- | It models a server that takes @a@ and provides @b@ having state @s@.+data Server s a b =+ Server { serverInitState :: s,+ -- ^ The initial state of the server.+ serverStateRef :: IORef s,+ -- ^ The current state of the server.+ serverProcess :: (s, a) -> Process (s, b),+ -- ^ Provide @b@ by specified @a@.+ serverTotalInputTimeRef :: IORef Double,+ -- ^ The counted total time spent in awating the input.+ serverTotalProcessingTimeRef :: IORef Double,+ -- ^ The counted total time spent to process the input and prepare the output.+ serverTotalOutputTimeRef :: IORef Double,+ -- ^ The counted total time spent for delivering the output.+ serverInputTimeRef :: IORef (SamplingStats Double),+ -- ^ The statistics for the time spent in awaiting the input.+ serverProcessingTimeRef :: IORef (SamplingStats Double),+ -- ^ The statistics for the time spent to process the input and prepare the output.+ serverOutputTimeRef :: IORef (SamplingStats Double),+ -- ^ The statistics for the time spent for delivering the output.+ serverInputReceivedSource :: SignalSource a,+ -- ^ A signal raised when the server recieves a new input to process.+ serverTaskProcessedSource :: SignalSource (a, b),+ -- ^ A signal raised when the input is processed and+ -- the output is prepared for deliverying.+ serverOutputProvidedSource :: SignalSource (a, b)+ -- ^ A signal raised when the server has supplied the output.+ }++-- | Create a new server that can provide output @b@ by input @a@.+-- Also it returns the corresponded processor that being applied+-- updates the server state.+newServer :: (a -> Process b)+ -- ^ provide an output by the specified input+ -> Simulation (Server () a b)+newServer provide =+ newServerWithState () $ \(s, a) ->+ do b <- provide a+ return (s, b)++-- | Create a new server that can provide output @b@ by input @a@+-- starting from state @s@. Also it returns the corresponded processor+-- that being applied updates the server state.+newServerWithState :: s+ -- ^ the initial state+ -> ((s, a) -> Process (s, b))+ -- ^ provide an output by the specified input+ -- and update the state + -> Simulation (Server s a b)+newServerWithState state provide =+ do r0 <- liftIO $ newIORef state+ r1 <- liftIO $ newIORef 0+ r2 <- liftIO $ newIORef 0+ r3 <- liftIO $ newIORef 0+ r4 <- liftIO $ newIORef emptySamplingStats+ r5 <- liftIO $ newIORef emptySamplingStats+ r6 <- liftIO $ newIORef emptySamplingStats+ s1 <- newSignalSource+ s2 <- newSignalSource+ s3 <- newSignalSource+ let server = Server { serverInitState = state,+ serverStateRef = r0,+ serverProcess = provide,+ serverTotalInputTimeRef = r1,+ serverTotalProcessingTimeRef = r2,+ serverTotalOutputTimeRef = r3,+ serverInputTimeRef = r4,+ serverProcessingTimeRef = r5,+ serverOutputTimeRef = r6,+ serverInputReceivedSource = s1,+ serverTaskProcessedSource = s2,+ serverOutputProvidedSource = s3 }+ return server++-- | Return a processor for the specified server.+--+-- The processor updates the internal state of the server. The usual case is when +-- the processor is applied only once in a chain of data processing. Otherwise; +-- every time the processor is used, the state of the server changes. +serverProcessor :: Server s a b -> Processor a b+serverProcessor server =+ Processor $ \xs -> loop (serverInitState server) Nothing xs+ where+ loop s r xs =+ Cons $+ do t0 <- liftDynamics time+ liftEvent $+ case r of+ Nothing -> return ()+ Just (t', a', b') ->+ do liftIO $+ do modifyIORef (serverTotalOutputTimeRef server) (+ (t0 - t'))+ modifyIORef (serverOutputTimeRef server) $+ addSamplingStats (t0 - t')+ triggerSignal (serverOutputProvidedSource server) (a', b')+ -- get input+ (a, xs') <- runStream xs+ t1 <- liftDynamics time+ liftEvent $+ do liftIO $+ do modifyIORef (serverTotalInputTimeRef server) (+ (t1 - t0))+ modifyIORef (serverInputTimeRef server) $+ addSamplingStats (t1 - t0)+ triggerSignal (serverInputReceivedSource server) a+ -- provide the service+ (s', b) <- serverProcess server (s, a)+ t2 <- liftDynamics time+ liftEvent $+ do liftIO $+ do writeIORef (serverStateRef server) s'+ modifyIORef (serverTotalProcessingTimeRef server) (+ (t2 - t1))+ modifyIORef (serverProcessingTimeRef server) $+ addSamplingStats (t2 - t1)+ triggerSignal (serverTaskProcessedSource server) (a, b)+ return (b, loop s' (Just $ (t2, a, b)) xs')++-- | Return the current state of the server.+--+-- See also 'serverStateChanged' and 'serverStateChanged_'.+serverState :: Server s a b -> Event s+serverState server =+ Event $ \p -> readIORef (serverStateRef server)+ +-- | Signal when the 'serverState' property value has changed.+serverStateChanged :: Server s a b -> Signal s+serverStateChanged server =+ mapSignalM (const $ serverState server) (serverStateChanged_ server)+ +-- | Signal when the 'serverState' property value has changed.+serverStateChanged_ :: Server s a b -> Signal ()+serverStateChanged_ server =+ mapSignal (const ()) (serverTaskProcessed server)++-- | Return the counted total time spent by the server in awaiting the input.+--+-- The value returned changes discretely and it is usually delayed relative+-- to the current simulation time.+--+-- See also 'serverTotalInputTimeChanged' and 'serverTotalInputTimeChanged_'.+serverTotalInputTime :: Server s a b -> Event Double+serverTotalInputTime server =+ Event $ \p -> readIORef (serverTotalInputTimeRef server)+ +-- | Signal when the 'serverTotalInputTime' property value has changed.+serverTotalInputTimeChanged :: Server s a b -> Signal Double+serverTotalInputTimeChanged server =+ mapSignalM (const $ serverTotalInputTime server) (serverTotalInputTimeChanged_ server)+ +-- | Signal when the 'serverTotalInputTime' property value has changed.+serverTotalInputTimeChanged_ :: Server s a b -> Signal ()+serverTotalInputTimeChanged_ server =+ mapSignal (const ()) (serverInputReceived server)++-- | Return the counted total time spent by the server to process all tasks.+--+-- The value returned changes discretely and it is usually delayed relative+-- to the current simulation time.+--+-- See also 'serverTotalProcessingTimeChanged' and 'serverTotalProcessingTimeChanged_'.+serverTotalProcessingTime :: Server s a b -> Event Double+serverTotalProcessingTime server =+ Event $ \p -> readIORef (serverTotalProcessingTimeRef server)+ +-- | Signal when the 'serverTotalProcessingTime' property value has changed.+serverTotalProcessingTimeChanged :: Server s a b -> Signal Double+serverTotalProcessingTimeChanged server =+ mapSignalM (const $ serverTotalProcessingTime server) (serverTotalProcessingTimeChanged_ server)+ +-- | Signal when the 'serverTotalProcessingTime' property value has changed.+serverTotalProcessingTimeChanged_ :: Server s a b -> Signal ()+serverTotalProcessingTimeChanged_ server =+ mapSignal (const ()) (serverTaskProcessed server)++-- | Return the counted total time when the server was in the lock state trying+-- to deliver the output.+--+-- The value returned changes discretely and it is usually delayed relative+-- to the current simulation time.+--+-- See also 'serverTotalOutputTimeChanged' and 'serverTotalOutputTimeChanged_'.+serverTotalOutputTime :: Server s a b -> Event Double+serverTotalOutputTime server =+ Event $ \p -> readIORef (serverTotalOutputTimeRef server)+ +-- | Signal when the 'serverTotalOutputTime' property value has changed.+serverTotalOutputTimeChanged :: Server s a b -> Signal Double+serverTotalOutputTimeChanged server =+ mapSignalM (const $ serverTotalOutputTime server) (serverTotalOutputTimeChanged_ server)+ +-- | Signal when the 'serverTotalOutputTime' property value has changed.+serverTotalOutputTimeChanged_ :: Server s a b -> Signal ()+serverTotalOutputTimeChanged_ server =+ mapSignal (const ()) (serverOutputProvided server)++-- | Return the statistics of the time spent by the server in awaiting the input.+--+-- The value returned changes discretely and it is usually delayed relative+-- to the current simulation time.+--+-- See also 'serverInputTimeChanged' and 'serverInputTimeChanged_'.+serverInputTime :: Server s a b -> Event (SamplingStats Double)+serverInputTime server =+ Event $ \p -> readIORef (serverInputTimeRef server)+ +-- | Signal when the 'serverInputTime' property value has changed.+serverInputTimeChanged :: Server s a b -> Signal (SamplingStats Double)+serverInputTimeChanged server =+ mapSignalM (const $ serverInputTime server) (serverInputTimeChanged_ server)+ +-- | Signal when the 'serverInputTime' property value has changed.+serverInputTimeChanged_ :: Server s a b -> Signal ()+serverInputTimeChanged_ server =+ mapSignal (const ()) (serverInputReceived server)++-- | Return the statistics of the time spent by the server to process the tasks.+--+-- The value returned changes discretely and it is usually delayed relative+-- to the current simulation time.+--+-- See also 'serverProcessingTimeChanged' and 'serverProcessingTimeChanged_'.+serverProcessingTime :: Server s a b -> Event (SamplingStats Double)+serverProcessingTime server =+ Event $ \p -> readIORef (serverProcessingTimeRef server)+ +-- | Signal when the 'serverProcessingTime' property value has changed.+serverProcessingTimeChanged :: Server s a b -> Signal (SamplingStats Double)+serverProcessingTimeChanged server =+ mapSignalM (const $ serverProcessingTime server) (serverProcessingTimeChanged_ server)+ +-- | Signal when the 'serverProcessingTime' property value has changed.+serverProcessingTimeChanged_ :: Server s a b -> Signal ()+serverProcessingTimeChanged_ server =+ mapSignal (const ()) (serverTaskProcessed server)++-- | Return the statistics of the time when the server was in the lock state trying+-- to deliver the output. +--+-- The value returned changes discretely and it is usually delayed relative+-- to the current simulation time.+--+-- See also 'serverOutputTimeChanged' and 'serverOutputTimeChanged_'.+serverOutputTime :: Server s a b -> Event (SamplingStats Double)+serverOutputTime server =+ Event $ \p -> readIORef (serverOutputTimeRef server)+ +-- | Signal when the 'serverOutputTime' property value has changed.+serverOutputTimeChanged :: Server s a b -> Signal (SamplingStats Double)+serverOutputTimeChanged server =+ mapSignalM (const $ serverOutputTime server) (serverOutputTimeChanged_ server)+ +-- | Signal when the 'serverOutputTime' property value has changed.+serverOutputTimeChanged_ :: Server s a b -> Signal ()+serverOutputTimeChanged_ server =+ mapSignal (const ()) (serverOutputProvided server)++-- | It returns the factor changing from 0 to 1, which estimates how often+-- the server was awaiting for the next input task.+--+-- This factor is calculated as+--+-- @+-- totalInputTime \/ (totalInputTime + totalProcessingTime + totalOutputTime)+-- @+--+-- As before in this module, the value returned changes discretely and+-- it is usually delayed relative to the current simulation time.+--+-- See also 'serverInputTimeFactorChanged' and 'serverInputTimeFactorChanged_'.+serverInputTimeFactor :: Server s a b -> Event Double+serverInputTimeFactor server =+ Event $ \p ->+ do x1 <- readIORef (serverTotalInputTimeRef server)+ x2 <- readIORef (serverTotalProcessingTimeRef server)+ x3 <- readIORef (serverTotalOutputTimeRef server)+ return (x1 / (x1 + x2 + x3))+ +-- | Signal when the 'serverInputTimeFactor' property value has changed.+serverInputTimeFactorChanged :: Server s a b -> Signal Double+serverInputTimeFactorChanged server =+ mapSignalM (const $ serverInputTimeFactor server) (serverInputTimeFactorChanged_ server)+ +-- | Signal when the 'serverInputTimeFactor' property value has changed.+serverInputTimeFactorChanged_ :: Server s a b -> Signal ()+serverInputTimeFactorChanged_ server =+ mapSignal (const ()) (serverInputReceived server) <>+ mapSignal (const ()) (serverTaskProcessed server) <>+ mapSignal (const ()) (serverOutputProvided server)++-- | It returns the factor changing from 0 to 1, which estimates how often+-- the server was busy with direct processing its tasks.+--+-- This factor is calculated as+--+-- @+-- totalProcessingTime \/ (totalInputTime + totalProcessingTime + totalOutputTime)+-- @+--+-- As before in this module, the value returned changes discretely and+-- it is usually delayed relative to the current simulation time.+--+-- See also 'serverProcessingTimeFactorChanged' and 'serverProcessingTimeFactorChanged_'.+serverProcessingTimeFactor :: Server s a b -> Event Double+serverProcessingTimeFactor server =+ Event $ \p ->+ do x1 <- readIORef (serverTotalInputTimeRef server)+ x2 <- readIORef (serverTotalProcessingTimeRef server)+ x3 <- readIORef (serverTotalOutputTimeRef server)+ return (x2 / (x1 + x2 + x3))+ +-- | Signal when the 'serverProcessingTimeFactor' property value has changed.+serverProcessingTimeFactorChanged :: Server s a b -> Signal Double+serverProcessingTimeFactorChanged server =+ mapSignalM (const $ serverProcessingTimeFactor server) (serverProcessingTimeFactorChanged_ server)+ +-- | Signal when the 'serverProcessingTimeFactor' property value has changed.+serverProcessingTimeFactorChanged_ :: Server s a b -> Signal ()+serverProcessingTimeFactorChanged_ server =+ mapSignal (const ()) (serverInputReceived server) <>+ mapSignal (const ()) (serverTaskProcessed server) <>+ mapSignal (const ()) (serverOutputProvided server)++-- | It returns the factor changing from 0 to 1, which estimates how often+-- the server was locked trying to deliver the output after the task is finished.+--+-- This factor is calculated as+--+-- @+-- totalOutputTime \/ (totalInputTime + totalProcessingTime + totalOutputTime)+-- @+--+-- As before in this module, the value returned changes discretely and+-- it is usually delayed relative to the current simulation time.+--+-- See also 'serverOutputTimeFactorChanged' and 'serverOutputTimeFactorChanged_'.+serverOutputTimeFactor :: Server s a b -> Event Double+serverOutputTimeFactor server =+ Event $ \p ->+ do x1 <- readIORef (serverTotalInputTimeRef server)+ x2 <- readIORef (serverTotalProcessingTimeRef server)+ x3 <- readIORef (serverTotalOutputTimeRef server)+ return (x3 / (x1 + x2 + x3))+ +-- | Signal when the 'serverOutputTimeFactor' property value has changed.+serverOutputTimeFactorChanged :: Server s a b -> Signal Double+serverOutputTimeFactorChanged server =+ mapSignalM (const $ serverOutputTimeFactor server) (serverOutputTimeFactorChanged_ server)+ +-- | Signal when the 'serverOutputTimeFactor' property value has changed.+serverOutputTimeFactorChanged_ :: Server s a b -> Signal ()+serverOutputTimeFactorChanged_ server =+ mapSignal (const ()) (serverInputReceived server) <>+ mapSignal (const ()) (serverTaskProcessed server) <>+ mapSignal (const ()) (serverOutputProvided server)++-- | Raised when the server receives a new input task.+serverInputReceived :: Server s a b -> Signal a+serverInputReceived = publishSignal . serverInputReceivedSource++-- | Raised when the server has just processed the task.+serverTaskProcessed :: Server s a b -> Signal (a, b)+serverTaskProcessed = publishSignal . serverTaskProcessedSource++-- | Raised when the server has just delivered the output.+serverOutputProvided :: Server s a b -> Signal (a, b)+serverOutputProvided = publishSignal . serverOutputProvidedSource++-- | Signal whenever any property of the server changes.+serverChanged_ :: Server s a b -> Signal ()+serverChanged_ server =+ mapSignal (const ()) (serverInputReceived server) <>+ mapSignal (const ()) (serverTaskProcessed server) <>+ mapSignal (const ()) (serverOutputProvided server)++-- | Return the summary for the server with desciption of its+-- properties and activities using the specified indent.+serverSummary :: Server s a b -> Int -> Event ShowS+serverSummary server indent =+ Event $ \p ->+ do tx1 <- readIORef (serverTotalInputTimeRef server)+ tx2 <- readIORef (serverTotalProcessingTimeRef server)+ tx3 <- readIORef (serverTotalOutputTimeRef server)+ let xf1 = tx1 / (tx1 + tx2 + tx3)+ xf2 = tx2 / (tx1 + tx2 + tx3)+ xf3 = tx3 / (tx1 + tx2 + tx3)+ xs1 <- readIORef (serverInputTimeRef server)+ xs2 <- readIORef (serverProcessingTimeRef server)+ xs3 <- readIORef (serverOutputTimeRef server)+ let tab = replicate indent ' '+ return $+ showString tab .+ showString "total input time (in awaiting the input) = " . shows tx1 .+ showString "\n" .+ showString tab .+ showString "total processing time = " . shows tx2 .+ showString "\n" .+ showString tab .+ showString "total output time (to deliver the output) = " . shows tx3 .+ showString "\n\n" .+ showString tab .+ showString "input time factor (from 0 to 1) = " . shows xf1 .+ showString "\n" .+ showString tab .+ showString "processing time factor (from 0 to 1) = " . shows xf2 .+ showString "\n" .+ showString tab .+ showString "output time factor (from 0 to 1) = " . shows xf3 .+ showString "\n\n" .+ showString tab .+ showString "input time:\n\n" .+ samplingStatsSummary xs1 (2 + indent) .+ showString "\n\n" .+ showString tab .+ showString "processing time:\n\n" .+ samplingStatsSummary xs2 (2 + indent) .+ showString "\n\n" .+ showString tab .+ showString "output time:\n\n" .+ samplingStatsSummary xs3 (2 + indent)
Simulation/Aivika/Signal.hs view
@@ -13,13 +13,14 @@ -- the handlers. -- module Simulation.Aivika.Signal- (Signal(..),+ (-- * Handling and Triggering Signal+ Signal(..), handleSignal_, SignalSource, newSignalSource, publishSignal, triggerSignal,- awaitSignal,+ -- * Useful Combinators mapSignal, mapSignalM, apSignal,@@ -30,108 +31,20 @@ merge3Signals, merge4Signals, merge5Signals,+ -- * Creating Signal in Time Points newSignalInTimes, newSignalInIntegTimes, newSignalInStartTime, newSignalInStopTime,+ -- * Signal History SignalHistory, signalHistorySignal, newSignalHistory,- readSignalHistory) where--import Data.IORef-import Data.Array--import Control.Monad-import Control.Monad.Trans+ readSignalHistory,+ -- * Signalable Computations+ Signalable(..),+ signalableChanged,+ emptySignalable,+ appendSignalable) where -import Simulation.Aivika.Internal.Specs import Simulation.Aivika.Internal.Signal-import Simulation.Aivika.Internal.Simulation-import Simulation.Aivika.Internal.Event-import Simulation.Aivika.Internal.Cont-import Simulation.Aivika.Internal.Process--import qualified Simulation.Aivika.Vector as V-import qualified Simulation.Aivika.Vector.Unboxed as UV---- | Await the signal.-awaitSignal :: Signal a -> Process a-awaitSignal signal =- Process $ \pid ->- Cont $ \c ->- Event $ \p ->- do r <- newIORef Nothing- h <- invokeEvent p $- handleSignal signal $ - \a -> Event $ - \p -> do x <- readIORef r- case x of- Nothing ->- error "The signal was lost: awaitSignal."- Just x ->- do invokeEvent p x- invokeEvent p $ resumeCont c a- writeIORef r $ Just h- --- | Represents the history of the signal values.-data SignalHistory a =- SignalHistory { signalHistorySignal :: Signal a, - -- ^ The signal for which the history is created.- signalHistoryTimes :: UV.Vector Double,- signalHistoryValues :: V.Vector a }---- | Create a history of the signal values.-newSignalHistory :: Signal a -> Event (SignalHistory a)-newSignalHistory signal =- do ts <- liftIO UV.newVector- xs <- liftIO V.newVector- handleSignal_ signal $ \a ->- Event $ \p ->- do liftIO $ UV.appendVector ts (pointTime p)- liftIO $ V.appendVector xs a- return SignalHistory { signalHistorySignal = signal,- signalHistoryTimes = ts,- signalHistoryValues = xs }- --- | Read the history of signal values.-readSignalHistory :: SignalHistory a -> Event (Array Int Double, Array Int a)-readSignalHistory history =- do xs <- liftIO $ UV.freezeVector (signalHistoryTimes history)- ys <- liftIO $ V.freezeVector (signalHistoryValues history)- return (xs, ys) - --- | Trigger the signal with the current time.-triggerSignalWithCurrentTime :: SignalSource Double -> Event ()-triggerSignalWithCurrentTime s =- Event $ \p -> invokeEvent p $ triggerSignal s (pointTime p)---- | Return a signal that is triggered in the specified time points.-newSignalInTimes :: [Double] -> Event (Signal Double)-newSignalInTimes xs =- do s <- liftSimulation newSignalSource- enqueueEventWithTimes xs $ triggerSignalWithCurrentTime s- return $ publishSignal s- --- | Return a signal that is triggered in the integration time points.--- It should be called with help of 'runEventInStartTime'.-newSignalInIntegTimes :: Event (Signal Double)-newSignalInIntegTimes =- do s <- liftSimulation newSignalSource- enqueueEventWithIntegTimes $ triggerSignalWithCurrentTime s- return $ publishSignal s- --- | Return a signal that is triggered in the start time.--- It should be called with help of 'runEventInStartTime'.-newSignalInStartTime :: Event (Signal Double)-newSignalInStartTime =- do s <- liftSimulation newSignalSource- enqueueEventWithStartTime $ triggerSignalWithCurrentTime s- return $ publishSignal s---- | Return a signal that is triggered in the stop time.-newSignalInStopTime :: Event (Signal Double)-newSignalInStopTime =- do s <- liftSimulation newSignalSource- enqueueEventWithStopTime $ triggerSignalWithCurrentTime s- return $ publishSignal s
Simulation/Aivika/Simulation.hs view
@@ -19,9 +19,7 @@ catchSimulation, finallySimulation, throwSimulation,- -- * Utilities- simulationIndex,- simulationCount,- simulationSpecs) where+ -- * Memoization+ memoSimulation) where import Simulation.Aivika.Internal.Simulation
Simulation/Aivika/Statistics.hs view
@@ -11,20 +11,22 @@ -- module Simulation.Aivika.Statistics- (SamplingStats(..),+ (-- * Simple Statistics+ SamplingStats(..), SamplingData(..), samplingStatsVariance, samplingStatsDeviation,+ samplingStatsSummary, returnSamplingStats, listSamplingStats, fromIntSamplingStats,- showSamplingStats,+ -- * Timing Statistics TimingStats(..), TimingData(..), timingStatsDeviation,+ timingStatsSummary, returnTimingStats,- fromIntTimingStats,- showTimingStats) where + fromIntTimingStats) where import Data.Monoid @@ -54,7 +56,7 @@ samplingStatsMean2 :: !Double -- ^ The average square value. }- deriving (Eq, Ord, Show)+ deriving (Eq, Ord) -- | Specifies data type from which values we can gather the statistics. class SamplingData a where @@ -182,24 +184,36 @@ stats { samplingStatsMin = fromIntegral $ samplingStatsMin stats, samplingStatsMax = fromIntegral $ samplingStatsMax stats } --- | Show the summary of the statistics with the specified indent. -showSamplingStats :: (Show a) => SamplingStats a -> Int -> ShowS-showSamplingStats stats indent =+-- | Show the summary of the statistics. +showSamplingStats :: (Show a) => SamplingStats a -> ShowS+showSamplingStats stats =+ showString "count = " . shows (samplingStatsCount stats) . + showString ", mean = " . shows (samplingStatsMean stats) . + showString ", std = " . shows (samplingStatsDeviation stats) . + showString ", min = " . shows (samplingStatsMin stats) . + showString ", max = " . shows (samplingStatsMax stats)++instance Show a => Show (SamplingStats a) where+ showsPrec prec = showSamplingStats++-- | Show the summary of the statistics using the specified indent. +samplingStatsSummary :: (Show a) => SamplingStats a -> Int -> ShowS+samplingStatsSummary stats indent = let tab = replicate indent ' ' in showString tab .- showString "count = " . shows (samplingStatsCount stats) . - showString "\n" . + showString "count = " . shows (samplingStatsCount stats) .+ showString "\n" . showString tab .- showString "mean = " . shows (samplingStatsMean stats) . - showString "\n" . + showString "mean = " . shows (samplingStatsMean stats) . + showString "\n" . showString tab .- showString "deviation = " . shows (samplingStatsDeviation stats) . + showString "std = " . shows (samplingStatsDeviation stats) . showString "\n" . showString tab .- showString "minimum = " . shows (samplingStatsMin stats) . + showString "min = " . shows (samplingStatsMin stats) . showString "\n" . showString tab .- showString "maximum = " . shows (samplingStatsMax stats)+ showString "max = " . shows (samplingStatsMax stats) -- | This is the timing statistics where data are bound to the time. data TimingStats a =@@ -221,7 +235,7 @@ -- ^ Return the sum of values. timingStatsSum2 :: !Double -- ^ Return the sum of square values.- } deriving (Eq, Ord, Show)+ } deriving (Eq, Ord) -- | Defines the data type from which values we can gather the timing statistics. class TimingData a where @@ -248,8 +262,8 @@ timingStatsMaxTime = (-1) / 0, timingStatsStartTime = 1 / 0, timingStatsLastTime = (-1) / 0,- timingStatsSum = 0,- timingStatsSum2 = 0 }+ timingStatsSum = 0 / 0,+ timingStatsSum2 = 0 / 0 } addTimingStats = addTimingStatsGeneric timingStatsMean = timingStatsMeanGeneric@@ -265,8 +279,8 @@ timingStatsMaxTime = (-1) / 0, timingStatsStartTime = 1 / 0, timingStatsLastTime = (-1) / 0,- timingStatsSum = 0,- timingStatsSum2 = 0 }+ timingStatsSum = 0 / 0,+ timingStatsSum2 = 0 / 0 } addTimingStats = addTimingStatsGeneric timingStatsMean = timingStatsMeanGeneric@@ -312,22 +326,26 @@ sumX2 = sumX2' + (t - t') * x * x timingStatsMeanGeneric :: ConvertableToDouble a => TimingStats a -> Double-timingStatsMeanGeneric stats - | t1 > t0 = sumX / (t1 - t0)- | otherwise = minX- where t0 = timingStatsStartTime stats- t1 = timingStatsLastTime stats- sumX = timingStatsSum stats- minX = convertToDouble $ timingStatsMin stats+timingStatsMeanGeneric stats+ | count == 0 = 0 / 0+ | t1 > t0 = sumX / (t1 - t0)+ | otherwise = minX+ where t0 = timingStatsStartTime stats+ t1 = timingStatsLastTime stats+ sumX = timingStatsSum stats+ minX = convertToDouble $ timingStatsMin stats+ count = timingStatsCount stats timingStatsMean2Generic :: ConvertableToDouble a => TimingStats a -> Double timingStatsMean2Generic stats- | t1 > t0 = sumX2 / (t1 - t0)- | otherwise = minX * minX+ | count == 0 = 0 / 0+ | t1 > t0 = sumX2 / (t1 - t0)+ | otherwise = minX * minX where t0 = timingStatsStartTime stats t1 = timingStatsLastTime stats sumX2 = timingStatsSum2 stats minX = convertToDouble $ timingStatsMin stats+ count = timingStatsCount stats timingStatsVarianceGeneric :: ConvertableToDouble a => TimingStats a -> Double timingStatsVarianceGeneric stats = ex2 - ex * ex@@ -348,27 +366,44 @@ stats { timingStatsMin = fromIntegral $ timingStatsMin stats, timingStatsMax = fromIntegral $ timingStatsMax stats } --- | Show the summary of the statistics with the specified indent. -showTimingStats :: (Show a, TimingData a) => TimingStats a -> Int -> ShowS-showTimingStats stats indent =+-- | Show the summary of the statistics. +showTimingStats :: (Show a, TimingData a) => TimingStats a -> ShowS+showTimingStats stats =+ showString "count = " . shows (timingStatsCount stats) . + showString ", mean = " . shows (timingStatsMean stats) . + showString ", std = " . shows (timingStatsDeviation stats) . + showString ", min = " . shows (timingStatsMin stats) . + showString " (t = " . shows (timingStatsMinTime stats) .+ showString "), max = " . shows (timingStatsMax stats) .+ showString " (t = " . shows (timingStatsMaxTime stats) .+ showString "), t in [" . shows (timingStatsStartTime stats) .+ showString ", " . shows (timingStatsLastTime stats) .+ showString "]"++instance (Show a, TimingData a) => Show (TimingStats a) where+ showsPrec prec = showTimingStats++-- | Show the summary of the statistics using the specified indent. +timingStatsSummary :: (Show a, TimingData a) => TimingStats a -> Int -> ShowS+timingStatsSummary stats indent = let tab = replicate indent ' ' in showString tab .- showString "count = " . shows (timingStatsCount stats) . - showString "\n" . - showString tab .- showString "mean = " . shows (timingStatsMean stats) . - showString "\n" . - showString tab .- showString "deviation = " . shows (timingStatsDeviation stats) . + showString "count = " . shows (timingStatsCount stats) . showString "\n" . showString tab .- showString "minimum = " . shows (timingStatsMin stats) . - showString " at t = " . shows (timingStatsMinTime stats) .+ showString "mean = " . shows (timingStatsMean stats) . showString "\n" . showString tab .- showString "maximum = " . shows (timingStatsMax stats) .- showString " at t = " . shows (timingStatsMaxTime stats) .+ showString "std = " . shows (timingStatsDeviation stats) . showString "\n" .+ showString tab .+ showString "min = " . shows (timingStatsMin stats) . + showString " (t = " . shows (timingStatsMinTime stats) .+ showString ")\n" .+ showString tab .+ showString "max = " . shows (timingStatsMax stats) .+ showString " (t = " . shows (timingStatsMaxTime stats) .+ showString ")\n" . showString tab . showString "t in [" . shows (timingStatsStartTime stats) . showString ", " . shows (timingStatsLastTime stats) .
+ Simulation/Aivika/Stream.hs view
@@ -0,0 +1,438 @@++-- |+-- Module : Simulation.Aivika.Stream+-- Copyright : Copyright (c) 2009-2013, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.6.3+--+-- The infinite stream of data in time.+--+module Simulation.Aivika.Stream+ (-- * Stream Type+ Stream(..),+ -- * Merging and Splitting Stream+ emptyStream,+ mergeStreams,+ mergeQueuedStreams,+ mergePriorityStreams,+ concatStreams,+ concatQueuedStreams,+ concatPriorityStreams,+ splitStream,+ splitStreamQueuing,+ splitStreamPrioritising,+ -- * Specifying Identifier+ streamUsingId,+ -- * Memoizing, Zipping and Uzipping Stream+ memoStream,+ zipStreamSeq,+ zipStreamParallel,+ zip3StreamSeq,+ zip3StreamParallel,+ unzipStream,+ streamSeq,+ streamParallel,+ -- * Consuming and Sinking Stream+ consumeStream,+ sinkStream,+ -- * Useful Combinators+ repeatProcess,+ mapStream,+ mapStreamM,+ apStreamDataFirst,+ apStreamDataLater,+ apStreamParallel,+ filterStream,+ filterStreamM,+ -- * Utilities+ leftStream,+ rightStream,+ replaceLeftStream,+ replaceRightStream,+ partitionEitherStream) where++import Data.IORef+import Data.Maybe+import Data.Monoid++import Control.Monad+import Control.Monad.Trans++import Simulation.Aivika.Simulation+import Simulation.Aivika.Cont+import Simulation.Aivika.Process+import Simulation.Aivika.Resource+import Simulation.Aivika.QueueStrategy++-- | Represents an infinite stream of data in time,+-- some kind of the cons cell.+newtype Stream a = Cons { runStream :: Process (a, Stream a)+ -- ^ Run the stream.+ }++instance Functor Stream where+ + fmap f (Cons s) = Cons y where+ y = do ~(x, xs) <- s+ return (f x, fmap f xs)++instance Monoid (Stream a) where++ mempty = emptyStream++ mappend = mergeStreams++ mconcat = concatStreams++-- | Create a stream that will use the specified process identifier.+-- It can be useful to refer to the underlying 'Process' computation which+-- can be passivated, interrupted, canceled and so on. See also the+-- 'processUsingId' function for more details.+streamUsingId :: ProcessId -> Stream a -> Stream a+streamUsingId pid (Cons s) =+ Cons $ processUsingId pid s++-- | Memoize the stream so that it would always return the same data+-- within the simulation run.+memoStream :: Stream a -> Simulation (Stream a)+memoStream (Cons s) =+ do p <- memoProcess $+ do ~(x, xs) <- s+ xs' <- liftSimulation $ memoStream xs+ return (x, xs')+ return (Cons p)++-- | Zip two streams trying to get data sequentially.+zipStreamSeq :: Stream a -> Stream b -> Stream (a, b)+zipStreamSeq (Cons sa) (Cons sb) = Cons y where+ y = do ~(x, xs) <- sa+ ~(y, ys) <- sb+ return ((x, y), zipStreamSeq xs ys)++-- | Zip two streams trying to get data as soon as possible,+-- launching the sub-processes in parallel.+zipStreamParallel :: Stream a -> Stream b -> Stream (a, b)+zipStreamParallel (Cons sa) (Cons sb) = Cons y where+ y = do ~((x, xs), (y, ys)) <- zipProcessParallel sa sb+ return ((x, y), zipStreamParallel xs ys)++-- | Zip three streams trying to get data sequentially.+zip3StreamSeq :: Stream a -> Stream b -> Stream c -> Stream (a, b, c)+zip3StreamSeq (Cons sa) (Cons sb) (Cons sc) = Cons y where+ y = do ~(x, xs) <- sa+ ~(y, ys) <- sb+ ~(z, zs) <- sc+ return ((x, y, z), zip3StreamSeq xs ys zs)++-- | Zip three streams trying to get data as soon as possible,+-- launching the sub-processes in parallel.+zip3StreamParallel :: Stream a -> Stream b -> Stream c -> Stream (a, b, c)+zip3StreamParallel (Cons sa) (Cons sb) (Cons sc) = Cons y where+ y = do ~((x, xs), (y, ys), (z, zs)) <- zip3ProcessParallel sa sb sc+ return ((x, y, z), zip3StreamParallel xs ys zs)++-- | Unzip the stream.+unzipStream :: Stream (a, b) -> Simulation (Stream a, Stream b)+unzipStream s =+ do s' <- memoStream s+ let sa = mapStream fst s'+ sb = mapStream snd s'+ return (sa, sb)++-- | To form each new portion of data for the output stream,+-- read data sequentially from the input streams.+--+-- This is a generalization of 'zipStreamSeq'.+streamSeq :: [Stream a] -> Stream [a]+streamSeq xs = Cons y where+ y = do ps <- forM xs $ runStream+ return (map fst ps, streamSeq $ map snd ps)++-- | To form each new portion of data for the output stream,+-- read data from the input streams in parallel.+--+-- This is a generalization of 'zipStreamParallel'.+streamParallel :: [Stream a] -> Stream [a]+streamParallel xs = Cons y where+ y = do ps <- processParallel $ map runStream xs+ return (map fst ps, streamParallel $ map snd ps)++-- | Return a stream of values generated by the specified process.+repeatProcess :: Process a -> Stream a+repeatProcess p = Cons y where+ y = do a <- p+ return (a, repeatProcess p)++-- | Map the stream according the specified function.+mapStream :: (a -> b) -> Stream a -> Stream b+mapStream = fmap++-- | Compose the stream.+mapStreamM :: (a -> Process b) -> Stream a -> Stream b+mapStreamM f (Cons s) = Cons y where+ y = do (a, xs) <- s+ b <- f a+ return (b, mapStreamM f xs)++-- | Transform the stream getting the transformation function after data have come.+apStreamDataFirst :: Process (a -> b) -> Stream a -> Stream b+apStreamDataFirst f (Cons s) = Cons y where+ y = do ~(a, xs) <- s+ g <- f+ return (g a, apStreamDataFirst f xs)++-- | Transform the stream getting the transformation function before requesting for data.+apStreamDataLater :: Process (a -> b) -> Stream a -> Stream b+apStreamDataLater f (Cons s) = Cons y where+ y = do g <- f+ ~(a, xs) <- s+ return (g a, apStreamDataLater f xs)++-- | Transform the stream trying to get the transformation function as soon as possible+-- at the same time when requesting for the next portion of data.+apStreamParallel :: Process (a -> b) -> Stream a -> Stream b+apStreamParallel f (Cons s) = Cons y where+ y = do ~(g, (a, xs)) <- zipProcessParallel f s+ return (g a, apStreamParallel f xs)++-- | Filter only those data values that satisfy to the specified predicate.+filterStream :: (a -> Bool) -> Stream a -> Stream a+filterStream p (Cons s) = Cons y where+ y = do (a, xs) <- s+ if p a+ then return (a, filterStream p xs)+ else let Cons z = filterStream p xs in z++-- | Filter only those data values that satisfy to the specified predicate.+filterStreamM :: (a -> Process Bool) -> Stream a -> Stream a+filterStreamM p (Cons s) = Cons y where+ y = do (a, xs) <- s+ b <- p a+ if b+ then return (a, filterStreamM p xs)+ else let Cons z = filterStreamM p xs in z++-- | The stream of 'Left' values.+leftStream :: Stream (Either a b) -> Stream a+leftStream (Cons s) = Cons y where+ y = do (a, xs) <- s+ case a of+ Left a -> return (a, leftStream xs)+ Right _ -> let Cons z = leftStream xs in z++-- | The stream of 'Right' values.+rightStream :: Stream (Either a b) -> Stream b+rightStream (Cons s) = Cons y where+ y = do (a, xs) <- s+ case a of+ Left _ -> let Cons z = rightStream xs in z+ Right a -> return (a, rightStream xs)++-- | Replace the 'Left' values.+replaceLeftStream :: Stream (Either a b) -> Stream c -> Stream (Either c b)+replaceLeftStream (Cons sab) (ys0 @ (Cons sc)) = Cons z where+ z = do (a, xs) <- sab+ case a of+ Left _ ->+ do (b, ys) <- sc+ return (Left b, replaceLeftStream xs ys)+ Right a ->+ return (Right a, replaceLeftStream xs ys0)++-- | Replace the 'Right' values.+replaceRightStream :: Stream (Either a b) -> Stream c -> Stream (Either a c)+replaceRightStream (Cons sab) (ys0 @ (Cons sc)) = Cons z where+ z = do (a, xs) <- sab+ case a of+ Right _ ->+ do (b, ys) <- sc+ return (Right b, replaceRightStream xs ys)+ Left a ->+ return (Left a, replaceRightStream xs ys0)++-- | Partition the stream of 'Either' values into two streams.+partitionEitherStream :: Stream (Either a b) -> Simulation (Stream a, Stream b)+partitionEitherStream s =+ do s' <- memoStream s+ return (leftStream s', rightStream s')++-- | Split the input stream into the specified number of output streams+-- after applying the 'FCFS' strategy for enqueuing the output requests.+splitStream :: Int -> Stream a -> Simulation [Stream a]+splitStream = splitStreamQueuing FCFS++-- | Split the input stream into the specified number of output streams.+--+-- If you don't know what the strategy to apply, then you probably+-- need the 'FCFS' strategy, or function 'splitStream' that+-- does namely this.+splitStreamQueuing :: EnqueueStrategy s q+ => s+ -- ^ the strategy applied for enqueuing the output requests+ -> Int+ -- ^ the number of output streams+ -> Stream a+ -- ^ the input stream+ -> Simulation [Stream a]+ -- ^ the splitted output streams+splitStreamQueuing s n x =+ do ref <- liftIO $ newIORef x+ res <- newResource s 1+ let reader =+ usingResource res $+ do p <- liftIO $ readIORef ref+ (a, xs) <- runStream p+ liftIO $ writeIORef ref xs+ return a+ return $ map (\i -> repeatProcess reader) [1..n]++-- | Split the input stream into a list of output streams+-- using the specified priorities.+splitStreamPrioritising :: PriorityQueueStrategy s q p+ => s+ -- ^ the strategy applied for enqueuing the output requests+ -> [Stream p]+ -- ^ the streams of priorities+ -> Stream a+ -- ^ the input stream+ -> Simulation [Stream a]+ -- ^ the splitted output streams+splitStreamPrioritising s ps x =+ do ref <- liftIO $ newIORef x+ res <- newResource s 1+ let stream (Cons p) = Cons z where+ z = do (p', ps) <- p+ a <- usingResourceWithPriority res p' $+ do p <- liftIO $ readIORef ref+ (a, xs) <- runStream p+ liftIO $ writeIORef ref xs+ return a+ return (a, stream ps)+ return $ map stream ps++-- | Concatenate the input streams applying the 'FCFS' strategy and+-- producing one output stream.+concatStreams :: [Stream a] -> Stream a+concatStreams = concatQueuedStreams FCFS++-- | Concatenate the input streams producing one output stream.+--+-- If you don't know what the strategy to apply, then you probably+-- need the 'FCFS' strategy, or function 'concatStreams' that+-- does namely this.+concatQueuedStreams :: EnqueueStrategy s q+ => s+ -- ^ the strategy applied for enqueuing the input data+ -> [Stream a]+ -- ^ the input stream+ -> Stream a+ -- ^ the combined output stream+concatQueuedStreams s streams = Cons z where+ z = do reading <- liftSimulation $ newResourceWithMaxCount FCFS 0 (Just 1)+ writing <- liftSimulation $ newResourceWithMaxCount s 1 (Just 1)+ ref <- liftIO $ newIORef Nothing+ let writer p =+ do (a, xs) <- runStream p+ requestResource writing+ liftIO $ writeIORef ref (Just a)+ releaseResource reading+ writer xs+ reader =+ do requestResource reading+ Just a <- liftIO $ readIORef ref+ liftIO $ writeIORef ref Nothing+ releaseResource writing+ return a+ forM_ streams $ spawnProcess CancelTogether . writer+ runStream $ repeatProcess reader++-- | Concatenate the input priority streams producing one output stream.+concatPriorityStreams :: PriorityQueueStrategy s q p+ => s+ -- ^ the strategy applied for enqueuing the input data+ -> [Stream (p, a)]+ -- ^ the input stream+ -> Stream a+ -- ^ the combined output stream+concatPriorityStreams s streams = Cons z where+ z = do reading <- liftSimulation $ newResourceWithMaxCount FCFS 0 (Just 1)+ writing <- liftSimulation $ newResourceWithMaxCount s 1 (Just 1)+ ref <- liftIO $ newIORef Nothing+ let writer p =+ do ((priority, a), xs) <- runStream p+ requestResourceWithPriority writing priority+ liftIO $ writeIORef ref (Just a)+ releaseResource reading+ writer xs+ reader =+ do requestResource reading+ Just a <- liftIO $ readIORef ref+ liftIO $ writeIORef ref Nothing+ releaseResource writing+ return a+ forM_ streams $ spawnProcess CancelTogether . writer+ runStream $ repeatProcess reader++-- | Merge two streams applying the 'FCFS' strategy for enqueuing the input data.+mergeStreams :: Stream a -> Stream a -> Stream a+mergeStreams = mergeQueuedStreams FCFS++-- | Merge two streams.+--+-- If you don't know what the strategy to apply, then you probably+-- need the 'FCFS' strategy, or function 'mergeStreams' that+-- does namely this.+mergeQueuedStreams :: EnqueueStrategy s q+ => s+ -- ^ the strategy applied for enqueuing the input data+ -> Stream a+ -- ^ the fist input stream+ -> Stream a+ -- ^ the second input stream+ -> Stream a+ -- ^ the output combined stream+mergeQueuedStreams s x y = concatQueuedStreams s [x, y]++-- | Merge two priority streams.+mergePriorityStreams :: PriorityQueueStrategy s q p+ => s+ -- ^ the strategy applied for enqueuing the input data+ -> Stream (p, a)+ -- ^ the fist input stream+ -> Stream (p, a)+ -- ^ the second input stream+ -> Stream a+ -- ^ the output combined stream+mergePriorityStreams s x y = concatPriorityStreams s [x, y]++-- | An empty stream that never returns data.+emptyStream :: Stream a+emptyStream = Cons z where+ z = do pid <- liftSimulation newProcessId+ -- use the generated identifier so that+ -- nobody could reactivate the process,+ -- although it can be still canceled+ processUsingId pid passivateProcess+ error "It should never happen: emptyStream."++-- | Consume the stream. It returns a process that infinitely reads data+-- from the stream and then redirects them to the provided function.+-- It is useful for modeling the process of enqueuing data in the queue+-- from the input stream.+consumeStream :: (a -> Process ()) -> Stream a -> Process ()+consumeStream f s = p s where+ p (Cons s) = do (a, xs) <- s+ f a+ p xs++-- | Sink the stream. It returns a process that infinitely reads data+-- from the stream. The resulting computation can be a moving force+-- to simulate the whole system of the interconnected streams and+-- processors.+sinkStream :: Stream a -> Process ()+sinkStream s = p s where+ p (Cons s) = do (a, xs) <- s+ p xs+
+ Simulation/Aivika/Stream/Random.hs view
@@ -0,0 +1,89 @@++-- |+-- Module : Simulation.Aivika.Stream.Random+-- Copyright : Copyright (c) 2009-2013, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.6.3+--+-- This module defines random streams of data, which are useful+-- for describing the input of the model.+--++module Simulation.Aivika.Stream.Random+ (randomUniformStream,+ randomNormalStream,+ randomExponentialStream,+ randomErlangStream,+ randomPoissonStream,+ randomBinomialStream) where++import System.Random++import Control.Monad.Trans++import Simulation.Aivika.Parameter+import Simulation.Aivika.Parameter.Random+import Simulation.Aivika.Simulation+import Simulation.Aivika.Dynamics+import Simulation.Aivika.Process+import Simulation.Aivika.Stream++-- | Create a new stream with delays distributed uniformly.+randomUniformStream :: Double -- ^ the minimum delay+ -> Double -- ^ the maximum delay+ -> Stream Double -- ^ the stream of delays+randomUniformStream min max = Cons z where+ z = do delay <- liftParameter $ randomUniform min max+ holdProcess delay+ return (delay, randomUniformStream min max)++-- | Create a new stream with delays distributed normally.+randomNormalStream :: Double -- ^ the mean delay+ -> Double -- ^ the delay deviation+ -> Stream Double -- ^ the stream of delays+randomNormalStream mu nu = Cons z where+ z = do delay <- liftParameter $ randomNormal mu nu+ holdProcess delay+ return (delay, randomNormalStream mu nu)+ +-- | Return a new stream with delays distibuted exponentially with the specified mean+-- (the reciprocal of the rate).+randomExponentialStream :: Double+ -- ^ the mean delay (the reciprocal of the rate)+ -> Stream Double+ -- ^ the stream of delays+randomExponentialStream mu = Cons z where+ z = do delay <- liftParameter $ randomExponential mu+ holdProcess delay+ return (delay, randomExponentialStream mu)+ +-- | Return a new stream with delays having the Erlang distribution with the specified+-- scale (the reciprocal of the rate) and shape parameters.+randomErlangStream :: Double -- ^ the scale (the reciprocal of the rate)+ -> Int -- ^ the shape+ -> Stream Double -- ^ the stream of delays+randomErlangStream beta m = Cons z where+ z = do delay <- liftParameter $ randomErlang beta m+ holdProcess delay+ return (delay, randomErlangStream beta m)++-- | Return a new stream with delays having the Poisson distribution with+-- the specified mean.+randomPoissonStream :: Double -- ^ the mean delay+ -> Stream Int -- ^ the stream of delays+randomPoissonStream mu = Cons z where+ z = do delay <- liftParameter $ randomPoisson mu+ holdProcess $ fromIntegral delay+ return (delay, randomPoissonStream mu)++-- | Return a new stream with delays having the binomial distribution with the specified+-- probability and trials.+randomBinomialStream :: Double -- ^ the probability+ -> Int -- ^ the number of trials+ -> Stream Int -- ^ the stream of delays+randomBinomialStream prob trials = Cons z where+ z = do delay <- liftParameter $ randomBinomial prob trials+ holdProcess $ fromIntegral delay+ return (delay, randomBinomialStream prob trials)
Simulation/Aivika/SystemDynamics.hs view
@@ -40,12 +40,17 @@ forecast, trend, -- * Difference Equations- sumDynamics,+ diffsum, -- * Table Functions lookupDynamics, lookupStepwiseDynamics, -- * Discrete Functions delay,+ delayI,+ step,+ pulse,+ pulseP,+ ramp, -- * Financial Functions npv, npve) where@@ -57,12 +62,16 @@ import Control.Monad.Trans import Simulation.Aivika.Internal.Specs+import Simulation.Aivika.Internal.Parameter import Simulation.Aivika.Internal.Simulation import Simulation.Aivika.Internal.Dynamics import Simulation.Aivika.Dynamics.Interpolate-import Simulation.Aivika.Dynamics.Memo.Unboxed import Simulation.Aivika.Unboxed+import Simulation.Aivika.Table +import qualified Simulation.Aivika.Dynamics.Memo as M+import qualified Simulation.Aivika.Dynamics.Memo.Unboxed as MU+ -- -- Equality and Ordering --@@ -246,7 +255,7 @@ -> Dynamics Double -- ^ the initial value -> Simulation (Dynamics Double) -- ^ the integral integ diff i =- mdo y <- memoDynamics z+ mdo y <- MU.memoDynamics z z <- Simulation $ \r -> case spcMethod (runSpecs r) of Euler -> return $ Dynamics $ integEuler diff i y@@ -460,14 +469,14 @@ -- the difference is used instead of derivative. -- -- As usual, to create a loopback, you should use the recursive do-notation.-sumDynamics :: (Num a, Unboxed a)- => Dynamics a -- ^ the difference- -> Dynamics a -- ^ the initial value- -> Simulation (Dynamics a) -- ^ the sum-sumDynamics (Dynamics diff) (Dynamics i) =- mdo y <- memoDynamics z- z <- Simulation $ \r ->- return $ Dynamics $ \p ->+diffsum :: (Num a, Unboxed a)+ => Dynamics a -- ^ the difference+ -> Dynamics a -- ^ the initial value+ -> Simulation (Dynamics a) -- ^ the sum+diffsum (Dynamics diff) (Dynamics i) =+ mdo y <-+ MU.memo0Dynamics $+ Dynamics $ \p -> case pointIteration p of 0 -> i p n -> do @@ -490,67 +499,26 @@ -- | Lookup @x@ in a table of pairs @(x, y)@ using linear interpolation. lookupDynamics :: Dynamics Double -> Array Int (Double, Double) -> Dynamics Double lookupDynamics (Dynamics m) tbl =- Dynamics (\p -> do a <- m p; return $ find first last a) where- (first, last) = bounds tbl- find left right x =- if left > right then- error "Incorrect index: table"- else- let index = (left + 1 + right) `div` 2- x1 = fst $ tbl ! index- in if x1 <= x then - let y | index < right = find index right x- | right == last = snd $ tbl ! right- | otherwise = - let x2 = fst $ tbl ! (index + 1)- y1 = snd $ tbl ! index- y2 = snd $ tbl ! (index + 1)- in y1 + (y2 - y1) * (x - x1) / (x2 - x1) - in y- else- let y | left < index = find left (index - 1) x- | left == first = snd $ tbl ! left- | otherwise = error "Incorrect index: table"- in y+ Dynamics $ \p ->+ do a <- m p+ return $ tableLookup a tbl -- | Lookup @x@ in a table of pairs @(x, y)@ using stepwise function.-lookupStepwiseDynamics :: Dynamics Double- -> Array Int (Double, Double)- -> Dynamics Double+lookupStepwiseDynamics :: Dynamics Double -> Array Int (Double, Double) -> Dynamics Double lookupStepwiseDynamics (Dynamics m) tbl =- Dynamics (\p -> do a <- m p; return $ find first last a) where- (first, last) = bounds tbl- find left right x =- if left > right then- error "Incorrect index: table"- else- let index = (left + 1 + right) `div` 2- x1 = fst $ tbl ! index- in if x1 <= x then - let y | index < right = find index right x- | right == last = snd $ tbl ! right- | otherwise = snd $ tbl ! right- in y- else- let y | left < index = find left (index - 1) x- | left == first = snd $ tbl ! left- | otherwise = error "Incorrect index: table"- in y+ Dynamics $ \p ->+ do a <- m p+ return $ tableLookupStepwise a tbl -- -- Discrete Functions -- --- | Return the delayed value.------ If you want to apply the result recursively in some loopback then you--- should use one of the memoization functions such as 'memoDynamics'--- and 'memo0Dynamics'. +-- | Return the delayed value using the specified lag time. delay :: Dynamics a -- ^ the value to delay -> Dynamics Double -- ^ the lag time- -> Dynamics a -- ^ the initial value -> Dynamics a -- ^ the delayed value-delay (Dynamics x) (Dynamics d) (Dynamics i) = Dynamics r +delay (Dynamics x) (Dynamics d) = discreteDynamics $ Dynamics r where r p = do let t = pointTime p@@ -559,6 +527,35 @@ a <- d p let t' = t - a n' = fromIntegral $ floor $ (t' - spcStartTime sc) / spcDT sc+ y | n' < 0 = x $ p { pointTime = spcStartTime sc,+ pointIteration = 0, + pointPhase = 0 }+ | n' < n = x $ p { pointTime = t',+ pointIteration = n',+ pointPhase = -1 }+ | n' > n = error $+ "Cannot return the future data: delay. " +++ "The lag time cannot be negative."+ | otherwise = error $+ "Cannot return the current data: delay. " +++ "The lag time is too small."+ y++-- | Return the delayed value using the specified lag time and initial value.+-- Because of the latter, it allows creating a loop back.+delayI :: Dynamics a -- ^ the value to delay+ -> Dynamics Double -- ^ the lag time+ -> Dynamics a -- ^ the initial value+ -> Simulation (Dynamics a) -- ^ the delayed value+delayI (Dynamics x) (Dynamics d) (Dynamics i) = M.memo0Dynamics $ Dynamics r + where+ r p = do + let t = pointTime p+ sc = pointSpecs p+ n = pointIteration p+ a <- d p+ let t' = t - a+ n' = fromIntegral $ floor $ (t' - spcStartTime sc) / spcDT sc y | n' < 0 = i $ p { pointTime = spcStartTime sc, pointIteration = 0, pointPhase = 0 }@@ -584,9 +581,10 @@ -- -- @ -- npv stream rate init factor =--- mdo df <- integ (- df * rate) 1+-- mdo let dt' = liftParameter dt+-- df <- integ (- df * rate) 1 -- accum <- integ (stream * df) init--- return $ (accum + dt * stream * df) * factor+-- return $ (accum + dt' * stream * df) * factor -- @ npv :: Dynamics Double -- ^ the stream -> Dynamics Double -- ^ the discount rate@@ -594,9 +592,10 @@ -> Dynamics Double -- ^ factor -> Simulation (Dynamics Double) -- ^ the Net Present Value (NPV) npv stream rate init factor =- mdo df <- integ (- df * rate) 1+ mdo let dt' = liftParameter dt+ df <- integ (- df * rate) 1 accum <- integ (stream * df) init- return $ (accum + dt * stream * df) * factor+ return $ (accum + dt' * stream * df) * factor -- | Return the Net Present Value End of period (NPVE) of the stream computed -- using the specified discount rate, the initial value and some factor.@@ -605,9 +604,10 @@ -- -- @ -- npve stream rate init factor =--- mdo df <- integ (- df * rate \/ (1 + rate * dt)) (1 \/ (1 + rate * dt))+-- mdo let dt' = liftParameter dt+-- df <- integ (- df * rate \/ (1 + rate * dt')) (1 \/ (1 + rate * dt')) -- accum <- integ (stream * df) init--- return $ (accum + dt * stream * df) * factor+-- return $ (accum + dt' * stream * df) * factor -- @ npve :: Dynamics Double -- ^ the stream -> Dynamics Double -- ^ the discount rate@@ -615,6 +615,94 @@ -> Dynamics Double -- ^ factor -> Simulation (Dynamics Double) -- ^ the Net Present Value End (NPVE) npve stream rate init factor =- mdo df <- integ (- df * rate / (1 + rate * dt)) (1 / (1 + rate * dt))+ mdo let dt' = liftParameter dt+ df <- integ (- df * rate / (1 + rate * dt')) (1 / (1 + rate * dt')) accum <- integ (stream * df) init- return $ (accum + dt * stream * df) * factor+ return $ (accum + dt' * stream * df) * factor++-- | Computation that returns 0 until the step time and then returns the specified height.+step :: Dynamics Double+ -- ^ the height+ -> Dynamics Double+ -- ^ the step time+ -> Dynamics Double+step h st =+ discreteDynamics $+ Dynamics $ \p ->+ do let sc = pointSpecs p+ t = pointTime p+ st' <- invokeDynamics p st+ let t' = t + spcDT sc / 2+ if st' < t'+ then invokeDynamics p h+ else return 0++-- | Computation that returns 1, starting at the time start, and lasting for the interval+-- width; 0 is returned at all other times.+pulse :: Dynamics Double+ -- ^ the time start+ -> Dynamics Double+ -- ^ the interval width+ -> Dynamics Double+pulse st w =+ discreteDynamics $+ Dynamics $ \p ->+ do let sc = pointSpecs p+ t = pointTime p+ st' <- invokeDynamics p st+ let t' = t + spcDT sc / 2+ if st' < t'+ then do w' <- invokeDynamics p w+ return $ if t' < st' + w' then 1 else 0+ else return 0++-- | Computation that returns 1, starting at the time start, and lasting for the interval+-- width and then repeats this pattern with the specified period; 0 is returned at all+-- other times.+pulseP :: Dynamics Double+ -- ^ the time start+ -> Dynamics Double+ -- ^ the interval width+ -> Dynamics Double+ -- ^ the time period+ -> Dynamics Double+pulseP st w period =+ discreteDynamics $+ Dynamics $ \p ->+ do let sc = pointSpecs p+ t = pointTime p+ p' <- invokeDynamics p period+ st' <- invokeDynamics p st+ let y' = if (p' > 0) && (t > st')+ then fromIntegral (floor $ (t - st') / p') * p'+ else 0+ let st' = st' + y'+ let t' = t + spcDT sc / 2+ if st' < t'+ then do w' <- invokeDynamics p w+ return $ if t' < st' + w' then 1 else 0+ else return 0++-- | Computation that returns 0 until the specified time start and then+-- slopes upward until the end time and then holds constant.+ramp :: Dynamics Double+ -- ^ the slope parameter+ -> Dynamics Double+ -- ^ the time start+ -> Dynamics Double+ -- ^ the end time+ -> Dynamics Double+ramp slope st e =+ discreteDynamics $+ Dynamics $ \p ->+ do let sc = pointSpecs p+ t = pointTime p+ st' <- invokeDynamics p st+ if st' < t+ then do slope' <- invokeDynamics p slope+ e' <- invokeDynamics p e+ if t < e'+ then return $ slope' * (t - st')+ else return $ slope' * (e' - st')+ else return 0+
+ Simulation/Aivika/Table.hs view
@@ -0,0 +1,64 @@++-- |+-- Module : Simulation.Aivika.Table+-- Copyright : Copyright (c) 2009-2013, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.6.3+--+-- It defines the table functions.+--+module Simulation.Aivika.Table+ (tableLookup,+ tableLookupStepwise) where++import Data.Array++-- | Lookup @x@ in a table of pairs @(x, y)@ using linear interpolation.+tableLookup :: Double -> Array Int (Double, Double) -> Double+tableLookup x tbl = find first last x+ where+ (first, last) = bounds tbl+ find left right x =+ if left > right then+ error "Incorrect index: tableLookup"+ else+ let index = (left + 1 + right) `div` 2+ x1 = fst $ tbl ! index+ in if x1 <= x then + let y | index < right = find index right x+ | right == last = snd $ tbl ! right+ | otherwise = + let x2 = fst $ tbl ! (index + 1)+ y1 = snd $ tbl ! index+ y2 = snd $ tbl ! (index + 1)+ in y1 + (y2 - y1) * (x - x1) / (x2 - x1) + in y+ else+ let y | left < index = find left (index - 1) x+ | left == first = snd $ tbl ! left+ | otherwise = error "Incorrect index: tableLookup"+ in y++-- | Lookup @x@ in a table of pairs @(x, y)@ using stepwise function.+tableLookupStepwise :: Double -> Array Int (Double, Double) -> Double+tableLookupStepwise x tbl = find first last x+ where+ (first, last) = bounds tbl+ find left right x =+ if left > right then+ error "Incorrect index: tableLookupStepwise"+ else+ let index = (left + 1 + right) `div` 2+ x1 = fst $ tbl ! index+ in if x1 <= x then + let y | index < right = find index right x+ | right == last = snd $ tbl ! right+ | otherwise = snd $ tbl ! right+ in y+ else+ let y | left < index = find left (index - 1) x+ | left == first = snd $ tbl ! left+ | otherwise = error "Incorrect index: tableLookupStepwise"+ in y
+ Simulation/Aivika/Task.hs view
@@ -0,0 +1,170 @@++-- |+-- Module : Simulation.Aivika.Task+-- Copyright : Copyright (c) 2009-2013, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.6.3+--+-- The 'Task' value represents a process that was already started in background.+-- We can check the completion of the task, receive notifications about changing+-- its state and even suspend an outer process awaiting the final result of the task.+-- It complements the 'Process' monad as it allows immediately continuing the main+-- computation without suspension.+--+module Simulation.Aivika.Task+ (-- * Task+ Task,+ TaskResult(..),+ taskId,+ tryGetTaskResult,+ taskResult,+ taskResultReceived,+ taskProcess,+ cancelTask,+ taskCancelled,+ -- * Running Task+ runTask,+ runTaskUsingId,+ -- * Spawning Tasks+ spawnTask,+ spawnTaskUsingId,+ -- * Enqueueing Task+ enqueueTask,+ enqueueTaskUsingId) where++import Data.IORef+import Data.Monoid++import Control.Monad+import Control.Monad.Trans+import Control.Exception++import Simulation.Aivika.Internal.Simulation+import Simulation.Aivika.Internal.Dynamics+import Simulation.Aivika.Internal.Event+import Simulation.Aivika.Internal.Cont+import Simulation.Aivika.Internal.Process+import Simulation.Aivika.Internal.Signal++-- | The task represents a process that was already started in background.+data Task a =+ Task { taskId :: ProcessId,+ -- ^ Return an identifier for the process that was launched+ -- in background for this task.+ taskResultRef :: IORef (Maybe (TaskResult a)),+ -- ^ It contains the result of the computation.+ taskResultReceived :: Signal (TaskResult a)+ -- ^ Return a signal that notifies about receiving+ -- the result of the task.+ }++-- | Represents the result of the task.+data TaskResult a = TaskCompleted a+ -- ^ the task was successfully completed and+ -- it returned the specified result+ | TaskError IOException+ -- ^ the specified exception was raised when performing the task.+ | TaskCancelled+ -- ^ the task was cancelled++-- | Try to get the task result immediately without suspension.+tryGetTaskResult :: Task a -> Event (Maybe (TaskResult a))+tryGetTaskResult t =+ Event $ \p -> readIORef (taskResultRef t)++-- | Return the task result suspending the outer process if required.+taskResult :: Task a -> Process (TaskResult a)+taskResult t =+ do x <- liftIO $ readIORef (taskResultRef t)+ case x of+ Just x -> return x+ Nothing ->+ do x <- processAwait (taskResultReceived t)+ return x++-- | Cancel the task.+cancelTask :: Task a -> Event ()+cancelTask t =+ cancelProcessUsingId (taskId t)++-- | Test whether the task was cancelled.+taskCancelled :: Task a -> Event Bool+taskCancelled t =+ processCancelled (taskId t)++-- | Create a task by the specified process and its identifier.+newTaskUsingId :: ProcessId -> Process a -> Event (Task a, Process ())+newTaskUsingId pid p =+ do r <- liftIO $ newIORef Nothing+ s <- liftSimulation newSignalSource+ let t = Task { taskId = pid,+ taskResultRef = r,+ taskResultReceived = publishSignal s }+ let m =+ do v <- liftIO $ newIORef TaskCancelled+ finallyProcess+ (catchProcess+ (do a <- p+ liftIO $ writeIORef v (TaskCompleted a))+ (\e ->+ liftIO $ writeIORef v (TaskError e)))+ (liftEvent $+ do x <- liftIO $ readIORef v+ liftIO $ writeIORef r (Just x)+ triggerSignal s x)+ return (t, m)++-- | Run the process with the specified identifier in background and+-- return the corresponded task immediately.+runTaskUsingId :: ProcessId -> Process a -> Event (Task a)+runTaskUsingId pid p =+ do (t, m) <- newTaskUsingId pid p+ runProcessUsingId pid m+ return t++-- | Run the process in background and return the corresponded task immediately.+runTask :: Process a -> Event (Task a)+runTask p =+ do pid <- liftSimulation newProcessId+ runTaskUsingId pid p++-- | Enqueue the process that will be started at the specified time with the given+-- identifier from the event queue. It returns the corresponded task immediately.+enqueueTaskUsingId :: Double -> ProcessId -> Process a -> Event (Task a)+enqueueTaskUsingId time pid p =+ do (t, m) <- newTaskUsingId pid p+ enqueueProcessUsingId time pid m+ return t++-- | Enqueue the process that will be started at the specified time from the event queue.+-- It returns the corresponded task immediately.+enqueueTask :: Double -> Process a -> Event (Task a)+enqueueTask time p =+ do pid <- liftSimulation newProcessId+ enqueueTaskUsingId time pid p++-- | Run using the specified identifier a child process in background and return+-- immediately the corresponded task.+spawnTaskUsingId :: ContCancellation -> ProcessId -> Process a -> Process (Task a)+spawnTaskUsingId cancellation pid p =+ do (t, m) <- liftEvent $ newTaskUsingId pid p+ spawnProcessUsingId cancellation pid m+ return t++-- | Run a child process in background and return immediately the corresponded task.+spawnTask :: ContCancellation -> Process a -> Process (Task a)+spawnTask cancellation p =+ do pid <- liftSimulation newProcessId+ spawnTaskUsingId cancellation pid p++-- | Return an outer process that behaves like the task itself except for one thing:+-- if the outer process is cancelled then it is not enough to cancel the task. +taskProcess :: Task a -> Process a+taskProcess t =+ do x <- taskResult t+ case x of+ TaskCompleted a -> return a+ TaskError e -> throwProcess e+ TaskCancelled -> cancelProcess
Simulation/Aivika/Unboxed.hs view
Simulation/Aivika/Var.hs view
@@ -121,18 +121,11 @@ let b = f a V.writeVector ys i $! b invokeEvent p $ triggerSignal s b- else do i <- UV.vectorBinarySearch xs t- if i >= 0- then do a <- V.readVector ys i- let b = f a- UV.appendVector xs t- V.appendVector ys $! b- invokeEvent p $ triggerSignal s b- else do a <- V.readVector ys $ - (i + 1) - 1- let b = f a- UV.appendVector xs t- V.appendVector ys $! b- invokeEvent p $ triggerSignal s b+ else do a <- V.readVector ys i+ let b = f a+ UV.appendVector xs t+ V.appendVector ys $! b+ invokeEvent p $ triggerSignal s b -- | Freeze the variable and return in arrays the time points and corresponded -- values when the variable had changed in different time points: (1) the last
Simulation/Aivika/Vector/Unboxed.hs view
aivika.cabal view
@@ -1,5 +1,5 @@ name: aivika-version: 0.7+version: 1.0 synopsis: A multi-paradigm simulation library description: Aivika is a multi-paradigm simulation library which has @@ -15,12 +15,20 @@ with an ability to resume, suspend and cancel the discontinuous processes; .- * allows working with limited resources (you can define your own behaviour+ * allows working with the resources (you can define your own behaviour or use the predefined queue strategies); . * allows customizing the queues (you can define your own behaviour or use the predefined queue strategies); .+ * allows defining an infinite stream of data based on the+ process-oriented computation (designed but not tested in+ anyway - please be very careful when using it);+ .+ * allows defining processors (actually, the Haskell arrows) that+ operate on infinite streams of data (designed but not tested+ in anyway - please be very careful when using them);+ . * supports the activity-oriented paradigm of DES; . * supports the basic constructs for the agent-based modeling;@@ -66,6 +74,22 @@ This document is included in the distributive of Aivika but you can usually find a more recent version by the link provided. .+ P.S.+ .+ Two items, streams and processors, are not yet tested. This is a+ goal for the future version of Aivika. The main reason why I ever uploaded+ my three packages is that the Aivika Experiment Chart package+ was broken in its dependencies, namely, when using the charting+ library. So, I decided to provide the compilable packages again.+ .+ Although I would like to say that the mentioned streams and processors+ will be the main improvement in the future version as they actually+ allow defining some DES models on a very high level as you would define+ them with help of diagrams.+ .+ Also the queues and server are not tested carefully. Use at your own+ risk. At least, the infinite queue seems to be working.+ . \[1] <http://hackage.haskell.org/package/aivika-experiment> . \[2] <http://hackage.haskell.org/package/aivika-experiment-chart>@@ -75,7 +99,7 @@ category: Simulation license: BSD3 license-file: LICENSE-copyright: (c) 2009-2013. David Sorokin <david.sorokin@gmail.com>+copyright: (c) 2009-2014. David Sorokin <david.sorokin@gmail.com> author: David Sorokin maintainer: David Sorokin <david.sorokin@gmail.com> homepage: http://github.com/dsorokin/aivika@@ -100,7 +124,8 @@ library - exposed-modules: Simulation.Aivika.Agent+ exposed-modules: Simulation.Aivika+ Simulation.Aivika.Agent Simulation.Aivika.Cont Simulation.Aivika.DoubleLinkedList Simulation.Aivika.Dynamics@@ -110,20 +135,28 @@ Simulation.Aivika.Dynamics.Memo.Unboxed Simulation.Aivika.Dynamics.Random Simulation.Aivika.Event+ Simulation.Aivika.Generator Simulation.Aivika.Parameter Simulation.Aivika.Parameter.Random Simulation.Aivika.PriorityQueue Simulation.Aivika.Process+ Simulation.Aivika.Processor+ Simulation.Aivika.Processor.RoundRobbin Simulation.Aivika.Queue+ Simulation.Aivika.Queue.Infinite Simulation.Aivika.QueueStrategy- Simulation.Aivika.Random Simulation.Aivika.Ref Simulation.Aivika.Resource+ Simulation.Aivika.Server Simulation.Aivika.Signal Simulation.Aivika.Simulation Simulation.Aivika.Specs Simulation.Aivika.Statistics+ Simulation.Aivika.Stream+ Simulation.Aivika.Stream.Random Simulation.Aivika.SystemDynamics+ Simulation.Aivika.Table+ Simulation.Aivika.Task Simulation.Aivika.Unboxed Simulation.Aivika.Var Simulation.Aivika.Var.Unboxed@@ -133,6 +166,7 @@ other-modules: Simulation.Aivika.Internal.Cont Simulation.Aivika.Internal.Dynamics Simulation.Aivika.Internal.Event+ Simulation.Aivika.Internal.Parameter Simulation.Aivika.Internal.Process Simulation.Aivika.Internal.Signal Simulation.Aivika.Internal.Simulation
examples/BassDiffusion.hs view
@@ -4,12 +4,7 @@ import Control.Monad import Control.Monad.Trans -import Simulation.Aivika.Specs-import Simulation.Aivika.Simulation-import Simulation.Aivika.Event-import Simulation.Aivika.Dynamics-import Simulation.Aivika.Agent-import Simulation.Aivika.Ref+import Simulation.Aivika n = 500 -- the number of agents @@ -20,16 +15,12 @@ specs = Specs { spcStartTime = 0.0, spcStopTime = 8.0, spcDT = 0.1,- spcMethod = RungeKutta4 }+ spcMethod = RungeKutta4,+ spcGeneratorType = SimpleGenerator } -exprnd :: Double -> IO Double-exprnd lambda =- do x <- getStdRandom random- return (- log x / lambda)- -boolrnd :: Double -> IO Bool-boolrnd p =- do x <- getStdRandom random+randomTrue :: Double -> Parameter Bool+randomTrue p =+ do x <- randomUniform 0 1 return (x <= p) data Person = Person { personAgent :: Agent,@@ -57,21 +48,24 @@ do setStateActivation (personPotentialAdopter p) $ do modifyRef potentialAdopters $ \a -> a + 1 -- add a timeout- t <- liftIO $ exprnd advertisingEffectiveness + t <- liftParameter $+ randomExponential (1 / advertisingEffectiveness) let st = personPotentialAdopter p st' = personAdopter p- addTimeout st t $ activateState st'+ addTimeout st t $ selectState st' setStateActivation (personAdopter p) $ do modifyRef adopters $ \a -> a + 1 -- add a timer that works while the state is active- let t = liftIO $ exprnd contactRate -- many times!+ let t = liftParameter $+ randomExponential (1 / contactRate) -- many times! addTimer (personAdopter p) t $ do i <- liftIO $ getStdRandom $ randomR (1, n) let p' = ps ! i- st <- agentState (personAgent p')+ st <- selectedState (personAgent p') when (st == Just (personPotentialAdopter p')) $- do b <- liftIO $ boolrnd adoptionFraction- when b $ activateState (personAdopter p')+ do b <- liftParameter $+ randomTrue adoptionFraction+ when b $ selectState (personAdopter p') setStateDeactivation (personPotentialAdopter p) $ modifyRef potentialAdopters $ \a -> a - 1 setStateDeactivation (personAdopter p) $@@ -83,7 +77,7 @@ definePerson p ps potentialAdopters adopters activatePerson :: Person -> Event ()-activatePerson p = activateState (personPotentialAdopter p)+activatePerson p = selectState (personPotentialAdopter p) activatePersons :: Array Int Person -> Event () activatePersons ps =
examples/ChemicalReaction.hs view
@@ -1,15 +1,14 @@ {-# LANGUAGE RecursiveDo #-} -import Simulation.Aivika.Specs-import Simulation.Aivika.Simulation-import Simulation.Aivika.Dynamics+import Simulation.Aivika import Simulation.Aivika.SystemDynamics specs = Specs { spcStartTime = 0, spcStopTime = 13, spcDT = 0.01,- spcMethod = RungeKutta4 }+ spcMethod = RungeKutta4,+ spcGeneratorType = SimpleGenerator } model :: Simulation [Double] model =
examples/FishBank.hs view
@@ -3,16 +3,15 @@ import Data.Array -import Simulation.Aivika.Specs-import Simulation.Aivika.Simulation-import Simulation.Aivika.Dynamics+import Simulation.Aivika import Simulation.Aivika.SystemDynamics specs = Specs { spcStartTime = 0, spcStopTime = 13, spcDT = 0.01, -- spcDT = 0.000005,- spcMethod = RungeKutta4 }+ spcMethod = RungeKutta4,+ spcGeneratorType = SimpleGenerator } model :: Simulation Double model =
examples/Furnace.hs view
@@ -4,66 +4,46 @@ import Control.Monad import Control.Monad.Trans -import Simulation.Aivika.Specs-import Simulation.Aivika.Simulation-import Simulation.Aivika.Dynamics-import Simulation.Aivika.Event-import Simulation.Aivika.Ref-import Simulation.Aivika.Process-import Simulation.Aivika.Random--import qualified Simulation.Aivika.DoubleLinkedList as DLL+import Simulation.Aivika+import Simulation.Aivika.Queue.Infinite -- | The simulation specs. specs = Specs { spcStartTime = 0.0, -- spcStopTime = 1000.0, spcStopTime = 300.0, spcDT = 0.1,- spcMethod = RungeKutta4 }+ spcMethod = RungeKutta4,+ spcGeneratorType = SimpleGenerator } --- | Return an exponentially distributed random value with mean --- 1 / @lambda@, where @lambda@ is a parameter of the function.-exprnd :: Double -> IO Double-exprnd lambda =- do x <- getStdRandom random- return (- log x / lambda)- -- | Return a random initial temperature of the item. -temprnd :: IO Double-temprnd =- do x <- getStdRandom random- return (400.0 + (600.0 - 400.0) * x)+randomTemp :: Parameter Double+randomTemp = randomUniform 400 600 -- | Represents the furnace. data Furnace = - Furnace { furnaceNormalGen :: IO Double,- -- ^ The normal random number generator.- furnacePits :: [Pit],+ Furnace { furnacePits :: [Pit], -- ^ The pits for ingots. furnacePitCount :: Ref Int, -- ^ The count of active pits with ingots.- furnaceAwaitingIngots :: DLL.DoubleLinkedList Ingot,- -- ^ The awaiting ingots in the queue.- furnaceQueueCount :: Ref Int,- -- ^ The queue count.- furnaceWaitCount :: Ref Int,- -- ^ The count of awaiting ingots.- furnaceWaitTime :: Ref Double,- -- ^ The wait time for all loaded ingots.- furnaceHeatingTime :: Ref Double,- -- ^ The heating time for all unloaded ingots.+ furnaceQueue :: FCFSQueue Ingot,+ -- ^ The furnace queue.+ furnaceUnloadedSource :: SignalSource (),+ -- ^ Notifies when the ingots have been+ -- unloaded from the furnace.+ furnaceHeatingTime :: Ref (SamplingStats Double),+ -- ^ The heating time for the ready ingots. furnaceTemp :: Ref Double, -- ^ The furnace temperature.- furnaceTotalCount :: Ref Int,- -- ^ The total count of ingots.- furnaceLoadCount :: Ref Int,- -- ^ The count of loaded ingots.- furnaceUnloadCount :: Ref Int,- -- ^ The count of unloaded ingots.- furnaceUnloadTemps :: Ref [Double]- -- ^ The temperatures of all unloaded ingots.+ furnaceReadyCount :: Ref Int,+ -- ^ The count of ready ingots.+ furnaceReadyTemps :: Ref [Double]+ -- ^ The temperatures of all ready ingots. } +-- | Notifies when the ingots have been unloaded from the furnace.+furnaceUnloaded :: Furnace -> Signal ()+furnaceUnloaded = publishSignal . furnaceUnloadedSource+ -- | A pit in the furnace to place the ingots. data Pit = Pit { pitIngot :: Ref (Maybe Ingot),@@ -90,32 +70,22 @@ -- | Create a furnace. newFurnace :: Simulation Furnace newFurnace =- do normalGen <- liftIO newNormalGen- pits <- sequence [newPit | i <- [1..10]]+ do pits <- sequence [newPit | i <- [1..10]] pitCount <- newRef 0- awaitingIngots <- liftIO DLL.newList- queueCount <- newRef 0- waitCount <- newRef 0- waitTime <- newRef 0.0- heatingTime <- newRef 0.0+ queue <- newFCFSQueue+ heatingTime <- newRef emptySamplingStats h <- newRef 1650.0- totalCount <- newRef 0- loadCount <- newRef 0- unloadCount <- newRef 0- unloadTemps <- newRef []- return Furnace { furnaceNormalGen = normalGen,- furnacePits = pits,+ readyCount <- newRef 0+ readyTemps <- newRef []+ s <- newSignalSource+ return Furnace { furnacePits = pits, furnacePitCount = pitCount,- furnaceAwaitingIngots = awaitingIngots,- furnaceQueueCount = queueCount,- furnaceWaitCount = waitCount,- furnaceWaitTime = waitTime,+ furnaceQueue = queue,+ furnaceUnloadedSource = s, furnaceHeatingTime = heatingTime, furnaceTemp = h,- furnaceTotalCount = totalCount,- furnaceLoadCount = loadCount, - furnaceUnloadCount = unloadCount, - furnaceUnloadTemps = unloadTemps }+ furnaceReadyCount = readyCount, + furnaceReadyTemps = readyTemps } -- | Create a new pit. newPit :: Simulation Pit@@ -129,9 +99,9 @@ newIngot :: Furnace -> Event Ingot newIngot furnace = do t <- liftDynamics time- xi <- liftIO $ furnaceNormalGen furnace- h' <- liftIO temprnd- let c = 0.1 + (0.05 + xi * 0.01)+ xi <- liftParameter $ randomNormal 0.05 0.01+ h' <- liftParameter randomTemp+ let c = 0.1 + xi return Ingot { ingotFurnace = furnace, ingotReceiveTime = t, ingotReceiveTemp = h',@@ -150,7 +120,7 @@ -- update the temperature of the ingot. let furnace = ingotFurnace ingot- dt' <- liftDynamics dt+ dt' <- liftParameter dt h' <- readRef (pitTemp pit) h <- readRef (furnaceTemp furnace) writeRef (pitTemp pit) $ @@ -169,84 +139,73 @@ do h' <- readRef (pitTemp pit) when (h' >= 2000.0) $ do Just ingot <- readRef (pitIngot pit) - unloadIngot ingot pit+ unloadIngot furnace ingot pit -- | Try to load an awaiting ingot in the specified empty pit. tryLoadPit :: Furnace -> Pit -> Event () tryLoadPit furnace pit =- do let ingots = furnaceAwaitingIngots furnace- flag <- liftIO $ DLL.listNull ingots- unless flag $- do ingot <- liftIO $ DLL.listFirst ingots- liftIO $ DLL.listRemoveFirst ingots- t' <- liftDynamics time- modifyRef (furnaceQueueCount furnace) (+ (-1))- loadIngot (ingot { ingotLoadTime = t',- ingotLoadTemp = 400.0 }) pit+ do ingot <- tryDequeue (furnaceQueue furnace)+ case ingot of+ Nothing ->+ return ()+ Just ingot ->+ do t' <- liftDynamics time+ loadIngot furnace (ingot { ingotLoadTime = t',+ ingotLoadTemp = 400.0 }) pit -- | Unload the ingot from the specified pit. -unloadIngot :: Ingot -> Pit -> Event ()-unloadIngot ingot pit = +unloadIngot :: Furnace -> Ingot -> Pit -> Event ()+unloadIngot furnace ingot pit = do h' <- readRef (pitTemp pit) writeRef (pitIngot pit) Nothing writeRef (pitTemp pit) 0.0- + -- count the active pits- let furnace = ingotFurnace ingot- count <- readRef (furnacePitCount furnace)- writeRef (furnacePitCount furnace) (count - 1)+ modifyRef (furnacePitCount furnace) (+ (- 1)) -- how long did we heat the ingot up? t' <- liftDynamics time- modifyRef (furnaceHeatingTime furnace)- (+ (t' - ingotLoadTime ingot))+ modifyRef (furnaceHeatingTime furnace) $+ addSamplingStats (t' - ingotLoadTime ingot) -- what is the temperature of the unloaded ingot?- modifyRef (furnaceUnloadTemps furnace) (h' :)+ modifyRef (furnaceReadyTemps furnace) (h' :) - -- count the unloaded ingots- modifyRef (furnaceUnloadCount furnace) (+ 1)+ -- count the ready ingots+ modifyRef (furnaceReadyCount furnace) (+ 1) -- | Load the ingot in the specified pit-loadIngot :: Ingot -> Pit -> Event ()-loadIngot ingot pit =+loadIngot :: Furnace -> Ingot -> Pit -> Event ()+loadIngot furnace ingot pit = do writeRef (pitIngot pit) $ Just ingot writeRef (pitTemp pit) $ ingotLoadTemp ingot- + -- count the active pits- let furnace = ingotFurnace ingot+ modifyRef (furnacePitCount furnace) (+ 1) count <- readRef (furnacePitCount furnace)- writeRef (furnacePitCount furnace) (count + 1) -- decrease the furnace temperature h <- readRef (furnaceTemp furnace) let h' = ingotLoadTemp ingot- dh = - (h - h') / fromInteger (toInteger (count + 1))+ dh = - (h - h') / fromIntegral count writeRef (furnaceTemp furnace) $ h + dh-- -- how long did we keep the ingot in the queue?- t' <- liftDynamics time- modifyRef (furnaceWaitCount furnace) (+ 1) - modifyRef (furnaceWaitTime furnace)- (+ (t' - ingotReceiveTime ingot))-- -- count the loaded ingots- modifyRef (furnaceLoadCount furnace) (+ 1)- + -- | Start iterating the furnace processing through the event queue. startIteratingFurnace :: Furnace -> Event () startIteratingFurnace furnace = let pits = furnacePits furnace in enqueueEventWithIntegTimes $- do ready <- ingotsReady furnace+ do -- try to unload ready ingots+ ready <- ingotsReady furnace when ready $ do mapM_ (tryUnloadPit furnace) pits- pits' <- emptyPits furnace- mapM_ (tryLoadPit furnace) pits'+ triggerSignal (furnaceUnloadedSource furnace) ()++ -- heat up mapM_ heatPitUp pits -- update the temperature of the furnace- dt' <- liftDynamics dt+ dt' <- liftParameter dt h <- readRef (furnaceTemp furnace) writeRef (furnaceTemp furnace) $ h + dt' * (2600.0 - h) * 0.2@@ -257,32 +216,37 @@ filterM (fmap isNothing . readRef . pitIngot) $ furnacePits furnace --- | Accept a new ingot.-acceptIngot :: Furnace -> Event ()-acceptIngot furnace =- do ingot <- newIngot furnace- - -- counting- modifyRef (furnaceTotalCount furnace) (+ 1)- - -- check what to do with the new ingot- count <- readRef (furnacePitCount furnace)- if count >= 10- then do let ingots = furnaceAwaitingIngots furnace- liftIO $ DLL.listAddLast ingots ingot- modifyRef (furnaceQueueCount furnace) (+ 1)- else do pit:_ <- emptyPits furnace- loadIngot ingot pit- --- | Process the furnace.-processFurnace :: Furnace -> Process ()-processFurnace furnace =- do delay <- liftIO $ exprnd (1.0 / 2.5)+-- | This process takes ingots from the queue and then+-- loads them in the furnace.+loadingProcess :: Furnace -> Process ()+loadingProcess furnace =+ do ingot <- dequeue (furnaceQueue furnace)+ let wait :: Process ()+ wait =+ do count <- liftEvent $ readRef (furnacePitCount furnace)+ when (count >= 10) $+ do processAwait (furnaceUnloaded furnace)+ wait+ wait+ -- take any empty pit and load it+ liftEvent $+ do pit: _ <- emptyPits furnace+ loadIngot furnace ingot pit+ -- repeat it again+ loadingProcess furnace+ +-- | The input process that adds new ingots to the queue.+inputProcess :: Furnace -> Process ()+inputProcess furnace =+ do delay <- liftParameter $+ randomExponential 2.5 holdProcess delay -- we have got a new ingot- liftEvent $ acceptIngot furnace+ liftEvent $+ do ingot <- newIngot furnace+ enqueue (furnaceQueue furnace) ingot -- repeat it again- processFurnace furnace+ inputProcess furnace -- | Initialize the furnace. initializeFurnace :: Furnace -> Event ()@@ -295,97 +259,92 @@ x6 <- newIngot furnace let p1 : p2 : p3 : p4 : p5 : p6 : ps = furnacePits furnace- loadIngot (x1 { ingotLoadTemp = 550.0 }) p1- loadIngot (x2 { ingotLoadTemp = 600.0 }) p2- loadIngot (x3 { ingotLoadTemp = 650.0 }) p3- loadIngot (x4 { ingotLoadTemp = 700.0 }) p4- loadIngot (x5 { ingotLoadTemp = 750.0 }) p5- loadIngot (x6 { ingotLoadTemp = 800.0 }) p6- writeRef (furnaceTotalCount furnace) 6+ loadIngot furnace (x1 { ingotLoadTemp = 550.0 }) p1+ loadIngot furnace (x2 { ingotLoadTemp = 600.0 }) p2+ loadIngot furnace (x3 { ingotLoadTemp = 650.0 }) p3+ loadIngot furnace (x4 { ingotLoadTemp = 700.0 }) p4+ loadIngot furnace (x5 { ingotLoadTemp = 750.0 }) p5+ loadIngot furnace (x6 { ingotLoadTemp = 800.0 }) p6 writeRef (furnaceTemp furnace) 1650.0 --- | Return a count, average and deviation.-stats :: [Double] -> (Int, Double, Double)-stats xs = (length xs, ex, sx)- where- n = fromInteger $ toInteger $ length xs- ex = sum xs / n- dx = (sum . map rho) xs / (n - 1.0)- sx = sqrt dx- rho x = (x - ex) ^ 2- -- | The simulation model. model :: Simulation () model = do furnace <- newFurnace- pid <- newProcessId-+ -- initialize the furnace and start its iterating in start time runEventInStartTime IncludingCurrentEvents $ do initializeFurnace furnace startIteratingFurnace furnace - -- accept input ingots- runProcessInStartTime IncludingCurrentEvents- pid (processFurnace furnace)+ -- generate randomly new input ingots+ runProcessInStartTime IncludingCurrentEvents $+ inputProcess furnace++ -- load permanently the input ingots in the furnace+ runProcessInStartTime IncludingCurrentEvents $+ loadingProcess furnace -- run the model in the final time point runEventInStopTime IncludingCurrentEvents $ do -- the ingots- c0 <- readRef (furnaceTotalCount furnace)- c1 <- readRef (furnaceLoadCount furnace)- c2 <- readRef (furnaceUnloadCount furnace)- c3 <- readRef (furnaceWaitCount furnace)+ c0 <- queueStoreCount (furnaceQueue furnace)+ c1 <- queueOutputCount (furnaceQueue furnace)+ c2 <- readRef (furnaceReadyCount furnace) liftIO $ do putStrLn "The count of ingots:"+ putStrLn "" putStrLn $ " total = " ++ show c0 putStrLn $ " loaded = " ++ show c1 putStrLn $ " ready = " ++ show c2- putStrLn $ " awaited in the queue = " ++ show c3 putStrLn "" -- the temperature of the ready ingots- (n1, e1, d1) <- - fmap stats $ readRef (furnaceUnloadTemps furnace)- + temps <- readRef (furnaceReadyTemps furnace)+ liftIO $ do - putStrLn "The temperature of the ready ingots:"- putStrLn $ " average = " ++ show e1- putStrLn $ " deviation = " ++ show d1+ putStrLn "The temperature of ready ingots:" putStrLn ""+ putStrLn $ samplingStatsSummary (listSamplingStats temps) 2 []+ putStrLn ""+ + -- the mean heating time+ r5 <- readRef (furnaceHeatingTime furnace)+ + liftIO $ do+ putStrLn "The heating time:"+ putStrLn ""+ putStrLn $ samplingStatsSummary r5 2 []+ putStrLn "" -- the ingots in pits r2 <- readRef (furnacePitCount furnace) liftIO $ do- putStrLn "The ingots in pits (in the final time): "+ putStr "The ingots in pits (in the final time): " putStrLn $ show r2 putStrLn "" - -- the queue size- r3 <- readRef (furnaceQueueCount furnace)+ -- the queue size and mean wait time+ r3 <- queueCount (furnaceQueue furnace)+ + r4 <- fmap samplingStatsMean $+ queueWaitTime (furnaceQueue furnace) liftIO $ do- putStrLn "The queue size (in the final time): "- putStrLn $ show r3+ putStrLn "The queue summary: " putStrLn ""- - -- the mean wait time in the queue- waitTime <- readRef (furnaceWaitTime furnace)- waitCount <- readRef (furnaceWaitCount furnace)+ putStrLn $ " size (in the final time) = " ++ show r3+ putStrLn $ " mean wait time = " ++ show r4+ putStrLn "" - let t4 = waitTime / fromIntegral waitCount- - -- the mean heating time- heatingTime <- readRef (furnaceHeatingTime furnace)- unloadCount <- readRef (furnaceUnloadCount furnace)+ summary <- queueSummary (furnaceQueue furnace) 2 - let t5 = heatingTime / fromIntegral unloadCount- liftIO $ do- putStrLn $ "The mean wait time: " ++ show t4- putStrLn $ "The mean heating time: " ++ show t5+ putStrLn "The detailed info about the queue (in the final time): "+ putStrLn ""+ putStrLn $ summary [] -- | The main program. main = runSimulation model specs
examples/MachRep1.hs view
@@ -15,58 +15,43 @@ -- Output is long-run proportion of up time. Should get value of about -- 0.66. -import System.Random import Control.Monad.Trans -import Simulation.Aivika.Specs-import Simulation.Aivika.Simulation-import Simulation.Aivika.Event-import Simulation.Aivika.Dynamics-import Simulation.Aivika.Ref-import Simulation.Aivika.Process+import Simulation.Aivika -upRate = 1.0 / 1.0 -- reciprocal of mean up time-repairRate = 1.0 / 0.5 -- reciprocal of mean repair time+meanUpTime = 1.0+meanRepairTime = 0.5 specs = Specs { spcStartTime = 0.0, spcStopTime = 1000.0, spcDT = 1.0,- spcMethod = RungeKutta4 }+ spcMethod = RungeKutta4,+ spcGeneratorType = SimpleGenerator } -exprnd :: Double -> IO Double-exprnd lambda =- do x <- getStdRandom random- return (- log x / lambda)- model :: Simulation Double model = do totalUpTime <- newRef 0.0 - pid1 <- newProcessId- pid2 <- newProcessId- let machine :: Process () machine =- do startUpTime <- liftDynamics time- upTime <- liftIO $ exprnd upRate+ do upTime <-+ liftParameter $+ randomExponential meanUpTime holdProcess upTime- finishUpTime <- liftDynamics time liftEvent $ - modifyRef totalUpTime- (+ (finishUpTime - startUpTime))- repairTime <- liftIO $ exprnd repairRate+ modifyRef totalUpTime (+ upTime)+ repairTime <-+ liftParameter $+ randomExponential meanRepairTime holdProcess repairTime machine - runProcessInStartTime IncludingCurrentEvents- pid1 machine- - runProcessInStartTime IncludingCurrentEvents- pid2 machine+ runProcessInStartTime IncludingCurrentEvents machine+ runProcessInStartTime IncludingCurrentEvents machine runEventInStopTime IncludingCurrentEvents $ do x <- readRef totalUpTime- y <- liftDynamics stoptime+ y <- liftParameter stoptime return $ x / (2 * y) main = runSimulation model specs >>= print
examples/MachRep1EventDriven.hs view
@@ -15,28 +15,19 @@ -- Output is long-run proportion of up time. Should get value of about -- 0.66. -import System.Random import Control.Monad.Trans -import Simulation.Aivika.Specs-import Simulation.Aivika.Simulation-import Simulation.Aivika.Dynamics-import Simulation.Aivika.Event-import Simulation.Aivika.Ref+import Simulation.Aivika -upRate = 1.0 / 1.0 -- reciprocal of mean up time-repairRate = 1.0 / 0.5 -- reciprocal of mean repair time+meanUpTime = 1.0+meanRepairTime = 0.5 specs = Specs { spcStartTime = 0.0, spcStopTime = 1000.0, spcDT = 1.0,- spcMethod = RungeKutta4 }+ spcMethod = RungeKutta4,+ spcGeneratorType = SimpleGenerator } -exprnd :: Double -> IO Double-exprnd lambda =- do x <- getStdRandom random- return (- log x / lambda)- model :: Simulation Double model = do totalUpTime <- newRef 0.0@@ -46,7 +37,9 @@ do finishUpTime <- liftDynamics time modifyRef totalUpTime (+ (finishUpTime - startUpTime))- repairTime <- liftIO $ exprnd repairRate+ repairTime <-+ liftParameter $+ randomExponential meanRepairTime -- enqueue a new event let t = finishUpTime + repairTime@@ -56,7 +49,9 @@ machineRepaired = do startUpTime <- liftDynamics time- upTime <- liftIO $ exprnd upRate+ upTime <-+ liftParameter $+ randomExponential meanUpTime -- enqueue a new event let t = startUpTime + upTime@@ -70,7 +65,7 @@ runEventInStopTime IncludingCurrentEvents $ do x <- readRef totalUpTime- y <- liftDynamics stoptime+ y <- liftParameter stoptime return $ x / (2 * y) main = runSimulation model specs >>= print
examples/MachRep1TimeDriven.hs view
@@ -15,28 +15,19 @@ -- Output is long-run proportion of up time. Should get value of about -- 0.66. -import System.Random import Control.Monad.Trans -import Simulation.Aivika.Specs-import Simulation.Aivika.Simulation-import Simulation.Aivika.Dynamics-import Simulation.Aivika.Event-import Simulation.Aivika.Ref+import Simulation.Aivika -upRate = 1.0 / 1.0 -- reciprocal of mean up time-repairRate = 1.0 / 0.5 -- reciprocal of mean repair time+meanUpTime = 1.0+meanRepairTime = 0.5 specs = Specs { spcStartTime = 0.0, spcStopTime = 1000.0, spcDT = 0.05,- spcMethod = RungeKutta4 }+ spcMethod = RungeKutta4,+ spcGeneratorType = SimpleGenerator } -exprnd :: Double -> IO Double-exprnd lambda =- do x <- getStdRandom random- return (- log x / lambda)- model :: Simulation Double model = do totalUpTime <- newRef 0.0@@ -69,12 +60,13 @@ -- the machine is broken startUpTime' <- readRef startUpTime finishUpTime' <- liftDynamics time- dt' <- liftDynamics dt+ dt' <- liftParameter dt modifyRef totalUpTime $ \a -> a + (finishUpTime' - startUpTime')- repairTime' <- - liftIO $ exprnd repairRate+ repairTime' <-+ liftParameter $+ randomExponential meanRepairTime writeRef repairNum $ round (repairTime' / dt') @@ -82,18 +74,19 @@ do writeRef repairNum (-1) -- the machine is repaired t' <- liftDynamics time- dt' <- liftDynamics dt+ dt' <- liftParameter dt writeRef startUpTime t'- upTime' <- - liftIO $ exprnd upRate+ upTime' <-+ liftParameter $+ randomExponential meanUpTime writeRef upNum $ round (upTime' / dt') - result | upNum' > 0 = untilBroken+ result | upNum' > 0 = untilBroken | upNum' == 0 = broken- | repairNum' > 0 = untilRepaired+ | repairNum' > 0 = untilRepaired | repairNum' == 0 = repaired- | otherwise = repaired + | otherwise = repaired result -- create two machines with type Event ()@@ -110,7 +103,7 @@ -- return the result in the stop time runEventInStopTime IncludingCurrentEvents $ do x <- readRef totalUpTime- y <- liftDynamics stoptime+ y <- liftParameter stoptime return $ x / (2 * y) main = runSimulation model specs >>= print
examples/MachRep2.hs view
@@ -17,31 +17,19 @@ -- that a given machine does not have immediate access to the repairperson -- when the machine breaks down. Output values should be about 0.6 and 0.67. -import System.Random import Control.Monad import Control.Monad.Trans -import Simulation.Aivika.Specs-import Simulation.Aivika.Simulation-import Simulation.Aivika.Dynamics-import Simulation.Aivika.Event-import Simulation.Aivika.Ref-import Simulation.Aivika.QueueStrategy-import Simulation.Aivika.Resource-import Simulation.Aivika.Process+import Simulation.Aivika -upRate = 1.0 / 1.0 -- reciprocal of mean up time-repairRate = 1.0 / 0.5 -- reciprocal of mean repair time+meanUpTime = 1.0+meanRepairTime = 0.5 specs = Specs { spcStartTime = 0.0, spcStopTime = 1000.0, spcDT = 1.0,- spcMethod = RungeKutta4 }- -exprnd :: Double -> IO Double-exprnd lambda =- do x <- getStdRandom random- return (- log x / lambda)+ spcMethod = RungeKutta4,+ spcGeneratorType = SimpleGenerator } model :: Simulation (Double, Double) model =@@ -55,19 +43,16 @@ -- total up time for all machines totalUpTime <- newRef 0.0 - repairPerson <- newResource FCFS 1- - pid1 <- newProcessId- pid2 <- newProcessId+ repairPerson <- newFCFSResource 1 let machine :: Process () machine =- do startUpTime <- liftDynamics time- upTime <- liftIO $ exprnd upRate+ do upTime <-+ liftParameter $+ randomExponential meanUpTime holdProcess upTime- finishUpTime <- liftDynamics time- liftEvent $ modifyRef totalUpTime - (+ (finishUpTime - startUpTime))+ liftEvent $+ modifyRef totalUpTime (+ upTime) -- check the resource availability liftEvent $@@ -77,21 +62,20 @@ modifyRef nImmedRep (+ 1) requestResource repairPerson- repairTime <- liftIO $ exprnd repairRate+ repairTime <-+ liftParameter $+ randomExponential meanRepairTime holdProcess repairTime releaseResource repairPerson machine - runProcessInStartTime IncludingCurrentEvents- pid1 machine-- runProcessInStartTime IncludingCurrentEvents- pid2 machine+ runProcessInStartTime IncludingCurrentEvents machine+ runProcessInStartTime IncludingCurrentEvents machine runEventInStopTime IncludingCurrentEvents $ do x <- readRef totalUpTime- y <- liftDynamics stoptime+ y <- liftParameter stoptime n <- readRef nRep nImmed <- readRef nImmedRep return (x / (2 * y),
examples/MachRep3.hs view
@@ -13,31 +13,19 @@ -- until both machines are down. We find the proportion of up time. It -- should come out to about 0.45. -import System.Random import Control.Monad import Control.Monad.Trans -import Simulation.Aivika.Specs-import Simulation.Aivika.Simulation-import Simulation.Aivika.Dynamics-import Simulation.Aivika.Event-import Simulation.Aivika.Ref-import Simulation.Aivika.QueueStrategy-import Simulation.Aivika.Resource-import Simulation.Aivika.Process+import Simulation.Aivika -upRate = 1.0 / 1.0 -- reciprocal of mean up time-repairRate = 1.0 / 0.5 -- reciprocal of mean repair time+meanUpTime = 1.0+meanRepairTime = 0.5 specs = Specs { spcStartTime = 0.0, spcStopTime = 1000.0, spcDT = 1.0,- spcMethod = RungeKutta4 }- -exprnd :: Double -> IO Double-exprnd lambda =- do x <- getStdRandom random- return (- log x / lambda)+ spcMethod = RungeKutta4,+ spcGeneratorType = SimpleGenerator } model :: Simulation Double model =@@ -54,14 +42,15 @@ let machine :: ProcessId -> Process () machine pid =- do startUpTime <- liftDynamics time- upTime <- liftIO $ exprnd upRate+ do upTime <-+ liftParameter $+ randomExponential meanUpTime holdProcess upTime- finishUpTime <- liftDynamics time- liftEvent $ modifyRef totalUpTime - (+ (finishUpTime - startUpTime))- - liftEvent $ modifyRef nUp $ \a -> a - 1+ liftEvent $+ modifyRef totalUpTime (+ upTime) + + liftEvent $+ modifyRef nUp (+ (-1)) nUp' <- liftEvent $ readRef nUp if nUp' == 1 then passivateProcess@@ -71,22 +60,25 @@ reactivateProcess pid requestResource repairPerson- repairTime <- liftIO $ exprnd repairRate+ repairTime <-+ liftParameter $+ randomExponential meanRepairTime holdProcess repairTime- liftEvent $ modifyRef nUp $ \a -> a + 1+ liftEvent $+ modifyRef nUp (+ 1) releaseResource repairPerson machine pid - runProcessInStartTime IncludingCurrentEvents+ runProcessInStartTimeUsingId IncludingCurrentEvents pid1 (machine pid2) - runProcessInStartTime IncludingCurrentEvents+ runProcessInStartTimeUsingId IncludingCurrentEvents pid2 (machine pid1) runEventInStopTime IncludingCurrentEvents $ do x <- readRef totalUpTime- y <- liftDynamics stoptime+ y <- liftDynamics time return $ x / (2 * y) main = runSimulation model specs >>= print
examples/README view
@@ -1,6 +1,6 @@ More examples are bundled with packages aivika-experiment and aivika-experiment-chart. They plot charts, save the simulation results in the CSV files and generate -an HTML web page which can be observed in your favourite Internet browser.+HTML web pages which can be observed in your favourite Internet browser. Some examples define a parametric Monte-Carlo simulation, after which the deviation charts and histograms are plotted, for example.
examples/TimeOut.hs view
@@ -18,16 +18,10 @@ -- We find the proportion of messages which timeout. The output should -- be about 0.61. -import System.Random import Control.Monad import Control.Monad.Trans -import Simulation.Aivika.Specs-import Simulation.Aivika.Simulation-import Simulation.Aivika.Dynamics-import Simulation.Aivika.Event-import Simulation.Aivika.Ref-import Simulation.Aivika.Process+import Simulation.Aivika ackRate = 1.0 / 1.0 -- reciprocal of the acknowledge mean time toPeriod = 0.5 -- timeout period@@ -35,12 +29,8 @@ specs = Specs { spcStartTime = 0.0, spcStopTime = 10000.0, spcDT = 1.0,- spcMethod = RungeKutta4 }- -exprnd :: Double -> IO Double-exprnd lambda =- do x <- getStdRandom random- return (- log x / lambda)+ spcMethod = RungeKutta4,+ spcGeneratorType = SimpleGenerator } model :: Simulation Double model =@@ -63,9 +53,9 @@ timeoutPid <- liftSimulation newProcessId ackPid <- liftSimulation newProcessId -- set up the timeout- liftEvent $ runProcess timeoutPid (timeout ackPid)+ liftEvent $ runProcessUsingId timeoutPid (timeout ackPid) -- set up the message send/ACK- liftEvent $ runProcess ackPid (acknowledge timeoutPid)+ liftEvent $ runProcessUsingId ackPid (acknowledge timeoutPid) passivateProcess liftEvent $ do code <- readRef reactivatedCode@@ -80,18 +70,20 @@ liftEvent $ do writeRef reactivatedCode 1 reactivateProcess nodePid- cancelProcess ackPid+ cancelProcessUsingId ackPid acknowledge :: ProcessId -> Process () acknowledge timeoutPid =- do ackTime <- liftIO $ exprnd ackRate+ do ackTime <-+ liftParameter $+ randomExponential (1 / ackRate) holdProcess ackTime liftEvent $ do writeRef reactivatedCode 2 reactivateProcess nodePid- cancelProcess timeoutPid+ cancelProcessUsingId timeoutPid - runProcessInStartTime IncludingCurrentEvents+ runProcessInStartTimeUsingId IncludingCurrentEvents nodePid node runEventInStopTime IncludingCurrentEvents $
examples/TimeOutInt.hs view
@@ -16,16 +16,10 @@ -- We find the proportion of messages which timeout. The output should -- be about 0.61. -import System.Random import Control.Monad import Control.Monad.Trans -import Simulation.Aivika.Specs-import Simulation.Aivika.Simulation-import Simulation.Aivika.Dynamics-import Simulation.Aivika.Event-import Simulation.Aivika.Ref-import Simulation.Aivika.Process+import Simulation.Aivika ackRate = 1.0 / 1.0 -- reciprocal of the acknowledge mean time toPeriod = 0.5 -- timeout period@@ -33,12 +27,8 @@ specs = Specs { spcStartTime = 0.0, spcStopTime = 10000.0, spcDT = 1.0,- spcMethod = RungeKutta4 }- -exprnd :: Double -> IO Double-exprnd lambda =- do x <- getStdRandom random- return (- log x / lambda)+ spcMethod = RungeKutta4,+ spcGeneratorType = SimpleGenerator } model :: Simulation Double model =@@ -47,7 +37,7 @@ -- number of timeouts which have occured nTimeOuts <- newRef 0- + nodePid <- newProcessId let node :: Process ()@@ -56,15 +46,17 @@ -- create the process ID timeoutPid <- liftSimulation newProcessId -- set up the timeout- liftEvent $ runProcess timeoutPid timeout+ liftEvent $ runProcessUsingId timeoutPid timeout -- wait for ACK, but could be timeout- ackTime <- liftIO $ exprnd ackRate + ackTime <-+ liftParameter $+ randomExponential (1 / ackRate) holdProcess ackTime liftEvent $ do interrupted <- processInterrupted nodePid if interrupted then modifyRef nTimeOuts $ (+) 1- else cancelProcess timeoutPid+ else cancelProcessUsingId timeoutPid node timeout :: Process ()@@ -72,7 +64,7 @@ do holdProcess toPeriod liftEvent $ interruptProcess nodePid - runProcessInStartTime IncludingCurrentEvents+ runProcessInStartTimeUsingId IncludingCurrentEvents nodePid node runEventInStopTime IncludingCurrentEvents $