aivika 2.0 → 2.1
raw patch · 78 files changed
+17454/−16566 lines, 78 filessetup-changedPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ Simulation.Aivika.Activity: activityChanged_ :: Activity s a b -> Signal ()
+ Simulation.Aivika.Activity: activityIdleFactor :: Activity s a b -> Event Double
+ Simulation.Aivika.Activity: activityIdleFactorChanged :: Activity s a b -> Signal Double
+ Simulation.Aivika.Activity: activityIdleFactorChanged_ :: Activity s a b -> Signal ()
+ Simulation.Aivika.Activity: activityIdleTime :: Activity s a b -> Event (SamplingStats Double)
+ Simulation.Aivika.Activity: activityIdleTimeChanged :: Activity s a b -> Signal (SamplingStats Double)
+ Simulation.Aivika.Activity: activityIdleTimeChanged_ :: Activity s a b -> Signal ()
+ Simulation.Aivika.Activity: activityInitState :: Activity s a b -> s
+ Simulation.Aivika.Activity: activityNet :: Activity s a b -> Net a b
+ Simulation.Aivika.Activity: activityState :: Activity s a b -> Event s
+ Simulation.Aivika.Activity: activityStateChanged :: Activity s a b -> Signal s
+ Simulation.Aivika.Activity: activityStateChanged_ :: Activity s a b -> Signal ()
+ Simulation.Aivika.Activity: activitySummary :: Activity s a b -> Int -> Event ShowS
+ Simulation.Aivika.Activity: activityTotalIdleTime :: Activity s a b -> Event Double
+ Simulation.Aivika.Activity: activityTotalIdleTimeChanged :: Activity s a b -> Signal Double
+ Simulation.Aivika.Activity: activityTotalIdleTimeChanged_ :: Activity s a b -> Signal ()
+ Simulation.Aivika.Activity: activityTotalUtilisationTime :: Activity s a b -> Event Double
+ Simulation.Aivika.Activity: activityTotalUtilisationTimeChanged :: Activity s a b -> Signal Double
+ Simulation.Aivika.Activity: activityTotalUtilisationTimeChanged_ :: Activity s a b -> Signal ()
+ Simulation.Aivika.Activity: activityUtilisationFactor :: Activity s a b -> Event Double
+ Simulation.Aivika.Activity: activityUtilisationFactorChanged :: Activity s a b -> Signal Double
+ Simulation.Aivika.Activity: activityUtilisationFactorChanged_ :: Activity s a b -> Signal ()
+ Simulation.Aivika.Activity: activityUtilisationTime :: Activity s a b -> Event (SamplingStats Double)
+ Simulation.Aivika.Activity: activityUtilisationTimeChanged :: Activity s a b -> Signal (SamplingStats Double)
+ Simulation.Aivika.Activity: activityUtilisationTimeChanged_ :: Activity s a b -> Signal ()
+ Simulation.Aivika.Activity: activityUtilised :: Activity s a b -> Signal (a, b)
+ Simulation.Aivika.Activity: activityUtilising :: Activity s a b -> Signal a
+ Simulation.Aivika.Activity: data Activity s a b
+ Simulation.Aivika.Activity: newActivity :: (a -> Process b) -> Simulation (Activity () a b)
+ Simulation.Aivika.Activity: newStateActivity :: (s -> a -> Process (s, b)) -> s -> Simulation (Activity s a b)
+ Simulation.Aivika.Circuit: integCircuitEither :: Double -> Circuit (Either Double Double) Double
+ Simulation.Aivika.Circuit: iterateCircuitInIntegTimes :: Circuit a a -> a -> Event (Task a)
+ Simulation.Aivika.Circuit: iterateCircuitInIntegTimes_ :: Circuit a a -> a -> Event ()
+ Simulation.Aivika.Circuit: iterateCircuitInTimes :: [Double] -> Circuit a a -> a -> Event (Task a)
+ Simulation.Aivika.Circuit: iterateCircuitInTimes_ :: [Double] -> Circuit a a -> a -> Event ()
+ Simulation.Aivika.Circuit: sumCircuitEither :: Num a => a -> Circuit (Either a a) a
+ Simulation.Aivika.Net: iterateNet :: Net a a -> a -> Process ()
+ Simulation.Aivika.Process: spawnProcessUsingIdWith :: ContCancellation -> ProcessId -> Process () -> Process ()
+ Simulation.Aivika.Process: spawnProcessWith :: ContCancellation -> Process () -> Process ()
+ Simulation.Aivika.Results: instance (Show s, ResultItemable (ResultValue s)) => ResultProvider (Activity s a b)
+ Simulation.Aivika.Results.Locale: ActivityId :: ResultId
+ Simulation.Aivika.Results.Locale: ActivityIdleFactorId :: ResultId
+ Simulation.Aivika.Results.Locale: ActivityIdleTimeId :: ResultId
+ Simulation.Aivika.Results.Locale: ActivityInitStateId :: ResultId
+ Simulation.Aivika.Results.Locale: ActivityStateId :: ResultId
+ Simulation.Aivika.Results.Locale: ActivityTotalIdleTimeId :: ResultId
+ Simulation.Aivika.Results.Locale: ActivityTotalUtilisationTimeId :: ResultId
+ Simulation.Aivika.Results.Locale: ActivityUtilisationFactorId :: ResultId
+ Simulation.Aivika.Results.Locale: ActivityUtilisationTimeId :: ResultId
+ Simulation.Aivika.SystemDynamics: diffsumEither :: (Num a, Unboxed a) => Dynamics (Either a a) -> Dynamics a -> Simulation (Dynamics a)
+ Simulation.Aivika.SystemDynamics: integEither :: Dynamics (Either Double Double) -> Dynamics Double -> Simulation (Dynamics Double)
+ Simulation.Aivika.Task: spawnTaskUsingIdWith :: ContCancellation -> ProcessId -> Process a -> Process (Task a)
+ Simulation.Aivika.Task: spawnTaskWith :: ContCancellation -> Process a -> Process (Task a)
+ Simulation.Aivika.Transform: integTransformEither :: Dynamics Double -> Transform (Either Double Double) Double
+ Simulation.Aivika.Transform: sumTransformEither :: (Num a, Unboxed a) => Dynamics a -> Transform (Either a a) a
- Simulation.Aivika.Process: spawnProcess :: ContCancellation -> Process () -> Process ()
+ Simulation.Aivika.Process: spawnProcess :: Process () -> Process ()
- Simulation.Aivika.Process: spawnProcessUsingId :: ContCancellation -> ProcessId -> Process () -> Process ()
+ Simulation.Aivika.Process: spawnProcessUsingId :: ProcessId -> Process () -> Process ()
- Simulation.Aivika.Task: spawnTask :: ContCancellation -> Process a -> Process (Task a)
+ Simulation.Aivika.Task: spawnTask :: Process a -> Process (Task a)
- Simulation.Aivika.Task: spawnTaskUsingId :: ContCancellation -> ProcessId -> Process a -> Process (Task a)
+ Simulation.Aivika.Task: spawnTaskUsingId :: ProcessId -> Process a -> Process (Task a)
Files
- LICENSE +30/−30
- Setup.lhs +3/−3
- Simulation/Aivika.hs +87/−85
- Simulation/Aivika/Activity.hs +369/−0
- Simulation/Aivika/Agent.hs +252/−252
- Simulation/Aivika/Arrival.hs +77/−77
- Simulation/Aivika/Circuit.hs +494/−379
- Simulation/Aivika/Cont.hs +19/−19
- Simulation/Aivika/DoubleLinkedList.hs +165/−165
- Simulation/Aivika/Dynamics.hs +31/−31
- Simulation/Aivika/Dynamics/Extra.hs +109/−109
- Simulation/Aivika/Dynamics/Memo.hs +159/−159
- Simulation/Aivika/Dynamics/Memo/Unboxed.hs +102/−102
- Simulation/Aivika/Dynamics/Random.hs +132/−132
- Simulation/Aivika/Event.hs +44/−44
- Simulation/Aivika/Generator.hs +220/−220
- Simulation/Aivika/Internal/Arrival.hs +39/−39
- Simulation/Aivika/Internal/Cont.hs +671/−671
- Simulation/Aivika/Internal/Dynamics.hs +214/−214
- Simulation/Aivika/Internal/Event.hs +399/−409
- Simulation/Aivika/Internal/Parameter.hs +261/−261
- Simulation/Aivika/Internal/Process.hs +643/−630
- Simulation/Aivika/Internal/Signal.hs +380/−380
- Simulation/Aivika/Internal/Simulation.hs +161/−161
- Simulation/Aivika/Internal/Specs.hs +223/−223
- Simulation/Aivika/Net.hs +249/−242
- Simulation/Aivika/Parameter.hs +38/−38
- Simulation/Aivika/Parameter/Random.hs +123/−123
- Simulation/Aivika/PriorityQueue.hs +163/−163
- Simulation/Aivika/Process.hs +84/−82
- Simulation/Aivika/Processor.hs +458/−458
- Simulation/Aivika/Processor/RoundRobbin.hs +58/−58
- Simulation/Aivika/Queue.hs +1097/−1097
- Simulation/Aivika/Queue/Infinite.hs +636/−636
- Simulation/Aivika/QueueStrategy.hs +187/−187
- Simulation/Aivika/Ref.hs +69/−69
- Simulation/Aivika/Ref/Plain.hs +53/−53
- Simulation/Aivika/Resource.hs +335/−335
- Simulation/Aivika/Results.hs +1931/−1884
- Simulation/Aivika/Results/IO.hs +476/−476
- Simulation/Aivika/Results/Locale.hs +377/−340
- Simulation/Aivika/Server.hs +507/−510
- Simulation/Aivika/Signal.hs +53/−53
- Simulation/Aivika/Simulation.hs +25/−25
- Simulation/Aivika/Specs.hs +25/−25
- Simulation/Aivika/Statistics.hs +417/−417
- Simulation/Aivika/Statistics/Accumulator.hs +44/−44
- Simulation/Aivika/Stream.hs +534/−534
- Simulation/Aivika/Stream/Random.hs +153/−153
- Simulation/Aivika/SystemDynamics.hs +777/−708
- Simulation/Aivika/Table.hs +64/−64
- Simulation/Aivika/Task.hs +179/−168
- Simulation/Aivika/Transform.hs +145/−126
- Simulation/Aivika/Transform/Extra.hs +54/−54
- Simulation/Aivika/Transform/Memo.hs +45/−45
- Simulation/Aivika/Transform/Memo/Unboxed.hs +41/−41
- Simulation/Aivika/Unboxed.hs +48/−48
- Simulation/Aivika/Var.hs +188/−188
- Simulation/Aivika/Var/Unboxed.hs +188/−188
- Simulation/Aivika/Vector.hs +183/−183
- Simulation/Aivika/Vector/Unboxed.hs +186/−186
- aivika.cabal +205/−203
- examples/BassDiffusion.hs +104/−104
- examples/ChemicalReaction.hs +30/−30
- examples/ChemicalReactionCircuit.hs +43/−43
- examples/FishBank.hs +60/−60
- examples/Furnace.hs +323/−323
- examples/InspectionAdjustmentStations.hs +161/−161
- examples/MachRep1.hs +67/−67
- examples/MachRep1EventDriven.hs +81/−81
- examples/MachRep1TimeDriven.hs +118/−118
- examples/MachRep2.hs +104/−104
- examples/MachRep3.hs +94/−94
- examples/QuarryOperations.hs +208/−0
- examples/TimeOut.hs +96/−96
- examples/TimeOutInt.hs +77/−77
- examples/TimeOutWait.hs +70/−70
- examples/WorkStationsInSeries.hs +139/−139
LICENSE view
@@ -1,30 +1,30 @@-Copyright (c) 2009, 2010, 2011, 2012, 2013, 2014 David Sorokin <david.sorokin@gmail.com> - -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -3. Neither the name of the author nor the names of his contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS -OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY -OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF -SUCH DAMAGE. +Copyright (c) 2009, 2010, 2011, 2012, 2013, 2014 David Sorokin <david.sorokin@gmail.com>++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions+are met:++1. Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++2. Redistributions in binary form must reproduce the above copyright+ notice, this list of conditions and the following disclaimer in the+ documentation and/or other materials provided with the distribution.++3. Neither the name of the author nor the names of his contributors+ may be used to endorse or promote products derived from this software+ without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE+ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF+SUCH DAMAGE.
Setup.lhs view
@@ -1,3 +1,3 @@-#!/usr/bin/env runhaskell -> import Distribution.Simple -> main = defaultMain +#!/usr/bin/env runhaskell+> import Distribution.Simple+> main = defaultMain
Simulation/Aivika.hs view
@@ -1,85 +1,87 @@- --- | --- Module : Simulation.Aivika --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.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.Arrival, - module Simulation.Aivika.Circuit, - module Simulation.Aivika.Cont, - module Simulation.Aivika.Dynamics, - module Simulation.Aivika.Dynamics.Extra, - module Simulation.Aivika.Dynamics.Memo.Unboxed, - module Simulation.Aivika.Dynamics.Random, - module Simulation.Aivika.Event, - module Simulation.Aivika.Generator, - module Simulation.Aivika.Net, - 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.Results, - module Simulation.Aivika.Results.Locale, - module Simulation.Aivika.Results.IO, - module Simulation.Aivika.Server, - module Simulation.Aivika.Signal, - module Simulation.Aivika.Simulation, - module Simulation.Aivika.Specs, - module Simulation.Aivika.Statistics, - module Simulation.Aivika.Statistics.Accumulator, - module Simulation.Aivika.Stream, - module Simulation.Aivika.Stream.Random, - module Simulation.Aivika.Task, - module Simulation.Aivika.Transform, - module Simulation.Aivika.Transform.Extra, - module Simulation.Aivika.Transform.Memo.Unboxed, - module Simulation.Aivika.Var.Unboxed) where - -import Simulation.Aivika.Agent -import Simulation.Aivika.Arrival -import Simulation.Aivika.Circuit -import Simulation.Aivika.Cont -import Simulation.Aivika.Dynamics -import Simulation.Aivika.Dynamics.Extra -import Simulation.Aivika.Dynamics.Memo.Unboxed -import Simulation.Aivika.Dynamics.Random -import Simulation.Aivika.Event -import Simulation.Aivika.Generator -import Simulation.Aivika.Net -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.Results -import Simulation.Aivika.Results.Locale -import Simulation.Aivika.Results.IO -import Simulation.Aivika.Server -import Simulation.Aivika.Signal -import Simulation.Aivika.Simulation -import Simulation.Aivika.Specs -import Simulation.Aivika.Statistics -import Simulation.Aivika.Statistics.Accumulator -import Simulation.Aivika.Stream -import Simulation.Aivika.Stream.Random -import Simulation.Aivika.Task -import Simulation.Aivika.Transform -import Simulation.Aivika.Transform.Extra -import Simulation.Aivika.Transform.Memo.Unboxed -import Simulation.Aivika.Var.Unboxed ++-- |+-- Module : Simulation.Aivika+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.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.Activity,+ module Simulation.Aivika.Agent,+ module Simulation.Aivika.Arrival,+ module Simulation.Aivika.Circuit,+ module Simulation.Aivika.Cont,+ module Simulation.Aivika.Dynamics,+ module Simulation.Aivika.Dynamics.Extra,+ module Simulation.Aivika.Dynamics.Memo.Unboxed,+ module Simulation.Aivika.Dynamics.Random,+ module Simulation.Aivika.Event,+ module Simulation.Aivika.Generator,+ module Simulation.Aivika.Net,+ 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.Results,+ module Simulation.Aivika.Results.Locale,+ module Simulation.Aivika.Results.IO,+ module Simulation.Aivika.Server,+ module Simulation.Aivika.Signal,+ module Simulation.Aivika.Simulation,+ module Simulation.Aivika.Specs,+ module Simulation.Aivika.Statistics,+ module Simulation.Aivika.Statistics.Accumulator,+ module Simulation.Aivika.Stream,+ module Simulation.Aivika.Stream.Random,+ module Simulation.Aivika.Task,+ module Simulation.Aivika.Transform,+ module Simulation.Aivika.Transform.Extra,+ module Simulation.Aivika.Transform.Memo.Unboxed,+ module Simulation.Aivika.Var.Unboxed) where++import Simulation.Aivika.Activity+import Simulation.Aivika.Agent+import Simulation.Aivika.Arrival+import Simulation.Aivika.Circuit+import Simulation.Aivika.Cont+import Simulation.Aivika.Dynamics+import Simulation.Aivika.Dynamics.Extra+import Simulation.Aivika.Dynamics.Memo.Unboxed+import Simulation.Aivika.Dynamics.Random+import Simulation.Aivika.Event+import Simulation.Aivika.Generator+import Simulation.Aivika.Net+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.Results+import Simulation.Aivika.Results.Locale+import Simulation.Aivika.Results.IO+import Simulation.Aivika.Server+import Simulation.Aivika.Signal+import Simulation.Aivika.Simulation+import Simulation.Aivika.Specs+import Simulation.Aivika.Statistics+import Simulation.Aivika.Statistics.Accumulator+import Simulation.Aivika.Stream+import Simulation.Aivika.Stream.Random+import Simulation.Aivika.Task+import Simulation.Aivika.Transform+import Simulation.Aivika.Transform.Extra+import Simulation.Aivika.Transform.Memo.Unboxed+import Simulation.Aivika.Var.Unboxed
+ Simulation/Aivika/Activity.hs view
@@ -0,0 +1,369 @@++-- |+-- Module : Simulation.Aivika.Activity+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- It models an activity that can be utilised. The activity is similar to a 'Server'+-- but destined for simulation within 'Net' computation.+module Simulation.Aivika.Activity+ (-- * Activity+ Activity,+ newActivity,+ newStateActivity,+ -- * Processing+ activityNet,+ -- * Activity Properties+ activityInitState,+ activityState,+ activityTotalUtilisationTime,+ activityTotalIdleTime,+ activityUtilisationTime,+ activityIdleTime,+ activityUtilisationFactor,+ activityIdleFactor,+ -- * Summary+ activitySummary,+ -- * Derived Signals for Properties+ activityStateChanged,+ activityStateChanged_,+ activityTotalUtilisationTimeChanged,+ activityTotalUtilisationTimeChanged_,+ activityTotalIdleTimeChanged,+ activityTotalIdleTimeChanged_,+ activityUtilisationTimeChanged,+ activityUtilisationTimeChanged_,+ activityIdleTimeChanged,+ activityIdleTimeChanged_,+ activityUtilisationFactorChanged,+ activityUtilisationFactorChanged_,+ activityIdleFactorChanged,+ activityIdleFactorChanged_,+ -- * Basic Signals+ activityUtilising,+ activityUtilised,+ -- * Overall Signal+ activityChanged_) where++import Data.IORef+import Data.Monoid++import Control.Monad.Trans+import Control.Arrow++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.Net+import Simulation.Aivika.Server+import Simulation.Aivika.Statistics++-- | Like 'Server' it models an activity that takes @a@ and provides @b@ having state @s@.+-- But unlike the former the activity is destined for simulation within 'Net' computation.+data Activity s a b =+ Activity { activityInitState :: s,+ -- ^ The initial state of the activity.+ activityStateRef :: IORef s,+ -- ^ The current state of the activity.+ activityProcess :: s -> a -> Process (s, b),+ -- ^ Provide @b@ by specified @a@.+ activityTotalUtilisationTimeRef :: IORef Double,+ -- ^ The counted total time of utilising the activity.+ activityTotalIdleTimeRef :: IORef Double,+ -- ^ The counted total time, when the activity was idle.+ activityUtilisationTimeRef :: IORef (SamplingStats Double),+ -- ^ The statistics for the utilisation time.+ activityIdleTimeRef :: IORef (SamplingStats Double),+ -- ^ The statistics for the time, when the activity was idle.+ activityUtilisingSource :: SignalSource a,+ -- ^ A signal raised when starting to utilise the activity.+ activityUtilisedSource :: SignalSource (a, b)+ -- ^ A signal raised when the activity has been utilised.+ }++-- | Create a new activity that can provide output @b@ by input @a@.+newActivity :: (a -> Process b)+ -- ^ provide an output by the specified input+ -> Simulation (Activity () a b)+newActivity provide =+ flip newStateActivity () $ \s a ->+ do b <- provide a+ return (s, b)++-- | Create a new activity that can provide output @b@ by input @a@+-- starting from state @s@.+newStateActivity :: (s -> a -> Process (s, b))+ -- ^ provide a new state and output by the specified + -- old state and input+ -> s+ -- ^ the initial state+ -> Simulation (Activity s a b)+newStateActivity provide state =+ do r0 <- liftIO $ newIORef state+ r1 <- liftIO $ newIORef 0+ r2 <- liftIO $ newIORef 0+ r3 <- liftIO $ newIORef emptySamplingStats+ r4 <- liftIO $ newIORef emptySamplingStats+ s1 <- newSignalSource+ s2 <- newSignalSource+ return Activity { activityInitState = state,+ activityStateRef = r0,+ activityProcess = provide,+ activityTotalUtilisationTimeRef = r1,+ activityTotalIdleTimeRef = r2,+ activityUtilisationTimeRef = r3,+ activityIdleTimeRef = r4,+ activityUtilisingSource = s1,+ activityUtilisedSource = s2 }++-- | Return a network computation for the specified activity.+--+-- The computation updates the internal state of the activity. The usual case is when +-- the computation is applied only once in a chain of data processing. Otherwise; +-- every time the computation is used, the state of the activity changes. Sometimes +-- it can be indeed useful if you want to aggregate the statistics for different +-- activities simultaneously, but it would be more preferable to avoid this.+--+-- If you connect different activity computations returned by this function in a chain +-- with help of '>>>' or other category combinator then this chain will act as one +-- whole, where the first activity will take a new task only after the last activity +-- finishes its current task and requests for the next one from the previous activity +-- in the chain. This is not always that thing you might need.+activityNet :: Activity s a b -> Net a b+activityNet act = Net $ loop (activityInitState act) Nothing+ where+ loop s r a =+ do t0 <- liftDynamics time+ liftEvent $+ do case r of+ Nothing -> return ()+ Just t' ->+ liftIO $+ do modifyIORef' (activityTotalIdleTimeRef act) (+ (t0 - t'))+ modifyIORef' (activityIdleTimeRef act) $+ addSamplingStats (t0 - t')+ triggerSignal (activityUtilisingSource act) a+ -- utilise the activity+ (s', b) <- activityProcess act s a+ t1 <- liftDynamics time+ liftEvent $+ do liftIO $+ do writeIORef (activityStateRef act) $! s'+ modifyIORef' (activityTotalUtilisationTimeRef act) (+ (t1 - t0))+ modifyIORef' (activityUtilisationTimeRef act) $+ addSamplingStats (t1 - t0)+ triggerSignal (activityUtilisedSource act) (a, b)+ return (b, Net $ loop s' (Just t1))++-- | Return the current state of the activity.+--+-- See also 'activityStateChanged' and 'activityStateChanged_'.+activityState :: Activity s a b -> Event s+activityState act =+ Event $ \p -> readIORef (activityStateRef act)+ +-- | Signal when the 'activityState' property value has changed.+activityStateChanged :: Activity s a b -> Signal s+activityStateChanged act =+ mapSignalM (const $ activityState act) (activityStateChanged_ act)+ +-- | Signal when the 'activityState' property value has changed.+activityStateChanged_ :: Activity s a b -> Signal ()+activityStateChanged_ act =+ mapSignal (const ()) (activityUtilised act)++-- | Return the counted total time when the activity was utilised.+--+-- The value returned changes discretely and it is usually delayed relative+-- to the current simulation time.+--+-- See also 'activityTotalUtilisationTimeChanged' and 'activityTotalUtilisationTimeChanged_'.+activityTotalUtilisationTime :: Activity s a b -> Event Double+activityTotalUtilisationTime act =+ Event $ \p -> readIORef (activityTotalUtilisationTimeRef act)+ +-- | Signal when the 'activityTotalUtilisationTime' property value has changed.+activityTotalUtilisationTimeChanged :: Activity s a b -> Signal Double+activityTotalUtilisationTimeChanged act =+ mapSignalM (const $ activityTotalUtilisationTime act) (activityTotalUtilisationTimeChanged_ act)+ +-- | Signal when the 'activityTotalUtilisationTime' property value has changed.+activityTotalUtilisationTimeChanged_ :: Activity s a b -> Signal ()+activityTotalUtilisationTimeChanged_ act =+ mapSignal (const ()) (activityUtilised act)++-- | Return the counted total time when the activity was idle.+--+-- The value returned changes discretely and it is usually delayed relative+-- to the current simulation time.+--+-- See also 'activityTotalIdleTimeChanged' and 'activityTotalIdleTimeChanged_'.+activityTotalIdleTime :: Activity s a b -> Event Double+activityTotalIdleTime act =+ Event $ \p -> readIORef (activityTotalIdleTimeRef act)+ +-- | Signal when the 'activityTotalIdleTime' property value has changed.+activityTotalIdleTimeChanged :: Activity s a b -> Signal Double+activityTotalIdleTimeChanged act =+ mapSignalM (const $ activityTotalIdleTime act) (activityTotalIdleTimeChanged_ act)+ +-- | Signal when the 'activityTotalIdleTime' property value has changed.+activityTotalIdleTimeChanged_ :: Activity s a b -> Signal ()+activityTotalIdleTimeChanged_ act =+ mapSignal (const ()) (activityUtilising act)++-- | Return the statistics for the time when the activity was utilised.+--+-- The value returned changes discretely and it is usually delayed relative+-- to the current simulation time.+--+-- See also 'activityUtilisationTimeChanged' and 'activityUtilisationTimeChanged_'.+activityUtilisationTime :: Activity s a b -> Event (SamplingStats Double)+activityUtilisationTime act =+ Event $ \p -> readIORef (activityUtilisationTimeRef act)+ +-- | Signal when the 'activityUtilisationTime' property value has changed.+activityUtilisationTimeChanged :: Activity s a b -> Signal (SamplingStats Double)+activityUtilisationTimeChanged act =+ mapSignalM (const $ activityUtilisationTime act) (activityUtilisationTimeChanged_ act)+ +-- | Signal when the 'activityUtilisationTime' property value has changed.+activityUtilisationTimeChanged_ :: Activity s a b -> Signal ()+activityUtilisationTimeChanged_ act =+ mapSignal (const ()) (activityUtilised act)++-- | Return the statistics for the time when the activity was idle.+--+-- The value returned changes discretely and it is usually delayed relative+-- to the current simulation time.+--+-- See also 'activityIdleTimeChanged' and 'activityIdleTimeChanged_'.+activityIdleTime :: Activity s a b -> Event (SamplingStats Double)+activityIdleTime act =+ Event $ \p -> readIORef (activityIdleTimeRef act)+ +-- | Signal when the 'activityIdleTime' property value has changed.+activityIdleTimeChanged :: Activity s a b -> Signal (SamplingStats Double)+activityIdleTimeChanged act =+ mapSignalM (const $ activityIdleTime act) (activityIdleTimeChanged_ act)+ +-- | Signal when the 'activityIdleTime' property value has changed.+activityIdleTimeChanged_ :: Activity s a b -> Signal ()+activityIdleTimeChanged_ act =+ mapSignal (const ()) (activityUtilising act)+ +-- | It returns the factor changing from 0 to 1, which estimates how often+-- the activity was utilised.+--+-- This factor is calculated as+--+-- @+-- totalUtilisationTime \/ (totalUtilisationTime + totalIdleTime)+-- @+--+-- As before in this module, the value returned changes discretely and+-- it is usually delayed relative to the current simulation time.+--+-- See also 'activityUtilisationFactorChanged' and 'activityUtilisationFactorChanged_'.+activityUtilisationFactor :: Activity s a b -> Event Double+activityUtilisationFactor act =+ Event $ \p ->+ do x1 <- readIORef (activityTotalUtilisationTimeRef act)+ x2 <- readIORef (activityTotalIdleTimeRef act)+ return (x1 / (x1 + x2))+ +-- | Signal when the 'activityUtilisationFactor' property value has changed.+activityUtilisationFactorChanged :: Activity s a b -> Signal Double+activityUtilisationFactorChanged act =+ mapSignalM (const $ activityUtilisationFactor act) (activityUtilisationFactorChanged_ act)+ +-- | Signal when the 'activityUtilisationFactor' property value has changed.+activityUtilisationFactorChanged_ :: Activity s a b -> Signal ()+activityUtilisationFactorChanged_ act =+ mapSignal (const ()) (activityUtilising act) <>+ mapSignal (const ()) (activityUtilised act)+ +-- | It returns the factor changing from 0 to 1, which estimates how often+-- the activity was idle.+--+-- This factor is calculated as+--+-- @+-- totalIdleTime \/ (totalUtilisationTime + totalIdleTime)+-- @+--+-- As before in this module, the value returned changes discretely and+-- it is usually delayed relative to the current simulation time.+--+-- See also 'activityIdleFactorChanged' and 'activityIdleFactorChanged_'.+activityIdleFactor :: Activity s a b -> Event Double+activityIdleFactor act =+ Event $ \p ->+ do x1 <- readIORef (activityTotalUtilisationTimeRef act)+ x2 <- readIORef (activityTotalIdleTimeRef act)+ return (x2 / (x1 + x2))+ +-- | Signal when the 'activityIdleFactor' property value has changed.+activityIdleFactorChanged :: Activity s a b -> Signal Double+activityIdleFactorChanged act =+ mapSignalM (const $ activityIdleFactor act) (activityIdleFactorChanged_ act)+ +-- | Signal when the 'activityIdleFactor' property value has changed.+activityIdleFactorChanged_ :: Activity s a b -> Signal ()+activityIdleFactorChanged_ act =+ mapSignal (const ()) (activityUtilising act) <>+ mapSignal (const ()) (activityUtilised act)++-- | Raised when starting to utilise the activity after a new input task is received.+activityUtilising :: Activity s a b -> Signal a+activityUtilising = publishSignal . activityUtilisingSource++-- | Raised when the activity has been utilised after the current task is processed.+activityUtilised :: Activity s a b -> Signal (a, b)+activityUtilised = publishSignal . activityUtilisedSource++-- | Signal whenever any property of the activity changes.+activityChanged_ :: Activity s a b -> Signal ()+activityChanged_ act =+ mapSignal (const ()) (activityUtilising act) <>+ mapSignal (const ()) (activityUtilised act)++-- | Return the summary for the activity with desciption of its+-- properties using the specified indent.+activitySummary :: Activity s a b -> Int -> Event ShowS+activitySummary act indent =+ Event $ \p ->+ do tx1 <- readIORef (activityTotalUtilisationTimeRef act)+ tx2 <- readIORef (activityTotalIdleTimeRef act)+ let xf1 = tx1 / (tx1 + tx2)+ xf2 = tx2 / (tx1 + tx2)+ xs1 <- readIORef (activityUtilisationTimeRef act)+ xs2 <- readIORef (activityIdleTimeRef act)+ let tab = replicate indent ' '+ return $+ showString tab .+ showString "total utilisation time = " . shows tx1 .+ showString "\n" .+ showString tab .+ showString "total idle time = " . shows tx2 .+ showString "\n" .+ showString tab .+ showString "utilisation factor (from 0 to 1) = " . shows xf1 .+ showString "\n" .+ showString tab .+ showString "idle factor (from 0 to 1) = " . shows xf2 .+ showString "\n" .+ showString tab .+ showString "utilisation time (locked while awaiting the input):\n\n" .+ samplingStatsSummary xs1 (2 + indent) .+ showString "\n\n" .+ showString tab .+ showString "idle time:\n\n" .+ samplingStatsSummary xs2 (2 + indent)
Simulation/Aivika/Agent.hs view
@@ -1,252 +1,252 @@- --- | --- Module : Simulation.Aivika.Agent --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- This module introduces basic entities for the agent-based modeling. --- -module Simulation.Aivika.Agent - (Agent, - AgentState, - newAgent, - newState, - newSubstate, - selectedState, - selectedStateChanged, - selectedStateChanged_, - selectState, - stateAgent, - stateParent, - addTimeout, - addTimer, - setStateActivation, - setStateDeactivation, - setStateTransition) where - -import Data.IORef -import Control.Monad - -import Simulation.Aivika.Internal.Specs -import Simulation.Aivika.Internal.Simulation -import Simulation.Aivika.Internal.Event -import Simulation.Aivika.Internal.Signal - --- --- Agent-based Modeling --- - --- | Represents an agent. -data Agent = Agent { agentModeRef :: IORef AgentMode, - agentStateRef :: IORef (Maybe AgentState), - agentStateChangedSource :: SignalSource (Maybe AgentState) } - --- | Represents the agent state. -data AgentState = AgentState { stateAgent :: Agent, - -- ^ Return the corresponded agent. - stateParent :: Maybe AgentState, - -- ^ Return the parent state or 'Nothing'. - stateActivateRef :: IORef (Event ()), - stateDeactivateRef :: IORef (Event ()), - stateTransitRef :: IORef (Event (Maybe AgentState)), - stateVersionRef :: IORef Int } - -data AgentMode = CreationMode - | TransientMode - | ProcessingMode - -instance Eq Agent where - x == y = agentStateRef x == agentStateRef y -- unique references - -instance Eq AgentState where - x == y = stateVersionRef x == stateVersionRef y -- unique references - -fullPath :: AgentState -> [AgentState] -> [AgentState] -fullPath st acc = - case stateParent st of - Nothing -> st : acc - Just st' -> fullPath st' (st : acc) - -partitionPath :: [AgentState] -> [AgentState] -> ([AgentState], [AgentState]) -partitionPath path1 path2 = - case (path1, path2) of - (h1 : t1, [h2]) | h1 == h2 -> - (reverse path1, path2) - (h1 : t1, h2 : t2) | h1 == h2 -> - partitionPath t1 t2 - _ -> - (reverse path1, path2) - -findPath :: Maybe AgentState -> AgentState -> ([AgentState], [AgentState]) -findPath Nothing target = ([], fullPath target []) -findPath (Just source) target - | stateAgent source /= stateAgent target = - error "Different agents: findPath." - | otherwise = - partitionPath path1 path2 - where - path1 = fullPath source [] - path2 = fullPath target [] - -traversePath :: Maybe AgentState -> AgentState -> Event () -traversePath source target = - let (path1, path2) = findPath source target - agent = stateAgent target - activate st p = invokeEvent p =<< readIORef (stateActivateRef st) - deactivate st p = invokeEvent p =<< readIORef (stateDeactivateRef st) - transit st p = invokeEvent p =<< readIORef (stateTransitRef st) - continue st p = invokeEvent p $ traversePath (Just target) st - in Event $ \p -> - unless (null path1 && null path2) $ - do writeIORef (agentModeRef agent) TransientMode - forM_ path1 $ \st -> - do writeIORef (agentStateRef agent) (Just st) - deactivate st p - -- it makes all timeout and timer handlers outdated - modifyIORef (stateVersionRef st) (1 +) - forM_ path2 $ \st -> - do writeIORef (agentStateRef agent) (Just st) - activate st p - st' <- transit target p - case st' of - Nothing -> - do writeIORef (agentModeRef agent) ProcessingMode - triggerAgentStateChanged p agent - Just st' -> - continue st' p - --- | Add to the state a timeout handler that will be actuated --- in the specified time period if the state will remain active. -addTimeout :: AgentState -> Double -> Event () -> Event () -addTimeout st dt action = - Event $ \p -> - do v <- readIORef (stateVersionRef st) - let m1 = Event $ \p -> - do v' <- readIORef (stateVersionRef st) - when (v == v') $ - invokeEvent p action - m2 = enqueueEvent (pointTime p + dt) m1 - invokeEvent p m2 - --- | Add to the state a timer handler that will be actuated --- in the specified time period and then repeated again many times, --- while the state remains active. -addTimer :: AgentState -> Event Double -> Event () -> Event () -addTimer st dt action = - Event $ \p -> - do v <- readIORef (stateVersionRef st) - let m1 = Event $ \p -> - do v' <- readIORef (stateVersionRef st) - when (v == v') $ - do invokeEvent p m2 - invokeEvent p action - m2 = Event $ \p -> - do dt' <- invokeEvent p dt - invokeEvent p $ enqueueEvent (pointTime p + dt') m1 - invokeEvent p m2 - --- | Create a new state. -newState :: Agent -> Simulation AgentState -newState agent = - Simulation $ \r -> - do aref <- newIORef $ return () - dref <- newIORef $ return () - tref <- newIORef $ return Nothing - vref <- newIORef 0 - return AgentState { stateAgent = agent, - stateParent = Nothing, - stateActivateRef = aref, - stateDeactivateRef = dref, - stateTransitRef = tref, - stateVersionRef = vref } - --- | Create a child state. -newSubstate :: AgentState -> Simulation AgentState -newSubstate parent = - Simulation $ \r -> - do let agent = stateAgent parent - aref <- newIORef $ return () - dref <- newIORef $ return () - tref <- newIORef $ return Nothing - vref <- newIORef 0 - return AgentState { stateAgent = agent, - stateParent = Just parent, - stateActivateRef= aref, - stateDeactivateRef = dref, - stateTransitRef = tref, - stateVersionRef = vref } - --- | Create an agent. -newAgent :: Simulation Agent -newAgent = - Simulation $ \r -> - do modeRef <- newIORef CreationMode - stateRef <- newIORef Nothing - stateChangedSource <- invokeSimulation r newSignalSource - return Agent { agentModeRef = modeRef, - agentStateRef = stateRef, - agentStateChangedSource = stateChangedSource } - --- | Return the selected active state. -selectedState :: Agent -> Event (Maybe AgentState) -selectedState agent = - Event $ \p -> readIORef (agentStateRef agent) - --- | Select the state. The activation and selection are repeated while --- there is the transition state defined by 'setStateTransition'. -selectState :: AgentState -> Event () -selectState st = - Event $ \p -> - do let agent = stateAgent st - mode <- readIORef (agentModeRef agent) - case mode of - CreationMode -> - do x0 <- readIORef (agentStateRef agent) - invokeEvent p $ traversePath x0 st - TransientMode -> - error $ - "Use the setStateTransition function to define " ++ - "the transition state: activateState." - ProcessingMode -> - do x0 @ (Just st0) <- readIORef (agentStateRef agent) - invokeEvent p $ traversePath x0 st - --- | Set the activation computation for the specified state. -setStateActivation :: AgentState -> Event () -> Simulation () -setStateActivation st action = - Simulation $ \r -> - writeIORef (stateActivateRef st) action - --- | Set the deactivation computation for the specified state. -setStateDeactivation :: AgentState -> Event () -> Simulation () -setStateDeactivation st action = - Simulation $ \r -> - writeIORef (stateDeactivateRef st) action - --- | Set the transition state which will be next and which is used only --- 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 = - Simulation $ \r -> - writeIORef (stateTransitRef st) action - --- | Trigger the signal when the agent state changes. -triggerAgentStateChanged :: Point -> Agent -> IO () -triggerAgentStateChanged p agent = - do st <- readIORef (agentStateRef agent) - invokeEvent p $ triggerSignal (agentStateChangedSource agent) st - --- | 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 selected state. -selectedStateChanged_ :: Agent -> Signal () -selectedStateChanged_ agent = - mapSignal (const ()) $ selectedStateChanged agent ++-- |+-- Module : Simulation.Aivika.Agent+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- This module introduces basic entities for the agent-based modeling.+--+module Simulation.Aivika.Agent+ (Agent,+ AgentState,+ newAgent,+ newState,+ newSubstate,+ selectedState,+ selectedStateChanged,+ selectedStateChanged_,+ selectState,+ stateAgent,+ stateParent,+ addTimeout,+ addTimer,+ setStateActivation,+ setStateDeactivation,+ setStateTransition) where++import Data.IORef+import Control.Monad++import Simulation.Aivika.Internal.Specs+import Simulation.Aivika.Internal.Simulation+import Simulation.Aivika.Internal.Event+import Simulation.Aivika.Internal.Signal++--+-- Agent-based Modeling+--++-- | Represents an agent.+data Agent = Agent { agentModeRef :: IORef AgentMode,+ agentStateRef :: IORef (Maybe AgentState), + agentStateChangedSource :: SignalSource (Maybe AgentState) }++-- | Represents the agent state.+data AgentState = AgentState { stateAgent :: Agent,+ -- ^ Return the corresponded agent.+ stateParent :: Maybe AgentState,+ -- ^ Return the parent state or 'Nothing'.+ stateActivateRef :: IORef (Event ()),+ stateDeactivateRef :: IORef (Event ()),+ stateTransitRef :: IORef (Event (Maybe AgentState)),+ stateVersionRef :: IORef Int }+ +data AgentMode = CreationMode+ | TransientMode+ | ProcessingMode+ +instance Eq Agent where+ x == y = agentStateRef x == agentStateRef y -- unique references+ +instance Eq AgentState where+ x == y = stateVersionRef x == stateVersionRef y -- unique references++fullPath :: AgentState -> [AgentState] -> [AgentState]+fullPath st acc =+ case stateParent st of+ Nothing -> st : acc+ Just st' -> fullPath st' (st : acc)++partitionPath :: [AgentState] -> [AgentState] -> ([AgentState], [AgentState])+partitionPath path1 path2 =+ case (path1, path2) of+ (h1 : t1, [h2]) | h1 == h2 -> + (reverse path1, path2)+ (h1 : t1, h2 : t2) | h1 == h2 -> + partitionPath t1 t2+ _ ->+ (reverse path1, path2)++findPath :: Maybe AgentState -> AgentState -> ([AgentState], [AgentState])+findPath Nothing target = ([], fullPath target [])+findPath (Just source) target+ | stateAgent source /= stateAgent target =+ error "Different agents: findPath."+ | otherwise =+ partitionPath path1 path2+ where+ path1 = fullPath source []+ path2 = fullPath target []++traversePath :: Maybe AgentState -> AgentState -> Event ()+traversePath source target =+ let (path1, path2) = findPath source target+ agent = stateAgent target+ activate st p = invokeEvent p =<< readIORef (stateActivateRef st)+ deactivate st p = invokeEvent p =<< readIORef (stateDeactivateRef st)+ transit st p = invokeEvent p =<< readIORef (stateTransitRef st)+ continue st p = invokeEvent p $ traversePath (Just target) st+ in Event $ \p ->+ unless (null path1 && null path2) $+ do writeIORef (agentModeRef agent) TransientMode+ forM_ path1 $ \st ->+ do writeIORef (agentStateRef agent) (Just st)+ deactivate st p+ -- it makes all timeout and timer handlers outdated+ modifyIORef (stateVersionRef st) (1 +)+ forM_ path2 $ \st ->+ do writeIORef (agentStateRef agent) (Just st)+ activate st p+ st' <- transit target p+ case st' of+ Nothing ->+ do writeIORef (agentModeRef agent) ProcessingMode+ triggerAgentStateChanged p agent+ Just st' ->+ continue st' p++-- | Add to the state a timeout handler that will be actuated +-- in the specified time period if the state will remain active.+addTimeout :: AgentState -> Double -> Event () -> Event ()+addTimeout st dt action =+ Event $ \p ->+ do v <- readIORef (stateVersionRef st)+ let m1 = Event $ \p ->+ do v' <- readIORef (stateVersionRef st)+ when (v == v') $+ invokeEvent p action+ m2 = enqueueEvent (pointTime p + dt) m1+ invokeEvent p m2++-- | Add to the state a timer handler that will be actuated+-- in the specified time period and then repeated again many times,+-- while the state remains active.+addTimer :: AgentState -> Event Double -> Event () -> Event ()+addTimer st dt action =+ Event $ \p ->+ do v <- readIORef (stateVersionRef st)+ let m1 = Event $ \p ->+ do v' <- readIORef (stateVersionRef st)+ when (v == v') $+ do invokeEvent p m2+ invokeEvent p action+ m2 = Event $ \p ->+ do dt' <- invokeEvent p dt+ invokeEvent p $ enqueueEvent (pointTime p + dt') m1+ invokeEvent p m2++-- | Create a new state.+newState :: Agent -> Simulation AgentState+newState agent =+ Simulation $ \r ->+ do aref <- newIORef $ return ()+ dref <- newIORef $ return ()+ tref <- newIORef $ return Nothing+ vref <- newIORef 0+ return AgentState { stateAgent = agent,+ stateParent = Nothing,+ stateActivateRef = aref,+ stateDeactivateRef = dref,+ stateTransitRef = tref,+ stateVersionRef = vref }++-- | Create a child state.+newSubstate :: AgentState -> Simulation AgentState+newSubstate parent =+ Simulation $ \r ->+ do let agent = stateAgent parent + aref <- newIORef $ return ()+ dref <- newIORef $ return ()+ tref <- newIORef $ return Nothing+ vref <- newIORef 0+ return AgentState { stateAgent = agent,+ stateParent = Just parent,+ stateActivateRef= aref,+ stateDeactivateRef = dref,+ stateTransitRef = tref,+ stateVersionRef = vref }++-- | Create an agent.+newAgent :: Simulation Agent+newAgent =+ Simulation $ \r ->+ do modeRef <- newIORef CreationMode+ stateRef <- newIORef Nothing+ stateChangedSource <- invokeSimulation r newSignalSource+ return Agent { agentModeRef = modeRef,+ agentStateRef = stateRef, + agentStateChangedSource = stateChangedSource }++-- | Return the selected active state.+selectedState :: Agent -> Event (Maybe AgentState)+selectedState agent =+ Event $ \p -> readIORef (agentStateRef agent)+ +-- | Select the state. The activation and selection are repeated while+-- there is the transition state defined by 'setStateTransition'.+selectState :: AgentState -> Event ()+selectState st =+ Event $ \p ->+ do let agent = stateAgent st+ mode <- readIORef (agentModeRef agent)+ case mode of+ CreationMode ->+ do x0 <- readIORef (agentStateRef agent)+ invokeEvent p $ traversePath x0 st+ TransientMode ->+ error $+ "Use the setStateTransition function to define " +++ "the transition state: activateState."+ ProcessingMode ->+ do x0 @ (Just st0) <- readIORef (agentStateRef agent)+ invokeEvent p $ traversePath x0 st++-- | Set the activation computation for the specified state.+setStateActivation :: AgentState -> Event () -> Simulation ()+setStateActivation st action =+ Simulation $ \r ->+ writeIORef (stateActivateRef st) action+ +-- | Set the deactivation computation for the specified state.+setStateDeactivation :: AgentState -> Event () -> Simulation ()+setStateDeactivation st action =+ Simulation $ \r ->+ writeIORef (stateDeactivateRef st) action+ +-- | Set the transition state which will be next and which is used only+-- 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 =+ Simulation $ \r ->+ writeIORef (stateTransitRef st) action+ +-- | Trigger the signal when the agent state changes.+triggerAgentStateChanged :: Point -> Agent -> IO ()+triggerAgentStateChanged p agent =+ do st <- readIORef (agentStateRef agent)+ invokeEvent p $ triggerSignal (agentStateChangedSource agent) st++-- | 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 selected state.+selectedStateChanged_ :: Agent -> Signal ()+selectedStateChanged_ agent =+ mapSignal (const ()) $ selectedStateChanged agent
Simulation/Aivika/Arrival.hs view
@@ -1,77 +1,77 @@- --- | --- Module : Simulation.Aivika.Arrival --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- This module defines the types and functions for working with the events --- that can represent something that arrive from outside the model, or --- represent other things which computation is delayed and hence is not synchronized. --- --- Therefore, the additional information is provided about the time and delay of arrival. - -module Simulation.Aivika.Arrival - (Arrival(..), - ArrivalTimer, - newArrivalTimer, - arrivalTimerProcessor, - arrivalProcessingTime, - arrivalProcessingTimeChanged, - arrivalProcessingTimeChanged_) where - -import Control.Monad -import Control.Monad.Trans - -import Simulation.Aivika.Simulation -import Simulation.Aivika.Dynamics -import Simulation.Aivika.Event -import Simulation.Aivika.Processor -import Simulation.Aivika.Stream -import Simulation.Aivika.Statistics -import Simulation.Aivika.Ref -import Simulation.Aivika.Signal -import Simulation.Aivika.Internal.Arrival - --- | Accumulates the statistics about that how long the arrived events are processed. -data ArrivalTimer = - ArrivalTimer { arrivalProcessingTimeRef :: Ref (SamplingStats Double), - arrivalProcessingTimeChangedSource :: SignalSource () } - --- | Create a new timer that measures how long the arrived events are processed. -newArrivalTimer :: Simulation ArrivalTimer -newArrivalTimer = - do r <- newRef emptySamplingStats - s <- newSignalSource - return ArrivalTimer { arrivalProcessingTimeRef = r, - arrivalProcessingTimeChangedSource = s } - --- | Return the statistics about that how long the arrived events were processed. -arrivalProcessingTime :: ArrivalTimer -> Event (SamplingStats Double) -arrivalProcessingTime = readRef . arrivalProcessingTimeRef - --- | Return a signal raised when the the processing time statistics changes. -arrivalProcessingTimeChanged :: ArrivalTimer -> Signal (SamplingStats Double) -arrivalProcessingTimeChanged timer = - mapSignalM (const $ arrivalProcessingTime timer) (arrivalProcessingTimeChanged_ timer) - --- | Return a signal raised when the the processing time statistics changes. -arrivalProcessingTimeChanged_ :: ArrivalTimer -> Signal () -arrivalProcessingTimeChanged_ timer = - publishSignal (arrivalProcessingTimeChangedSource timer) - --- | Return a processor that actually measures how much time has passed from --- the time of arriving the events. -arrivalTimerProcessor :: ArrivalTimer -> Processor (Arrival a) (Arrival a) -arrivalTimerProcessor timer = - Processor $ \xs -> Cons $ loop xs where - loop xs = - do (a, xs) <- runStream xs - liftEvent $ - do t <- liftDynamics time - modifyRef (arrivalProcessingTimeRef timer) $ - addSamplingStats (t - arrivalTime a) - triggerSignal (arrivalProcessingTimeChangedSource timer) () - return (a, Cons $ loop xs) ++-- |+-- Module : Simulation.Aivika.Arrival+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- This module defines the types and functions for working with the events+-- that can represent something that arrive from outside the model, or+-- represent other things which computation is delayed and hence is not synchronized.+--+-- Therefore, the additional information is provided about the time and delay of arrival.++module Simulation.Aivika.Arrival+ (Arrival(..),+ ArrivalTimer,+ newArrivalTimer,+ arrivalTimerProcessor,+ arrivalProcessingTime,+ arrivalProcessingTimeChanged,+ arrivalProcessingTimeChanged_) where++import Control.Monad+import Control.Monad.Trans++import Simulation.Aivika.Simulation+import Simulation.Aivika.Dynamics+import Simulation.Aivika.Event+import Simulation.Aivika.Processor+import Simulation.Aivika.Stream+import Simulation.Aivika.Statistics+import Simulation.Aivika.Ref+import Simulation.Aivika.Signal+import Simulation.Aivika.Internal.Arrival++-- | Accumulates the statistics about that how long the arrived events are processed.+data ArrivalTimer =+ ArrivalTimer { arrivalProcessingTimeRef :: Ref (SamplingStats Double),+ arrivalProcessingTimeChangedSource :: SignalSource () }++-- | Create a new timer that measures how long the arrived events are processed.+newArrivalTimer :: Simulation ArrivalTimer+newArrivalTimer =+ do r <- newRef emptySamplingStats+ s <- newSignalSource+ return ArrivalTimer { arrivalProcessingTimeRef = r,+ arrivalProcessingTimeChangedSource = s }++-- | Return the statistics about that how long the arrived events were processed.+arrivalProcessingTime :: ArrivalTimer -> Event (SamplingStats Double)+arrivalProcessingTime = readRef . arrivalProcessingTimeRef++-- | Return a signal raised when the the processing time statistics changes.+arrivalProcessingTimeChanged :: ArrivalTimer -> Signal (SamplingStats Double)+arrivalProcessingTimeChanged timer =+ mapSignalM (const $ arrivalProcessingTime timer) (arrivalProcessingTimeChanged_ timer)++-- | Return a signal raised when the the processing time statistics changes.+arrivalProcessingTimeChanged_ :: ArrivalTimer -> Signal ()+arrivalProcessingTimeChanged_ timer =+ publishSignal (arrivalProcessingTimeChangedSource timer)++-- | Return a processor that actually measures how much time has passed from+-- the time of arriving the events.+arrivalTimerProcessor :: ArrivalTimer -> Processor (Arrival a) (Arrival a)+arrivalTimerProcessor timer =+ Processor $ \xs -> Cons $ loop xs where+ loop xs =+ do (a, xs) <- runStream xs+ liftEvent $+ do t <- liftDynamics time+ modifyRef (arrivalProcessingTimeRef timer) $+ addSamplingStats (t - arrivalTime a)+ triggerSignal (arrivalProcessingTimeChangedSource timer) ()+ return (a, Cons $ loop xs)
Simulation/Aivika/Circuit.hs view
@@ -1,379 +1,494 @@- -{-# LANGUAGE RecursiveDo, Arrows #-} - --- | --- Module : Simulation.Aivika.Circuit --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- It represents a circuit synchronized with the event queue. --- Also it allows creating the recursive links with help of --- the proc-notation. --- --- The implementation is based on the <http://en.wikibooks.org/wiki/Haskell/Arrow_tutorial Arrow Tutorial>. --- -module Simulation.Aivika.Circuit - (-- * The Circuit Arrow - Circuit(..), - -- * Circuit Primitives - arrCircuit, - accumCircuit, - -- * The Arrival Circuit - arrivalCircuit, - -- * Delaying the Circuit - delayCircuit, - -- * The Time Circuit - timeCircuit, - -- * Conditional Computation - (<?<), - (>?>), - filterCircuit, - filterCircuitM, - neverCircuit, - -- * Converting to Signals and Processors - circuitSignaling, - circuitProcessor, - -- * Integrals and Difference Equations - integCircuit, - sumCircuit, - -- * The Circuit Transform - circuitTransform) where - -import qualified Control.Category as C -import Control.Arrow -import Control.Monad.Fix - -import Data.IORef - -import Simulation.Aivika.Internal.Arrival -import Simulation.Aivika.Internal.Specs -import Simulation.Aivika.Internal.Simulation -import Simulation.Aivika.Internal.Dynamics -import Simulation.Aivika.Internal.Event -import Simulation.Aivika.Dynamics.Memo -import Simulation.Aivika.Transform -import Simulation.Aivika.SystemDynamics -import Simulation.Aivika.Signal -import Simulation.Aivika.Stream -import Simulation.Aivika.Processor - --- | Represents a circuit synchronized with the event queue. --- Besides, it allows creating the recursive links with help of --- the proc-notation. --- -newtype Circuit a b = - Circuit { runCircuit :: a -> Event (b, Circuit a b) - -- ^ Run the circuit. - } - -instance C.Category Circuit where - - id = Circuit $ \a -> return (a, C.id) - - (.) = dot - where - (Circuit g) `dot` (Circuit f) = - Circuit $ \a -> - Event $ \p -> - do (b, cir1) <- invokeEvent p (f a) - (c, cir2) <- invokeEvent p (g b) - return (c, cir2 `dot` cir1) - -instance Arrow Circuit where - - arr f = Circuit $ \a -> return (f a, arr f) - - first (Circuit f) = - Circuit $ \(b, d) -> - Event $ \p -> - do (c, cir) <- invokeEvent p (f b) - return ((c, d), first cir) - - second (Circuit f) = - Circuit $ \(d, b) -> - Event $ \p -> - do (c, cir) <- invokeEvent p (f b) - return ((d, c), second cir) - - (Circuit f) *** (Circuit g) = - Circuit $ \(b, b') -> - Event $ \p -> - do (c, cir1) <- invokeEvent p (f b) - (c', cir2) <- invokeEvent p (g b') - return ((c, c'), cir1 *** cir2) - - (Circuit f) &&& (Circuit g) = - Circuit $ \b -> - Event $ \p -> - do (c, cir1) <- invokeEvent p (f b) - (c', cir2) <- invokeEvent p (g b) - return ((c, c'), cir1 &&& cir2) - -instance ArrowLoop Circuit where - - loop (Circuit f) = - Circuit $ \b -> - Event $ \p -> - do rec ((c, d), cir) <- invokeEvent p (f (b, d)) - return (c, loop cir) - -instance ArrowChoice Circuit where - - left x@(Circuit f) = - Circuit $ \ebd -> - Event $ \p -> - case ebd of - Left b -> - do (c, cir) <- invokeEvent p (f b) - return (Left c, left cir) - Right d -> - return (Right d, left x) - - right x@(Circuit f) = - Circuit $ \edb -> - Event $ \p -> - case edb of - Right b -> - do (c, cir) <- invokeEvent p (f b) - return (Right c, right cir) - Left d -> - return (Left d, right x) - - x@(Circuit f) +++ y@(Circuit g) = - Circuit $ \ebb' -> - Event $ \p -> - case ebb' of - Left b -> - do (c, cir1) <- invokeEvent p (f b) - return (Left c, cir1 +++ y) - Right b' -> - do (c', cir2) <- invokeEvent p (g b') - return (Right c', x +++ cir2) - - x@(Circuit f) ||| y@(Circuit g) = - Circuit $ \ebc -> - Event $ \p -> - case ebc of - Left b -> - do (d, cir1) <- invokeEvent p (f b) - return (d, cir1 ||| y) - Right b' -> - do (d, cir2) <- invokeEvent p (g b') - return (d, x ||| cir2) - --- | Get a signal transform by the specified circuit. -circuitSignaling :: Circuit a b -> Signal a -> Signal b -circuitSignaling (Circuit cir) sa = - Signal { handleSignal = \f -> - Event $ \p -> - do r <- newIORef cir - invokeEvent p $ - handleSignal sa $ \a -> - Event $ \p -> - do cir <- readIORef r - (b, Circuit cir') <- invokeEvent p (cir a) - writeIORef r cir' - invokeEvent p (f b) } - --- | Transform the circuit to a processor. -circuitProcessor :: Circuit a b -> Processor a b -circuitProcessor (Circuit cir) = Processor $ \sa -> - Cons $ - do (a, xs) <- runStream sa - (b, cir') <- liftEvent (cir a) - let f = runProcessor (circuitProcessor cir') - return (b, f xs) - --- | Create a simple circuit by the specified handling function --- that runs the computation for each input value to get an output. -arrCircuit :: (a -> Event b) -> Circuit a b -arrCircuit f = - let x = - Circuit $ \a -> - Event $ \p -> - do b <- invokeEvent p (f a) - return (b, x) - in x - --- | Accumulator that outputs a value determined by the supplied function. -accumCircuit :: (acc -> a -> Event (acc, b)) -> acc -> Circuit a b -accumCircuit f acc = - Circuit $ \a -> - Event $ \p -> - do (acc', b) <- invokeEvent p (f acc a) - return (b, accumCircuit f acc') - --- | A circuit that adds the information about the time points at which --- the values were received. -arrivalCircuit :: Circuit a (Arrival a) -arrivalCircuit = - let loop t0 = - Circuit $ \a -> - Event $ \p -> - let t = pointTime p - b = Arrival { arrivalValue = a, - arrivalTime = t, - arrivalDelay = - case t0 of - Nothing -> Nothing - Just t0 -> Just (t - t0) } - in return (b, loop $ Just t) - in loop Nothing - --- | Delay the input by one step using the specified initial value. -delayCircuit :: a -> Circuit a a -delayCircuit a0 = - Circuit $ \a -> - return (a0, delayCircuit a) - --- | A circuit that returns the current modeling time. -timeCircuit :: Circuit a Double -timeCircuit = - Circuit $ \a -> - Event $ \p -> - return (pointTime p, timeCircuit) - --- | Like '>>>' but processes only the represented events. -(>?>) :: Circuit a (Maybe b) - -- ^ whether there is an event - -> Circuit b c - -- ^ process the event if it presents - -> Circuit a (Maybe c) - -- ^ the resulting circuit that processes only the represented events -whether >?> process = - Circuit $ \a -> - Event $ \p -> - do (b, whether') <- invokeEvent p (runCircuit whether a) - case b of - Nothing -> - return (Nothing, whether' >?> process) - Just b -> - do (c, process') <- invokeEvent p (runCircuit process b) - return (Just c, whether' >?> process') - --- | Like '<<<' but processes only the represented events. -(<?<) :: Circuit b c - -- ^ process the event if it presents - -> Circuit a (Maybe b) - -- ^ whether there is an event - -> Circuit a (Maybe c) - -- ^ the resulting circuit that processes only the represented events -(<?<) = flip (>?>) - --- | Filter the circuit, calculating only those parts of the circuit that satisfy --- the specified predicate. -filterCircuit :: (a -> Bool) -> Circuit a b -> Circuit a (Maybe b) -filterCircuit pred = filterCircuitM (return . pred) - --- | Filter the circuit within the 'Event' computation, calculating only those parts --- of the circuit that satisfy the specified predicate. -filterCircuitM :: (a -> Event Bool) -> Circuit a b -> Circuit a (Maybe b) -filterCircuitM pred cir = - Circuit $ \a -> - Event $ \p -> - do x <- invokeEvent p (pred a) - if x - then do (b, cir') <- invokeEvent p (runCircuit cir a) - return (Just b, filterCircuitM pred cir') - else return (Nothing, filterCircuitM pred cir) - --- | The source of events that never occur. -neverCircuit :: Circuit a (Maybe b) -neverCircuit = - Circuit $ \a -> return (Nothing, neverCircuit) - --- | An approximation of the integral using Euler's method. --- --- This function can be rather inaccurate as it depends on --- the time points at wich the 'Circuit' computation is actuated. --- Also Euler's method per se is not most accurate, although simple --- enough for implementation. --- --- Consider using the 'integ' function whenever possible. --- That function can integrate with help of the Runge-Kutta method by --- the specified integration time points that are passed in the simulation --- specs to every 'Simulation', when running the model. --- --- At the same time, the 'integCircuit' function has no mutable state --- unlike the former. The latter consumes less memory but at the cost --- of inaccuracy and relatively more slow simulation, had we requested --- the integral in the same time points. --- --- Regarding the recursive equations, the both functions allow defining them --- but whithin different computations (either with help of the recursive --- do-notation or the proc-notation). -integCircuit :: Double - -- ^ the initial value - -> Circuit Double Double - -- ^ map the derivative to an integral -integCircuit init = start - where - start = - Circuit $ \a -> - Event $ \p -> - do let t = pointTime p - return (init, next t init a) - next t0 v0 a0 = - Circuit $ \a -> - Event $ \p -> - do let t = pointTime p - dt = t - t0 - v = v0 + a0 * dt - v `seq` return (v, next t v a) - --- | A sum of differences starting from the specified initial value. --- --- Consider using the more accurate 'diffsum' function whener possible as --- it is calculated in every integration time point specified by specs --- passed in to every 'Simulation', when running the model. --- --- At the same time, the 'sumCircuit' function has no mutable state and --- it consumes less memory than the former. --- --- Regarding the recursive equations, the both functions allow defining them --- but whithin different computations (either with help of the recursive --- do-notation or the proc-notation). -sumCircuit :: Num a => - a - -- ^ the initial value - -> Circuit a a - -- ^ map the difference to a sum -sumCircuit init = start - where - start = - Circuit $ \a -> - Event $ \p -> - return (init, next init a) - next v0 a0 = - Circuit $ \a -> - Event $ \p -> - do let v = v0 + a0 - v `seq` return (v, next v a) - --- | Approximate the circuit as a transform of time varying function, --- calculating the values in the integration time points and then --- interpolating in all other time points. The resulting transform --- computation is synchronized with the event queue. --- --- This procedure consumes memory as the underlying memoization allocates --- an array to store the calculated values. -circuitTransform :: Circuit a b -> Transform a b -circuitTransform cir = Transform start - where - start m = - Simulation $ \r -> - do ref <- newIORef cir - invokeSimulation r $ - memo0Dynamics (next ref m) - next ref m = - Dynamics $ \p -> - do a <- invokeDynamics p m - cir <- readIORef ref - (b, cir') <- - invokeDynamics p $ - runEvent (runCircuit cir a) - writeIORef ref cir' - return b ++{-# LANGUAGE RecursiveDo, Arrows #-}++-- |+-- Module : Simulation.Aivika.Circuit+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- It represents a circuit synchronized with the event queue.+-- Also it allows creating the recursive links with help of+-- the proc-notation.+--+-- The implementation is based on the <http://en.wikibooks.org/wiki/Haskell/Arrow_tutorial Arrow Tutorial>.+--+module Simulation.Aivika.Circuit+ (-- * The Circuit Arrow+ Circuit(..),+ iterateCircuitInIntegTimes,+ iterateCircuitInIntegTimes_,+ iterateCircuitInTimes,+ iterateCircuitInTimes_,+ -- * Circuit Primitives+ arrCircuit,+ accumCircuit,+ -- * The Arrival Circuit+ arrivalCircuit,+ -- * Delaying the Circuit+ delayCircuit,+ -- * The Time Circuit+ timeCircuit,+ -- * Conditional Computation+ (<?<),+ (>?>),+ filterCircuit,+ filterCircuitM,+ neverCircuit,+ -- * Converting to Signals and Processors+ circuitSignaling,+ circuitProcessor,+ -- * Integrals and Difference Equations+ integCircuit,+ integCircuitEither,+ sumCircuit,+ sumCircuitEither,+ -- * The Circuit Transform+ circuitTransform) where++import qualified Control.Category as C+import Control.Arrow+import Control.Monad.Fix++import Data.IORef++import Simulation.Aivika.Internal.Arrival+import Simulation.Aivika.Internal.Specs+import Simulation.Aivika.Internal.Simulation+import Simulation.Aivika.Internal.Dynamics+import Simulation.Aivika.Internal.Event+import Simulation.Aivika.Dynamics.Memo+import Simulation.Aivika.Transform+import Simulation.Aivika.SystemDynamics+import Simulation.Aivika.Signal+import Simulation.Aivika.Stream+import Simulation.Aivika.Process+import Simulation.Aivika.Processor+import Simulation.Aivika.Task++-- | Represents a circuit synchronized with the event queue.+-- Besides, it allows creating the recursive links with help of+-- the proc-notation.+--+newtype Circuit a b =+ Circuit { runCircuit :: a -> Event (b, Circuit a b)+ -- ^ Run the circuit.+ }++instance C.Category Circuit where++ id = Circuit $ \a -> return (a, C.id)++ (.) = dot+ where + (Circuit g) `dot` (Circuit f) =+ Circuit $ \a ->+ Event $ \p ->+ do (b, cir1) <- invokeEvent p (f a)+ (c, cir2) <- invokeEvent p (g b)+ return (c, cir2 `dot` cir1)++instance Arrow Circuit where++ arr f = Circuit $ \a -> return (f a, arr f)++ first (Circuit f) =+ Circuit $ \(b, d) ->+ Event $ \p ->+ do (c, cir) <- invokeEvent p (f b)+ return ((c, d), first cir)++ second (Circuit f) =+ Circuit $ \(d, b) ->+ Event $ \p ->+ do (c, cir) <- invokeEvent p (f b)+ return ((d, c), second cir)++ (Circuit f) *** (Circuit g) =+ Circuit $ \(b, b') ->+ Event $ \p ->+ do (c, cir1) <- invokeEvent p (f b)+ (c', cir2) <- invokeEvent p (g b')+ return ((c, c'), cir1 *** cir2)+ + (Circuit f) &&& (Circuit g) =+ Circuit $ \b ->+ Event $ \p ->+ do (c, cir1) <- invokeEvent p (f b)+ (c', cir2) <- invokeEvent p (g b)+ return ((c, c'), cir1 &&& cir2)++instance ArrowLoop Circuit where++ loop (Circuit f) =+ Circuit $ \b ->+ Event $ \p ->+ do rec ((c, d), cir) <- invokeEvent p (f (b, d))+ return (c, loop cir)++instance ArrowChoice Circuit where++ left x@(Circuit f) =+ Circuit $ \ebd ->+ Event $ \p ->+ case ebd of+ Left b ->+ do (c, cir) <- invokeEvent p (f b)+ return (Left c, left cir)+ Right d ->+ return (Right d, left x)++ right x@(Circuit f) =+ Circuit $ \edb ->+ Event $ \p ->+ case edb of+ Right b ->+ do (c, cir) <- invokeEvent p (f b)+ return (Right c, right cir)+ Left d ->+ return (Left d, right x)++ x@(Circuit f) +++ y@(Circuit g) =+ Circuit $ \ebb' ->+ Event $ \p ->+ case ebb' of+ Left b ->+ do (c, cir1) <- invokeEvent p (f b)+ return (Left c, cir1 +++ y)+ Right b' ->+ do (c', cir2) <- invokeEvent p (g b')+ return (Right c', x +++ cir2)++ x@(Circuit f) ||| y@(Circuit g) =+ Circuit $ \ebc ->+ Event $ \p ->+ case ebc of+ Left b ->+ do (d, cir1) <- invokeEvent p (f b)+ return (d, cir1 ||| y)+ Right b' ->+ do (d, cir2) <- invokeEvent p (g b')+ return (d, x ||| cir2)++-- | Get a signal transform by the specified circuit.+circuitSignaling :: Circuit a b -> Signal a -> Signal b+circuitSignaling (Circuit cir) sa =+ Signal { handleSignal = \f ->+ Event $ \p ->+ do r <- newIORef cir+ invokeEvent p $+ handleSignal sa $ \a ->+ Event $ \p ->+ do cir <- readIORef r+ (b, Circuit cir') <- invokeEvent p (cir a)+ writeIORef r cir'+ invokeEvent p (f b) }++-- | Transform the circuit to a processor.+circuitProcessor :: Circuit a b -> Processor a b+circuitProcessor (Circuit cir) = Processor $ \sa ->+ Cons $+ do (a, xs) <- runStream sa+ (b, cir') <- liftEvent (cir a)+ let f = runProcessor (circuitProcessor cir')+ return (b, f xs)++-- | Create a simple circuit by the specified handling function+-- that runs the computation for each input value to get an output.+arrCircuit :: (a -> Event b) -> Circuit a b+arrCircuit f =+ let x =+ Circuit $ \a ->+ Event $ \p ->+ do b <- invokeEvent p (f a)+ return (b, x)+ in x++-- | Accumulator that outputs a value determined by the supplied function.+accumCircuit :: (acc -> a -> Event (acc, b)) -> acc -> Circuit a b+accumCircuit f acc =+ Circuit $ \a ->+ Event $ \p ->+ do (acc', b) <- invokeEvent p (f acc a)+ return (b, accumCircuit f acc') ++-- | A circuit that adds the information about the time points at which +-- the values were received.+arrivalCircuit :: Circuit a (Arrival a)+arrivalCircuit =+ let loop t0 =+ Circuit $ \a ->+ Event $ \p ->+ let t = pointTime p+ b = Arrival { arrivalValue = a,+ arrivalTime = t,+ arrivalDelay = + case t0 of+ Nothing -> Nothing+ Just t0 -> Just (t - t0) }+ in return (b, loop $ Just t)+ in loop Nothing++-- | Delay the input by one step using the specified initial value.+delayCircuit :: a -> Circuit a a+delayCircuit a0 =+ Circuit $ \a ->+ return (a0, delayCircuit a)++-- | A circuit that returns the current modeling time.+timeCircuit :: Circuit a Double+timeCircuit =+ Circuit $ \a ->+ Event $ \p ->+ return (pointTime p, timeCircuit)++-- | Like '>>>' but processes only the represented events.+(>?>) :: Circuit a (Maybe b)+ -- ^ whether there is an event+ -> Circuit b c+ -- ^ process the event if it presents+ -> Circuit a (Maybe c)+ -- ^ the resulting circuit that processes only the represented events+whether >?> process =+ Circuit $ \a ->+ Event $ \p ->+ do (b, whether') <- invokeEvent p (runCircuit whether a)+ case b of+ Nothing ->+ return (Nothing, whether' >?> process)+ Just b ->+ do (c, process') <- invokeEvent p (runCircuit process b)+ return (Just c, whether' >?> process')++-- | Like '<<<' but processes only the represented events.+(<?<) :: Circuit b c+ -- ^ process the event if it presents+ -> Circuit a (Maybe b)+ -- ^ whether there is an event+ -> Circuit a (Maybe c)+ -- ^ the resulting circuit that processes only the represented events+(<?<) = flip (>?>)++-- | Filter the circuit, calculating only those parts of the circuit that satisfy+-- the specified predicate.+filterCircuit :: (a -> Bool) -> Circuit a b -> Circuit a (Maybe b)+filterCircuit pred = filterCircuitM (return . pred)++-- | Filter the circuit within the 'Event' computation, calculating only those parts+-- of the circuit that satisfy the specified predicate.+filterCircuitM :: (a -> Event Bool) -> Circuit a b -> Circuit a (Maybe b)+filterCircuitM pred cir =+ Circuit $ \a ->+ Event $ \p ->+ do x <- invokeEvent p (pred a)+ if x+ then do (b, cir') <- invokeEvent p (runCircuit cir a)+ return (Just b, filterCircuitM pred cir')+ else return (Nothing, filterCircuitM pred cir)++-- | The source of events that never occur.+neverCircuit :: Circuit a (Maybe b)+neverCircuit =+ Circuit $ \a -> return (Nothing, neverCircuit)++-- | An approximation of the integral using Euler's method.+--+-- This function can be rather inaccurate as it depends on+-- the time points at wich the 'Circuit' computation is actuated.+-- Also Euler's method per se is not most accurate, although simple+-- enough for implementation.+--+-- Consider using the 'integ' function whenever possible.+-- That function can integrate with help of the Runge-Kutta method by+-- the specified integration time points that are passed in the simulation+-- specs to every 'Simulation', when running the model.+--+-- At the same time, the 'integCircuit' function has no mutable state+-- unlike the former. The latter consumes less memory but at the cost+-- of inaccuracy and relatively more slow simulation, had we requested+-- the integral in the same time points.+--+-- Regarding the recursive equations, the both functions allow defining them+-- but whithin different computations (either with help of the recursive+-- do-notation or the proc-notation).+integCircuit :: Double+ -- ^ the initial value+ -> Circuit Double Double+ -- ^ map the derivative to an integral+integCircuit init = start+ where+ start = + Circuit $ \a ->+ Event $ \p ->+ do let t = pointTime p+ return (init, next t init a)+ next t0 v0 a0 =+ Circuit $ \a ->+ Event $ \p ->+ do let t = pointTime p+ dt = t - t0+ v = v0 + a0 * dt+ v `seq` return (v, next t v a)++-- | Like 'integCircuit' but allows either setting a new 'Left' integral value,+-- or using the 'Right' derivative when integrating by Euler's method.+integCircuitEither :: Double+ -- ^ the initial value+ -> Circuit (Either Double Double) Double+ -- ^ map either a new 'Left' value or+ -- the 'Right' derivative to an integral+integCircuitEither init = start+ where+ start = + Circuit $ \a ->+ Event $ \p ->+ do let t = pointTime p+ return (init, next t init a)+ next t0 v0 a0 =+ Circuit $ \a ->+ Event $ \p ->+ do let t = pointTime p+ case a0 of+ Left v ->+ v `seq` return (v, next t v a)+ Right a0 -> do+ let dt = t - t0+ v = v0 + a0 * dt+ v `seq` return (v, next t v a)++-- | A sum of differences starting from the specified initial value.+--+-- Consider using the more accurate 'diffsum' function whener possible as+-- it is calculated in every integration time point specified by specs+-- passed in to every 'Simulation', when running the model.+--+-- At the same time, the 'sumCircuit' function has no mutable state and+-- it consumes less memory than the former.+--+-- Regarding the recursive equations, the both functions allow defining them+-- but whithin different computations (either with help of the recursive+-- do-notation or the proc-notation).+sumCircuit :: Num a =>+ a+ -- ^ the initial value+ -> Circuit a a+ -- ^ map the difference to a sum+sumCircuit init = start+ where+ start = + Circuit $ \a ->+ Event $ \p ->+ return (init, next init a)+ next v0 a0 =+ Circuit $ \a ->+ Event $ \p ->+ do let v = v0 + a0+ v `seq` return (v, next v a)++-- | Like 'sumCircuit' but allows either setting a new 'Left' value for the sum, or updating it+-- by specifying the 'Right' difference.+sumCircuitEither :: Num a =>+ a+ -- ^ the initial value+ -> Circuit (Either a a) a+ -- ^ map either a new 'Left' value or+ -- the 'Right' difference to a sum+sumCircuitEither init = start+ where+ start = + Circuit $ \a ->+ Event $ \p ->+ return (init, next init a)+ next v0 a0 =+ Circuit $ \a ->+ Event $ \p ->+ case a0 of+ Left v ->+ v `seq` return (v, next v a)+ Right a0 -> do+ let v = v0 + a0+ v `seq` return (v, next v a)++-- | Approximate the circuit as a transform of time varying function,+-- calculating the values in the integration time points and then+-- interpolating in all other time points. The resulting transform+-- computation is synchronized with the event queue. +--+-- This procedure consumes memory as the underlying memoization allocates+-- an array to store the calculated values.+circuitTransform :: Circuit a b -> Transform a b+circuitTransform cir = Transform start+ where+ start m =+ Simulation $ \r ->+ do ref <- newIORef cir+ invokeSimulation r $+ memo0Dynamics (next ref m)+ next ref m =+ Dynamics $ \p ->+ do a <- invokeDynamics p m+ cir <- readIORef ref+ (b, cir') <-+ invokeDynamics p $+ runEvent (runCircuit cir a)+ writeIORef ref cir'+ return b++-- | Iterate the circuit in the specified time points.+iterateCircuitInPoints_ :: [Point] -> Circuit a a -> a -> Event ()+iterateCircuitInPoints_ [] cir a = return ()+iterateCircuitInPoints_ (p : ps) cir a =+ enqueueEvent (pointTime p) $+ Event $ \p' ->+ do (a', cir') <- invokeEvent p $ runCircuit cir a+ invokeEvent p $ iterateCircuitInPoints_ ps cir' a'++-- | Iterate the circuit in the specified time points returning a task+-- which completes after the final output of the circuit is received.+iterateCircuitInPoints :: [Point] -> Circuit a a -> a -> Event (Task a)+iterateCircuitInPoints ps cir a =+ do let loop [] cir a source = triggerSignal source a+ loop (p : ps) cir a source =+ enqueueEvent (pointTime p) $+ Event $ \p' ->+ do (a', cir') <- invokeEvent p $ runCircuit cir a+ invokeEvent p $ loop ps cir' a' source+ source <- liftSimulation newSignalSource+ loop ps cir a source+ runTask $ processAwait $ publishSignal source++-- | Iterate the circuit in the integration time points.+iterateCircuitInIntegTimes_ :: Circuit a a -> a -> Event ()+iterateCircuitInIntegTimes_ cir a =+ Event $ \p ->+ do let ps = integPoints $ pointRun p+ invokeEvent p $ + iterateCircuitInPoints_ ps cir a++-- | Iterate the circuit in the specified time points.+iterateCircuitInTimes_ :: [Double] -> Circuit a a -> a -> Event ()+iterateCircuitInTimes_ ts cir a =+ Event $ \p ->+ do let ps = map (pointAt $ pointRun p) ts+ invokeEvent p $ + iterateCircuitInPoints_ ps cir a ++-- | Iterate the circuit in the integration time points returning a task+-- which completes after the final output of the circuit is received.+iterateCircuitInIntegTimes :: Circuit a a -> a -> Event (Task a)+iterateCircuitInIntegTimes cir a =+ Event $ \p ->+ do let ps = integPoints $ pointRun p+ invokeEvent p $ + iterateCircuitInPoints ps cir a++-- | Iterate the circuit in the specified time points returning a task+-- which completes after the final output of the circuit is received.+iterateCircuitInTimes :: [Double] -> Circuit a a -> a -> Event (Task a)+iterateCircuitInTimes ts cir a =+ Event $ \p ->+ do let ps = map (pointAt $ pointRun p) ts+ invokeEvent p $ + iterateCircuitInPoints ps cir a
Simulation/Aivika/Cont.hs view
@@ -1,19 +1,19 @@- --- | --- Module : Simulation.Aivika.Cont --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- The 'Cont' monad is a variation of the standard Cont monad --- and F# async workflow, where the result of applying --- the continuations is the 'Event' computation. --- -module Simulation.Aivika.Cont - (ContCancellation(..), - Cont) where - -import Simulation.Aivika.Internal.Event -import Simulation.Aivika.Internal.Cont ++-- |+-- Module : Simulation.Aivika.Cont+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- The 'Cont' monad is a variation of the standard Cont monad +-- and F# async workflow, where the result of applying +-- the continuations is the 'Event' computation.+--+module Simulation.Aivika.Cont+ (ContCancellation(..),+ Cont) where++import Simulation.Aivika.Internal.Event+import Simulation.Aivika.Internal.Cont
Simulation/Aivika/DoubleLinkedList.hs view
@@ -1,165 +1,165 @@- --- | --- Module : Simulation.Aivika.DoubleLinkedList --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- An imperative double-linked list. --- -module Simulation.Aivika.DoubleLinkedList - (DoubleLinkedList, - listNull, - listCount, - newList, - listInsertFirst, - listAddLast, - listRemoveFirst, - listRemoveLast, - listFirst, - listLast) where - -import Data.IORef -import Control.Monad - --- | A cell of the double-linked list. -data DoubleLinkedItem a = - DoubleLinkedItem { itemVal :: a, - itemPrev :: IORef (Maybe (DoubleLinkedItem a)), - itemNext :: IORef (Maybe (DoubleLinkedItem a)) } - --- | The 'DoubleLinkedList' type represents an imperative double-linked list. -data DoubleLinkedList a = - DoubleLinkedList { listHead :: IORef (Maybe (DoubleLinkedItem a)), - listTail :: IORef (Maybe (DoubleLinkedItem a)), - listSize :: IORef Int } - --- | Test whether the list is empty. -listNull :: DoubleLinkedList a -> IO Bool -listNull x = - do head <- readIORef (listHead x) - case head of - Nothing -> return True - Just _ -> return False - --- | Return the number of elements in the list. -listCount :: DoubleLinkedList a -> IO Int -listCount x = readIORef (listSize x) - --- | Create a new list. -newList :: IO (DoubleLinkedList a) -newList = - do head <- newIORef Nothing - tail <- newIORef Nothing - size <- newIORef 0 - return DoubleLinkedList { listHead = head, - listTail = tail, - listSize = size } - --- | Insert a new element in the beginning. -listInsertFirst :: DoubleLinkedList a -> a -> IO () -listInsertFirst x v = - do size <- readIORef (listSize x) - writeIORef (listSize x) (size + 1) - head <- readIORef (listHead x) - case head of - Nothing -> - do prev <- newIORef Nothing - next <- newIORef Nothing - let item = Just DoubleLinkedItem { itemVal = v, - itemPrev = prev, - itemNext = next } - writeIORef (listHead x) item - writeIORef (listTail x) item - Just h -> - do prev <- newIORef Nothing - next <- newIORef head - let item = Just DoubleLinkedItem { itemVal = v, - itemPrev = prev, - itemNext = next } - writeIORef (itemPrev h) item - writeIORef (listHead x) item - --- | Add a new element to the end. -listAddLast :: DoubleLinkedList a -> a -> IO () -listAddLast x v = - do size <- readIORef (listSize x) - writeIORef (listSize x) (size + 1) - tail <- readIORef (listTail x) - case tail of - Nothing -> - do prev <- newIORef Nothing - next <- newIORef Nothing - let item = Just DoubleLinkedItem { itemVal = v, - itemPrev = prev, - itemNext = next } - writeIORef (listHead x) item - writeIORef (listTail x) item - Just t -> - do prev <- newIORef tail - next <- newIORef Nothing - let item = Just DoubleLinkedItem { itemVal = v, - itemPrev = prev, - itemNext = next } - writeIORef (itemNext t) item - writeIORef (listTail x) item - --- | Remove the first element. -listRemoveFirst :: DoubleLinkedList a -> IO () -listRemoveFirst x = - do head <- readIORef (listHead x) - case head of - Nothing -> - error "Empty list: listRemoveFirst" - Just h -> - do size <- readIORef (listSize x) - writeIORef (listSize x) (size - 1) - head' <- readIORef (itemNext h) - case head' of - Nothing -> - do writeIORef (listHead x) Nothing - writeIORef (listTail x) Nothing - Just h' -> - do writeIORef (itemPrev h') Nothing - writeIORef (listHead x) head' - --- | Remove the last element. -listRemoveLast :: DoubleLinkedList a -> IO () -listRemoveLast x = - do tail <- readIORef (listTail x) - case tail of - Nothing -> - error "Empty list: listRemoveLast" - Just t -> - do size <- readIORef (listSize x) - writeIORef (listSize x) (size - 1) - tail' <- readIORef (itemPrev t) - case tail' of - Nothing -> - do writeIORef (listHead x) Nothing - writeIORef (listTail x) Nothing - Just t' -> - do writeIORef (itemNext t') Nothing - writeIORef (listTail x) tail' - --- | Return the first element. -listFirst :: DoubleLinkedList a -> IO a -listFirst x = - do head <- readIORef (listHead x) - case head of - Nothing -> - error "Empty list: listFirst" - Just h -> - return $ itemVal h - --- | Return the last element. -listLast :: DoubleLinkedList a -> IO a -listLast x = - do tail <- readIORef (listTail x) - case tail of - Nothing -> - error "Empty list: listLast" - Just t -> - return $ itemVal t ++-- |+-- Module : Simulation.Aivika.DoubleLinkedList+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- An imperative double-linked list.+--+module Simulation.Aivika.DoubleLinkedList + (DoubleLinkedList, + listNull, + listCount,+ newList, + listInsertFirst,+ listAddLast,+ listRemoveFirst,+ listRemoveLast,+ listFirst,+ listLast) where ++import Data.IORef+import Control.Monad++-- | A cell of the double-linked list.+data DoubleLinkedItem a = + DoubleLinkedItem { itemVal :: a,+ itemPrev :: IORef (Maybe (DoubleLinkedItem a)),+ itemNext :: IORef (Maybe (DoubleLinkedItem a)) }+ +-- | The 'DoubleLinkedList' type represents an imperative double-linked list.+data DoubleLinkedList a = + DoubleLinkedList { listHead :: IORef (Maybe (DoubleLinkedItem a)),+ listTail :: IORef (Maybe (DoubleLinkedItem a)), + listSize :: IORef Int }++-- | Test whether the list is empty.+listNull :: DoubleLinkedList a -> IO Bool+listNull x =+ do head <- readIORef (listHead x) + case head of+ Nothing -> return True+ Just _ -> return False+ +-- | Return the number of elements in the list.+listCount :: DoubleLinkedList a -> IO Int+listCount x = readIORef (listSize x)++-- | Create a new list.+newList :: IO (DoubleLinkedList a)+newList =+ do head <- newIORef Nothing + tail <- newIORef Nothing+ size <- newIORef 0+ return DoubleLinkedList { listHead = head,+ listTail = tail,+ listSize = size }++-- | Insert a new element in the beginning.+listInsertFirst :: DoubleLinkedList a -> a -> IO ()+listInsertFirst x v =+ do size <- readIORef (listSize x)+ writeIORef (listSize x) (size + 1)+ head <- readIORef (listHead x)+ case head of+ Nothing ->+ do prev <- newIORef Nothing+ next <- newIORef Nothing+ let item = Just DoubleLinkedItem { itemVal = v, + itemPrev = prev, + itemNext = next }+ writeIORef (listHead x) item+ writeIORef (listTail x) item+ Just h ->+ do prev <- newIORef Nothing+ next <- newIORef head+ let item = Just DoubleLinkedItem { itemVal = v,+ itemPrev = prev,+ itemNext = next }+ writeIORef (itemPrev h) item+ writeIORef (listHead x) item++-- | Add a new element to the end.+listAddLast :: DoubleLinkedList a -> a -> IO ()+listAddLast x v =+ do size <- readIORef (listSize x)+ writeIORef (listSize x) (size + 1)+ tail <- readIORef (listTail x)+ case tail of+ Nothing ->+ do prev <- newIORef Nothing+ next <- newIORef Nothing+ let item = Just DoubleLinkedItem { itemVal = v, + itemPrev = prev, + itemNext = next }+ writeIORef (listHead x) item+ writeIORef (listTail x) item+ Just t ->+ do prev <- newIORef tail+ next <- newIORef Nothing+ let item = Just DoubleLinkedItem { itemVal = v,+ itemPrev = prev,+ itemNext = next }+ writeIORef (itemNext t) item+ writeIORef (listTail x) item++-- | Remove the first element.+listRemoveFirst :: DoubleLinkedList a -> IO ()+listRemoveFirst x =+ do head <- readIORef (listHead x) + case head of+ Nothing ->+ error "Empty list: listRemoveFirst"+ Just h ->+ do size <- readIORef (listSize x)+ writeIORef (listSize x) (size - 1)+ head' <- readIORef (itemNext h)+ case head' of+ Nothing ->+ do writeIORef (listHead x) Nothing+ writeIORef (listTail x) Nothing+ Just h' ->+ do writeIORef (itemPrev h') Nothing+ writeIORef (listHead x) head'++-- | Remove the last element.+listRemoveLast :: DoubleLinkedList a -> IO ()+listRemoveLast x =+ do tail <- readIORef (listTail x) + case tail of+ Nothing ->+ error "Empty list: listRemoveLast"+ Just t ->+ do size <- readIORef (listSize x)+ writeIORef (listSize x) (size - 1)+ tail' <- readIORef (itemPrev t)+ case tail' of+ Nothing ->+ do writeIORef (listHead x) Nothing+ writeIORef (listTail x) Nothing+ Just t' ->+ do writeIORef (itemNext t') Nothing+ writeIORef (listTail x) tail'++-- | Return the first element.+listFirst :: DoubleLinkedList a -> IO a+listFirst x =+ do head <- readIORef (listHead x)+ case head of+ Nothing ->+ error "Empty list: listFirst"+ Just h ->+ return $ itemVal h++-- | Return the last element.+listLast :: DoubleLinkedList a -> IO a+listLast x =+ do tail <- readIORef (listTail x)+ case tail of+ Nothing ->+ error "Empty list: listLast"+ Just t ->+ return $ itemVal t
Simulation/Aivika/Dynamics.hs view
@@ -1,31 +1,31 @@- --- | --- Module : Simulation.Aivika.Dynamics --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- The module defines the 'Dynamics' monad representing a time varying polymorphic function. --- -module Simulation.Aivika.Dynamics - (-- * Dynamics Monad - Dynamics, - DynamicsLift(..), - runDynamicsInStartTime, - runDynamicsInStopTime, - runDynamicsInIntegTimes, - runDynamicsInTime, - runDynamicsInTimes, - -- * Error Handling - catchDynamics, - finallyDynamics, - throwDynamics, - -- * Simulation Time - time, - isTimeInteg, - integIteration, - integPhase) where - -import Simulation.Aivika.Internal.Dynamics ++-- |+-- Module : Simulation.Aivika.Dynamics+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- The module defines the 'Dynamics' monad representing a time varying polymorphic function. +--+module Simulation.Aivika.Dynamics+ (-- * Dynamics Monad+ Dynamics,+ DynamicsLift(..),+ runDynamicsInStartTime,+ runDynamicsInStopTime,+ runDynamicsInIntegTimes,+ runDynamicsInTime,+ runDynamicsInTimes,+ -- * Error Handling+ catchDynamics,+ finallyDynamics,+ throwDynamics,+ -- * Simulation Time+ time,+ isTimeInteg,+ integIteration,+ integPhase) where++import Simulation.Aivika.Internal.Dynamics
Simulation/Aivika/Dynamics/Extra.hs view
@@ -1,109 +1,109 @@- -{-# LANGUAGE RecursiveDo #-} - --- | --- Module : Simulation.Aivika.Dynamics.Extra --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- This module defines auxiliary functions such as interpolation ones --- that complement the memoization, for example. There are scan functions too. --- - -module Simulation.Aivika.Dynamics.Extra - (-- * Interpolation - initDynamics, - discreteDynamics, - interpolateDynamics, - -- * Scans - scanDynamics, - scan1Dynamics) where - -import Simulation.Aivika.Internal.Specs -import Simulation.Aivika.Internal.Simulation -import Simulation.Aivika.Internal.Dynamics - --- | Return the initial value. -initDynamics :: Dynamics a -> Dynamics a -{-# INLINE initDynamics #-} -initDynamics (Dynamics m) = - Dynamics $ \p -> - 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 -{-# INLINE discreteDynamics #-} -discreteDynamics (Dynamics m) = - Dynamics $ \p -> - if pointPhase p == 0 then - m p - else - let sc = pointSpecs p - n = pointIteration p - in m $ p { pointTime = basicTime sc n 0, - pointPhase = 0 } - --- | Interpolate the computation based on the integration time points only. --- Unlike the 'discreteDynamics' function it knows about the intermediate --- time points that are used in the Runge-Kutta method. -interpolateDynamics :: Dynamics a -> Dynamics a -{-# INLINE interpolateDynamics #-} -interpolateDynamics (Dynamics m) = - Dynamics $ \p -> - if pointPhase p >= 0 then - m p - else - let sc = pointSpecs p - n = pointIteration p - in m $ p { pointTime = basicTime sc n 0, - pointPhase = 0 } - --- | Like the standard 'scanl1' function but applied to values in --- the integration time points. The accumulator values are transformed --- according to the second argument, which should be either function --- 'memo0Dynamics' or its unboxed version. -scan1Dynamics :: (a -> a -> a) - -> (Dynamics a -> Simulation (Dynamics a)) - -> (Dynamics a -> Simulation (Dynamics a)) -scan1Dynamics f tr m = - mdo y <- tr $ Dynamics $ \p -> - case pointIteration p of - 0 -> - invokeDynamics p m - n -> do - let sc = pointSpecs p - ty = basicTime sc (n - 1) 0 - py = p { pointTime = ty, pointIteration = n - 1, pointPhase = 0 } - s <- invokeDynamics py y - x <- invokeDynamics p m - return $! f s x - return y - --- | Like the standard 'scanl' function but applied to values in --- the integration time points. The accumulator values are transformed --- according to the third argument, which should be either function --- 'memo0Dynamics' or its unboxed version. -scanDynamics :: (a -> b -> a) - -> a - -> (Dynamics a -> Simulation (Dynamics a)) - -> (Dynamics b -> Simulation (Dynamics a)) -scanDynamics f acc tr m = - mdo y <- tr $ Dynamics $ \p -> - case pointIteration p of - 0 -> do - x <- invokeDynamics p m - return $! f acc x - n -> do - let sc = pointSpecs p - ty = basicTime sc (n - 1) 0 - py = p { pointTime = ty, pointIteration = n - 1, pointPhase = 0 } - s <- invokeDynamics py y - x <- invokeDynamics p m - return $! f s x - return y ++{-# LANGUAGE RecursiveDo #-}++-- |+-- Module : Simulation.Aivika.Dynamics.Extra+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- This module defines auxiliary functions such as interpolation ones+-- that complement the memoization, for example. There are scan functions too.+--++module Simulation.Aivika.Dynamics.Extra+ (-- * Interpolation+ initDynamics,+ discreteDynamics,+ interpolateDynamics,+ -- * Scans+ scanDynamics,+ scan1Dynamics) where++import Simulation.Aivika.Internal.Specs+import Simulation.Aivika.Internal.Simulation+import Simulation.Aivika.Internal.Dynamics++-- | Return the initial value.+initDynamics :: Dynamics a -> Dynamics a+{-# INLINE initDynamics #-}+initDynamics (Dynamics m) =+ Dynamics $ \p ->+ 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+{-# INLINE discreteDynamics #-}+discreteDynamics (Dynamics m) =+ Dynamics $ \p ->+ if pointPhase p == 0 then+ m p+ else+ let sc = pointSpecs p+ n = pointIteration p+ in m $ p { pointTime = basicTime sc n 0,+ pointPhase = 0 }++-- | Interpolate the computation based on the integration time points only.+-- Unlike the 'discreteDynamics' function it knows about the intermediate +-- time points that are used in the Runge-Kutta method.+interpolateDynamics :: Dynamics a -> Dynamics a+{-# INLINE interpolateDynamics #-}+interpolateDynamics (Dynamics m) = + Dynamics $ \p -> + if pointPhase p >= 0 then + m p+ else + let sc = pointSpecs p+ n = pointIteration p+ in m $ p { pointTime = basicTime sc n 0,+ pointPhase = 0 }++-- | Like the standard 'scanl1' function but applied to values in +-- the integration time points. The accumulator values are transformed+-- according to the second argument, which should be either function +-- 'memo0Dynamics' or its unboxed version.+scan1Dynamics :: (a -> a -> a)+ -> (Dynamics a -> Simulation (Dynamics a))+ -> (Dynamics a -> Simulation (Dynamics a))+scan1Dynamics f tr m =+ mdo y <- tr $ Dynamics $ \p ->+ case pointIteration p of+ 0 -> + invokeDynamics p m+ n -> do + let sc = pointSpecs p+ ty = basicTime sc (n - 1) 0+ py = p { pointTime = ty, pointIteration = n - 1, pointPhase = 0 }+ s <- invokeDynamics py y+ x <- invokeDynamics p m+ return $! f s x+ return y++-- | Like the standard 'scanl' function but applied to values in +-- the integration time points. The accumulator values are transformed+-- according to the third argument, which should be either function+-- 'memo0Dynamics' or its unboxed version.+scanDynamics :: (a -> b -> a)+ -> a+ -> (Dynamics a -> Simulation (Dynamics a))+ -> (Dynamics b -> Simulation (Dynamics a))+scanDynamics f acc tr m =+ mdo y <- tr $ Dynamics $ \p ->+ case pointIteration p of+ 0 -> do+ x <- invokeDynamics p m+ return $! f acc x+ n -> do + let sc = pointSpecs p+ ty = basicTime sc (n - 1) 0+ py = p { pointTime = ty, pointIteration = n - 1, pointPhase = 0 }+ s <- invokeDynamics py y+ x <- invokeDynamics p m+ return $! f s x+ return y
Simulation/Aivika/Dynamics/Memo.hs view
@@ -1,159 +1,159 @@- --- | --- Module : Simulation.Aivika.Dynamics.Memo --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- This module defines memo functions. The memoization creates such 'Dynamics' --- computations, which values are cached in the integration time points. Then --- these values are interpolated in all other time points. --- - -module Simulation.Aivika.Dynamics.Memo - (memoDynamics, - memo0Dynamics, - iterateDynamics, - unzipDynamics, - unzip0Dynamics) where - -import Data.Array -import Data.Array.IO.Safe -import Data.IORef -import Control.Monad - -import Simulation.Aivika.Internal.Specs -import Simulation.Aivika.Internal.Parameter -import Simulation.Aivika.Internal.Simulation -import Simulation.Aivika.Internal.Dynamics -import Simulation.Aivika.Dynamics.Extra - --- | Create a boxed array with default values. -newBoxedArray_ :: Ix i => (i, i) -> IO (IOArray i e) -newBoxedArray_ = newArray_ - --- | Memoize and order the computation in the integration time points using --- the interpolation that knows of the Runge-Kutta method. The values are --- calculated sequentially starting from 'starttime'. -memoDynamics :: Dynamics e -> Simulation (Dynamics e) -{-# INLINE memoDynamics #-} -memoDynamics (Dynamics m) = - Simulation $ \r -> - do let sc = runSpecs r - (phl, phu) = integPhaseBnds sc - (nl, nu) = integIterationBnds sc - arr <- newBoxedArray_ ((phl, nl), (phu, nu)) - nref <- newIORef 0 - phref <- newIORef 0 - let r p = - do let sc = pointSpecs p - n = pointIteration p - ph = pointPhase p - phu = integPhaseHiBnd sc - loop n' ph' = - if (n' > n) || ((n' == n) && (ph' > ph)) - then - readArray arr (ph, n) - else - let p' = p { pointIteration = n', pointPhase = ph', - pointTime = basicTime sc n' ph' } - in do a <- m p' - a `seq` writeArray arr (ph', n') a - if ph' >= phu - then do writeIORef phref 0 - writeIORef nref (n' + 1) - loop (n' + 1) 0 - else do writeIORef phref (ph' + 1) - loop n' (ph' + 1) - n' <- readIORef nref - ph' <- readIORef phref - loop n' ph' - return $ interpolateDynamics $ Dynamics r - --- | Memoize and order the computation in the integration time points using --- the 'discreteDynamics' interpolation. It consumes less memory than the 'memoDynamics' --- function but it is not aware of the Runge-Kutta method. There is a subtle --- difference when we request for values in the intermediate time points --- that are used by this method to integrate. In general case you should --- prefer the 'memo0Dynamics' function above 'memoDynamics'. -memo0Dynamics :: Dynamics e -> Simulation (Dynamics e) -{-# INLINE memo0Dynamics #-} -memo0Dynamics (Dynamics m) = - Simulation $ \r -> - do let sc = runSpecs r - bnds = integIterationBnds sc - arr <- newBoxedArray_ bnds - nref <- newIORef 0 - let r p = - do let sc = pointSpecs p - n = pointIteration p - loop n' = - if n' > n - then - readArray arr n - else - let p' = p { pointIteration = n', pointPhase = 0, - pointTime = basicTime sc n' 0 } - in do a <- m p' - a `seq` writeArray arr n' a - writeIORef nref (n' + 1) - loop (n' + 1) - n' <- readIORef nref - loop n' - return $ discreteDynamics $ Dynamics r - --- | Iterate sequentially the dynamic process with side effects in --- the integration time points. It is equivalent to a call of the --- 'memo0Dynamics' function but significantly more efficient, for the array --- is not created. -iterateDynamics :: Dynamics () -> Simulation (Dynamics ()) -{-# INLINE iterateDynamics #-} -iterateDynamics (Dynamics m) = - Simulation $ \r -> - do let sc = runSpecs r - nref <- newIORef 0 - let r p = - do let sc = pointSpecs p - n = pointIteration p - loop n' = - unless (n' > n) $ - let p' = p { pointIteration = n', pointPhase = 0, - pointTime = basicTime sc n' 0 } - in do a <- m p' - a `seq` writeIORef nref (n' + 1) - loop (n' + 1) - n' <- readIORef nref - loop n' - return $ discreteDynamics $ Dynamics r - --- | Memoize and unzip the computation of pairs, applying the 'memoDynamics' function. -unzipDynamics :: Dynamics (a, b) -> Simulation (Dynamics a, Dynamics b) -unzipDynamics m = - Simulation $ \r -> - do m' <- invokeSimulation r (memoDynamics m) - let ma = - Dynamics $ \p -> - do (a, _) <- invokeDynamics p m' - return a - mb = - Dynamics $ \p -> - do (_, b) <- invokeDynamics p m' - return b - return (ma, mb) - --- | Memoize and unzip the computation of pairs, applying the 'memo0Dynamics' function. -unzip0Dynamics :: Dynamics (a, b) -> Simulation (Dynamics a, Dynamics b) -unzip0Dynamics m = - Simulation $ \r -> - do m' <- invokeSimulation r (memo0Dynamics m) - let ma = - Dynamics $ \p -> - do (a, _) <- invokeDynamics p m' - return a - mb = - Dynamics $ \p -> - do (_, b) <- invokeDynamics p m' - return b - return (ma, mb) ++-- |+-- Module : Simulation.Aivika.Dynamics.Memo+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- This module defines memo functions. The memoization creates such 'Dynamics'+-- computations, which values are cached in the integration time points. Then+-- these values are interpolated in all other time points.+--++module Simulation.Aivika.Dynamics.Memo+ (memoDynamics,+ memo0Dynamics,+ iterateDynamics,+ unzipDynamics,+ unzip0Dynamics) where++import Data.Array+import Data.Array.IO.Safe+import Data.IORef+import Control.Monad++import Simulation.Aivika.Internal.Specs+import Simulation.Aivika.Internal.Parameter+import Simulation.Aivika.Internal.Simulation+import Simulation.Aivika.Internal.Dynamics+import Simulation.Aivika.Dynamics.Extra++-- | Create a boxed array with default values.+newBoxedArray_ :: Ix i => (i, i) -> IO (IOArray i e)+newBoxedArray_ = newArray_++-- | Memoize and order the computation in the integration time points using +-- the interpolation that knows of the Runge-Kutta method. The values are+-- calculated sequentially starting from 'starttime'.+memoDynamics :: Dynamics e -> Simulation (Dynamics e)+{-# INLINE memoDynamics #-}+memoDynamics (Dynamics m) = + Simulation $ \r ->+ do let sc = runSpecs r+ (phl, phu) = integPhaseBnds sc+ (nl, nu) = integIterationBnds sc+ arr <- newBoxedArray_ ((phl, nl), (phu, nu))+ nref <- newIORef 0+ phref <- newIORef 0+ let r p = + do let sc = pointSpecs p+ n = pointIteration p+ ph = pointPhase p+ phu = integPhaseHiBnd sc + loop n' ph' = + if (n' > n) || ((n' == n) && (ph' > ph)) + then + readArray arr (ph, n)+ else + let p' = p { pointIteration = n', pointPhase = ph',+ pointTime = basicTime sc n' ph' }+ in do a <- m p'+ a `seq` writeArray arr (ph', n') a+ if ph' >= phu + then do writeIORef phref 0+ writeIORef nref (n' + 1)+ loop (n' + 1) 0+ else do writeIORef phref (ph' + 1)+ loop n' (ph' + 1)+ n' <- readIORef nref+ ph' <- readIORef phref+ loop n' ph'+ return $ interpolateDynamics $ Dynamics r++-- | Memoize and order the computation in the integration time points using +-- the 'discreteDynamics' interpolation. It consumes less memory than the 'memoDynamics'+-- function but it is not aware of the Runge-Kutta method. There is a subtle+-- difference when we request for values in the intermediate time points+-- that are used by this method to integrate. In general case you should +-- prefer the 'memo0Dynamics' function above 'memoDynamics'.+memo0Dynamics :: Dynamics e -> Simulation (Dynamics e)+{-# INLINE memo0Dynamics #-}+memo0Dynamics (Dynamics m) = + Simulation $ \r ->+ do let sc = runSpecs r+ bnds = integIterationBnds sc+ arr <- newBoxedArray_ bnds+ nref <- newIORef 0+ let r p =+ do let sc = pointSpecs p+ n = pointIteration p+ loop n' = + if n' > n+ then + readArray arr n+ else + let p' = p { pointIteration = n', pointPhase = 0,+ pointTime = basicTime sc n' 0 }+ in do a <- m p'+ a `seq` writeArray arr n' a+ writeIORef nref (n' + 1)+ loop (n' + 1)+ n' <- readIORef nref+ loop n'+ return $ discreteDynamics $ Dynamics r++-- | Iterate sequentially the dynamic process with side effects in +-- the integration time points. It is equivalent to a call of the+-- 'memo0Dynamics' function but significantly more efficient, for the array +-- is not created.+iterateDynamics :: Dynamics () -> Simulation (Dynamics ())+{-# INLINE iterateDynamics #-}+iterateDynamics (Dynamics m) = + Simulation $ \r ->+ do let sc = runSpecs r+ nref <- newIORef 0+ let r p =+ do let sc = pointSpecs p+ n = pointIteration p+ loop n' = + unless (n' > n) $+ let p' = p { pointIteration = n', pointPhase = 0,+ pointTime = basicTime sc n' 0 }+ in do a <- m p'+ a `seq` writeIORef nref (n' + 1)+ loop (n' + 1)+ n' <- readIORef nref+ loop n'+ return $ discreteDynamics $ Dynamics r++-- | Memoize and unzip the computation of pairs, applying the 'memoDynamics' function.+unzipDynamics :: Dynamics (a, b) -> Simulation (Dynamics a, Dynamics b)+unzipDynamics m =+ Simulation $ \r ->+ do m' <- invokeSimulation r (memoDynamics m)+ let ma =+ Dynamics $ \p ->+ do (a, _) <- invokeDynamics p m'+ return a+ mb =+ Dynamics $ \p ->+ do (_, b) <- invokeDynamics p m'+ return b+ return (ma, mb)++-- | Memoize and unzip the computation of pairs, applying the 'memo0Dynamics' function.+unzip0Dynamics :: Dynamics (a, b) -> Simulation (Dynamics a, Dynamics b)+unzip0Dynamics m =+ Simulation $ \r ->+ do m' <- invokeSimulation r (memo0Dynamics m)+ let ma =+ Dynamics $ \p ->+ do (a, _) <- invokeDynamics p m'+ return a+ mb =+ Dynamics $ \p ->+ do (_, b) <- invokeDynamics p m'+ return b+ return (ma, mb)
Simulation/Aivika/Dynamics/Memo/Unboxed.hs view
@@ -1,102 +1,102 @@- -{-# LANGUAGE FlexibleContexts #-} - --- | --- Module : Simulation.Aivika.Dynamics.Memo.Unboxed --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- This module defines the unboxed memo functions. The memoization creates such 'Dynamics' --- computations, which values are cached in the integration time points. Then --- these values are interpolated in all other time points. --- - -module Simulation.Aivika.Dynamics.Memo.Unboxed - (memoDynamics, - memo0Dynamics) where - -import Data.Array -import Data.Array.IO.Safe -import Data.IORef -import Control.Monad - -import Simulation.Aivika.Internal.Specs -import Simulation.Aivika.Internal.Parameter -import Simulation.Aivika.Internal.Simulation -import Simulation.Aivika.Internal.Dynamics -import Simulation.Aivika.Dynamics.Extra -import Simulation.Aivika.Unboxed - --- | Memoize and order the computation in the integration time points using --- the interpolation that knows of the Runge-Kutta method. The values are --- calculated sequentially starting from 'starttime'. -memoDynamics :: Unboxed e => Dynamics e -> Simulation (Dynamics e) -{-# INLINE memoDynamics #-} -memoDynamics (Dynamics m) = - Simulation $ \r -> - do let sc = runSpecs r - (phl, phu) = integPhaseBnds sc - (nl, nu) = integIterationBnds sc - arr <- newUnboxedArray_ ((phl, nl), (phu, nu)) - nref <- newIORef 0 - phref <- newIORef 0 - let r p = - do let sc = pointSpecs p - n = pointIteration p - ph = pointPhase p - phu = integPhaseHiBnd sc - loop n' ph' = - if (n' > n) || ((n' == n) && (ph' > ph)) - then - readArray arr (ph, n) - else - let p' = p { pointIteration = n', - pointPhase = ph', - pointTime = basicTime sc n' ph' } - in do a <- m p' - a `seq` writeArray arr (ph', n') a - if ph' >= phu - then do writeIORef phref 0 - writeIORef nref (n' + 1) - loop (n' + 1) 0 - else do writeIORef phref (ph' + 1) - loop n' (ph' + 1) - n' <- readIORef nref - ph' <- readIORef phref - loop n' ph' - return $ interpolateDynamics $ Dynamics r - --- | Memoize and order the computation in the integration time points using --- the 'discreteDynamics' interpolation. It consumes less memory than the 'memoDynamics' --- function but it is not aware of the Runge-Kutta method. There is a subtle --- difference when we request for values in the intermediate time points --- that are used by this method to integrate. In general case you should --- prefer the 'memo0Dynamics' function above 'memoDynamics'. -memo0Dynamics :: Unboxed e => Dynamics e -> Simulation (Dynamics e) -{-# INLINE memo0Dynamics #-} -memo0Dynamics (Dynamics m) = - Simulation $ \r -> - do let sc = runSpecs r - bnds = integIterationBnds sc - arr <- newUnboxedArray_ bnds - nref <- newIORef 0 - let r p = - do let sc = pointSpecs p - n = pointIteration p - loop n' = - if n' > n - then - readArray arr n - else - let p' = p { pointIteration = n', pointPhase = 0, - pointTime = basicTime sc n' 0 } - in do a <- m p' - a `seq` writeArray arr n' a - writeIORef nref (n' + 1) - loop (n' + 1) - n' <- readIORef nref - loop n' - return $ discreteDynamics $ Dynamics r ++{-# LANGUAGE FlexibleContexts #-}++-- |+-- Module : Simulation.Aivika.Dynamics.Memo.Unboxed+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- This module defines the unboxed memo functions. The memoization creates such 'Dynamics'+-- computations, which values are cached in the integration time points. Then+-- these values are interpolated in all other time points.+--++module Simulation.Aivika.Dynamics.Memo.Unboxed+ (memoDynamics,+ memo0Dynamics) where++import Data.Array+import Data.Array.IO.Safe+import Data.IORef+import Control.Monad++import Simulation.Aivika.Internal.Specs+import Simulation.Aivika.Internal.Parameter+import Simulation.Aivika.Internal.Simulation+import Simulation.Aivika.Internal.Dynamics+import Simulation.Aivika.Dynamics.Extra+import Simulation.Aivika.Unboxed++-- | Memoize and order the computation in the integration time points using +-- the interpolation that knows of the Runge-Kutta method. The values are+-- calculated sequentially starting from 'starttime'.+memoDynamics :: Unboxed e => Dynamics e -> Simulation (Dynamics e)+{-# INLINE memoDynamics #-}+memoDynamics (Dynamics m) = + Simulation $ \r ->+ do let sc = runSpecs r+ (phl, phu) = integPhaseBnds sc+ (nl, nu) = integIterationBnds sc+ arr <- newUnboxedArray_ ((phl, nl), (phu, nu))+ nref <- newIORef 0+ phref <- newIORef 0+ let r p =+ do let sc = pointSpecs p+ n = pointIteration p+ ph = pointPhase p+ phu = integPhaseHiBnd sc + loop n' ph' = + if (n' > n) || ((n' == n) && (ph' > ph)) + then + readArray arr (ph, n)+ else + let p' = p { pointIteration = n', + pointPhase = ph',+ pointTime = basicTime sc n' ph' }+ in do a <- m p'+ a `seq` writeArray arr (ph', n') a+ if ph' >= phu + then do writeIORef phref 0+ writeIORef nref (n' + 1)+ loop (n' + 1) 0+ else do writeIORef phref (ph' + 1)+ loop n' (ph' + 1)+ n' <- readIORef nref+ ph' <- readIORef phref+ loop n' ph'+ return $ interpolateDynamics $ Dynamics r++-- | Memoize and order the computation in the integration time points using +-- the 'discreteDynamics' interpolation. It consumes less memory than the 'memoDynamics'+-- function but it is not aware of the Runge-Kutta method. There is a subtle+-- difference when we request for values in the intermediate time points+-- that are used by this method to integrate. In general case you should +-- prefer the 'memo0Dynamics' function above 'memoDynamics'.+memo0Dynamics :: Unboxed e => Dynamics e -> Simulation (Dynamics e)+{-# INLINE memo0Dynamics #-}+memo0Dynamics (Dynamics m) = + Simulation $ \r ->+ do let sc = runSpecs r+ bnds = integIterationBnds sc+ arr <- newUnboxedArray_ bnds+ nref <- newIORef 0+ let r p =+ do let sc = pointSpecs p+ n = pointIteration p+ loop n' = + if n' > n+ then + readArray arr n+ else + let p' = p { pointIteration = n', pointPhase = 0,+ pointTime = basicTime sc n' 0 }+ in do a <- m p'+ a `seq` writeArray arr n' a+ writeIORef nref (n' + 1)+ loop (n' + 1)+ n' <- readIORef nref+ loop n'+ return $ discreteDynamics $ Dynamics r
Simulation/Aivika/Dynamics/Random.hs view
@@ -1,132 +1,132 @@- --- | --- Module : Simulation.Aivika.Dynamics.Random --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- This module defines the random functions that always return the same values --- in the integration time points within a single simulation run. The values --- for another simulation run will be regenerated anew. --- --- For example, the computations returned by these functions can be used in --- the equations of System Dynamics. --- --- Also it is worth noting that the values are generated in a strong order starting --- from 'starttime' with step 'dt'. This is how the 'memo0Dynamics' function --- actually works. --- - -module Simulation.Aivika.Dynamics.Random - (memoRandomUniformDynamics, - memoRandomUniformIntDynamics, - memoRandomNormalDynamics, - memoRandomExponentialDynamics, - memoRandomErlangDynamics, - memoRandomPoissonDynamics, - memoRandomBinomialDynamics) where - -import System.Random - -import Control.Monad.Trans - -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 - --- | 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 - generateUniform g min' max' - --- | Computation that generates random integer numbers distributed uniformly and --- memoizes them in the integration time points. -memoRandomUniformIntDynamics :: Dynamics Int -- ^ minimum - -> Dynamics Int -- ^ maximum - -> Simulation (Dynamics Int) -memoRandomUniformIntDynamics min max = - memo0Dynamics $ - Dynamics $ \p -> - do let g = runGenerator $ pointRun p - min' <- invokeDynamics p min - max' <- invokeDynamics p max - generateUniformInt 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 - generateNormal 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 - generateExponential 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 - generateErlang 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 - generatePoisson 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 - generateBinomial g prob' trials' ++-- |+-- Module : Simulation.Aivika.Dynamics.Random+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- This module defines the random functions that always return the same values+-- in the integration time points within a single simulation run. The values+-- for another simulation run will be regenerated anew.+--+-- For example, the computations returned by these functions can be used in+-- the equations of System Dynamics.+--+-- Also it is worth noting that the values are generated in a strong order starting+-- from 'starttime' with step 'dt'. This is how the 'memo0Dynamics' function+-- actually works.+--++module Simulation.Aivika.Dynamics.Random+ (memoRandomUniformDynamics,+ memoRandomUniformIntDynamics,+ memoRandomNormalDynamics,+ memoRandomExponentialDynamics,+ memoRandomErlangDynamics,+ memoRandomPoissonDynamics,+ memoRandomBinomialDynamics) where++import System.Random++import Control.Monad.Trans++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++-- | 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+ generateUniform g min' max'++-- | Computation that generates random integer numbers distributed uniformly and+-- memoizes them in the integration time points.+memoRandomUniformIntDynamics :: Dynamics Int -- ^ minimum+ -> Dynamics Int -- ^ maximum+ -> Simulation (Dynamics Int)+memoRandomUniformIntDynamics min max =+ memo0Dynamics $+ Dynamics $ \p ->+ do let g = runGenerator $ pointRun p+ min' <- invokeDynamics p min+ max' <- invokeDynamics p max+ generateUniformInt 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+ generateNormal 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+ generateExponential 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+ generateErlang 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+ generatePoisson 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+ generateBinomial g prob' trials'
Simulation/Aivika/Event.hs view
@@ -1,44 +1,44 @@- --- | --- Module : Simulation.Aivika.Event --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- The module defines the 'Event' monad which is very similar to the 'Dynamics' --- monad but only now the computation is strongly synchronized with the event queue. --- -module Simulation.Aivika.Event - (-- * Event Monad - Event, - EventLift(..), - EventProcessing(..), - runEvent, - runEventWith, - runEventInStartTime, - runEventInStopTime, - -- * Event Queue - enqueueEvent, - enqueueEventWithCancellation, - enqueueEventWithTimes, - enqueueEventWithIntegTimes, - yieldEvent, - eventQueueCount, - -- * Cancelling Event - EventCancellation, - cancelEvent, - eventCancelled, - eventFinished, - -- * Error Handling - catchEvent, - finallyEvent, - throwEvent, - -- * Memoization - memoEvent, - memoEventInTime, - -- * Disposable - DisposableEvent(..)) where - -import Simulation.Aivika.Internal.Event ++-- |+-- Module : Simulation.Aivika.Event+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- The module defines the 'Event' monad which is very similar to the 'Dynamics'+-- monad but only now the computation is strongly synchronized with the event queue.+--+module Simulation.Aivika.Event+ (-- * Event Monad+ Event,+ EventLift(..),+ EventProcessing(..),+ runEvent,+ runEventWith,+ runEventInStartTime,+ runEventInStopTime,+ -- * Event Queue+ enqueueEvent,+ enqueueEventWithCancellation,+ enqueueEventWithTimes,+ enqueueEventWithIntegTimes,+ yieldEvent,+ eventQueueCount,+ -- * Cancelling Event+ EventCancellation,+ cancelEvent,+ eventCancelled,+ eventFinished,+ -- * Error Handling+ catchEvent,+ finallyEvent,+ throwEvent,+ -- * Memoization+ memoEvent,+ memoEventInTime,+ -- * Disposable+ DisposableEvent(..)) where++import Simulation.Aivika.Internal.Event
Simulation/Aivika/Generator.hs view
@@ -1,220 +1,220 @@- --- | --- Module : Simulation.Aivika.Generator --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.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 { generateUniform :: Double -> Double -> IO Double, - -- ^ Generate an uniform random number - -- with the specified minimum and maximum. - generateUniformInt :: Int -> Int -> IO Int, - -- ^ Generate an uniform integer random number - -- with the specified minimum and maximum. - generateNormal :: Double -> Double -> IO Double, - -- ^ Generate the normal random number - -- with the specified mean and deviation. - generateExponential :: Double -> IO Double, - -- ^ Generate the random number distributed exponentially - -- with the specified mean (the reciprocal of the rate). - generateErlang :: Double -> Int -> IO Double, - -- ^ Generate the Erlang random number - -- with the specified scale (the reciprocal of the rate) and integer shape. - generatePoisson :: Double -> IO Int, - -- ^ Generate the Poisson random number - -- with the specified mean. - generateBinomial :: 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. -generateUniform01 :: IO Double - -- ^ the generator - -> Double - -- ^ minimum - -> Double - -- ^ maximum - -> IO Double -generateUniform01 g min max = - do x <- g - return $ min + x * (max - min) - --- | Generate the uniform random number with the specified minimum and maximum. -generateUniformInt01 :: IO Double - -- ^ the generator - -> Int - -- ^ minimum - -> Int - -- ^ maximum - -> IO Int -generateUniformInt01 g min max = - do x <- g - let min' = fromIntegral min - max' = fromIntegral max - return $ round (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. -newNormalGenerator01 :: IO Double - -- ^ the generator - -> IO (IO Double) -newNormalGenerator01 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. -generateExponential01 :: IO Double - -- ^ the generator - -> Double - -- ^ the mean - -> IO Double -generateExponential01 g mu = - do x <- g - return (- log x * mu) - --- | Return the Erlang random number. -generateErlang01 :: IO Double - -- ^ the generator - -> Double - -- ^ the scale - -> Int - -- ^ the shape - -> IO Double -generateErlang01 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. -generatePoisson01 :: IO Double - -- ^ the generator - -> Double - -- ^ the mean - -> IO Int -generatePoisson01 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. -generateBinomial01 :: IO Double - -- ^ the generator - -> Double - -- ^ the probability - -> Int - -- ^ the number of trials - -> IO Int -generateBinomial01 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. - | CustomGenerator01 (IO Double) - -- ^ The custom random number generator by the specified uniform - -- generator of numbers from 0 to 1. - --- | 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 - CustomGenerator01 g -> - newRandomGenerator01 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 - newRandomGenerator01 g1 - --- | Create a new random generator by the specified uniform generator of numbers from 0 to 1. -newRandomGenerator01 :: IO Double -> IO Generator -newRandomGenerator01 g = - do let g1 = g - g2 <- newNormalGenerator01 g1 - let g3 mu nu = - do x <- g2 - return $ mu + nu * x - return Generator { generateUniform = generateUniform01 g1, - generateUniformInt = generateUniformInt01 g1, - generateNormal = g3, - generateExponential = generateExponential01 g1, - generateErlang = generateErlang01 g1, - generatePoisson = generatePoisson01 g1, - generateBinomial = generateBinomial01 g1 } ++-- |+-- Module : Simulation.Aivika.Generator+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.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 { generateUniform :: Double -> Double -> IO Double,+ -- ^ Generate an uniform random number+ -- with the specified minimum and maximum.+ generateUniformInt :: Int -> Int -> IO Int,+ -- ^ Generate an uniform integer random number+ -- with the specified minimum and maximum.+ generateNormal :: Double -> Double -> IO Double,+ -- ^ Generate the normal random number+ -- with the specified mean and deviation.+ generateExponential :: Double -> IO Double,+ -- ^ Generate the random number distributed exponentially+ -- with the specified mean (the reciprocal of the rate).+ generateErlang :: Double -> Int -> IO Double,+ -- ^ Generate the Erlang random number+ -- with the specified scale (the reciprocal of the rate) and integer shape.+ generatePoisson :: Double -> IO Int,+ -- ^ Generate the Poisson random number+ -- with the specified mean.+ generateBinomial :: 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.+generateUniform01 :: IO Double+ -- ^ the generator+ -> Double+ -- ^ minimum+ -> Double+ -- ^ maximum+ -> IO Double+generateUniform01 g min max =+ do x <- g+ return $ min + x * (max - min)++-- | Generate the uniform random number with the specified minimum and maximum.+generateUniformInt01 :: IO Double+ -- ^ the generator+ -> Int+ -- ^ minimum+ -> Int+ -- ^ maximum+ -> IO Int+generateUniformInt01 g min max =+ do x <- g+ let min' = fromIntegral min+ max' = fromIntegral max+ return $ round (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.+newNormalGenerator01 :: IO Double+ -- ^ the generator+ -> IO (IO Double)+newNormalGenerator01 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.+generateExponential01 :: IO Double+ -- ^ the generator+ -> Double+ -- ^ the mean+ -> IO Double+generateExponential01 g mu =+ do x <- g+ return (- log x * mu)++-- | Return the Erlang random number.+generateErlang01 :: IO Double+ -- ^ the generator+ -> Double+ -- ^ the scale+ -> Int+ -- ^ the shape+ -> IO Double+generateErlang01 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.+generatePoisson01 :: IO Double+ -- ^ the generator+ -> Double+ -- ^ the mean+ -> IO Int+generatePoisson01 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. +generateBinomial01 :: IO Double+ -- ^ the generator+ -> Double + -- ^ the probability+ -> Int+ -- ^ the number of trials+ -> IO Int+generateBinomial01 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.+ | CustomGenerator01 (IO Double)+ -- ^ The custom random number generator by the specified uniform+ -- generator of numbers from 0 to 1.++-- | 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+ CustomGenerator01 g ->+ newRandomGenerator01 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+ newRandomGenerator01 g1++-- | Create a new random generator by the specified uniform generator of numbers from 0 to 1.+newRandomGenerator01 :: IO Double -> IO Generator+newRandomGenerator01 g =+ do let g1 = g+ g2 <- newNormalGenerator01 g1+ let g3 mu nu =+ do x <- g2+ return $ mu + nu * x+ return Generator { generateUniform = generateUniform01 g1,+ generateUniformInt = generateUniformInt01 g1,+ generateNormal = g3,+ generateExponential = generateExponential01 g1,+ generateErlang = generateErlang01 g1,+ generatePoisson = generatePoisson01 g1,+ generateBinomial = generateBinomial01 g1 }
Simulation/Aivika/Internal/Arrival.hs view
@@ -1,39 +1,39 @@- --- | --- Module : Simulation.Aivika.Internal.Arrival --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- This module defines the types and functions for working with the events --- that can represent something that arrive from outside the model, or --- represent other things which computation is delayed and hence is not synchronized. --- --- Therefore, the additional information is provided about the time and delay of arrival. - -module Simulation.Aivika.Internal.Arrival - (Arrival(..)) where - -import Simulation.Aivika.Event - --- | It defines when an event has arrived, usually generated by some random stream. --- --- Such events should arrive one by one without time lag in the following sense --- that the model should start awaiting the next event exactly in that time --- when the previous event has arrived. --- --- Another use case is a situation when the actual event is not synchronized with --- the 'Event' computation, being synchronized with the event queue, nevertheless. --- Then the arrival is used for providing the additional information about the time --- at which the event had been actually arrived. -data Arrival a = - Arrival { arrivalValue :: a, - -- ^ the data we received with the event - arrivalTime :: Double, - -- ^ the simulation time at which the event has arrived - arrivalDelay :: Maybe Double - -- ^ the delay time which has passed from the time of - -- arriving the previous event - } deriving (Eq, Ord, Show) ++-- |+-- Module : Simulation.Aivika.Internal.Arrival+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- This module defines the types and functions for working with the events+-- that can represent something that arrive from outside the model, or+-- represent other things which computation is delayed and hence is not synchronized.+--+-- Therefore, the additional information is provided about the time and delay of arrival.++module Simulation.Aivika.Internal.Arrival+ (Arrival(..)) where++import Simulation.Aivika.Event++-- | It defines when an event has arrived, usually generated by some random stream.+--+-- Such events should arrive one by one without time lag in the following sense+-- that the model should start awaiting the next event exactly in that time+-- when the previous event has arrived.+--+-- Another use case is a situation when the actual event is not synchronized with+-- the 'Event' computation, being synchronized with the event queue, nevertheless.+-- Then the arrival is used for providing the additional information about the time+-- at which the event had been actually arrived.+data Arrival a =+ Arrival { arrivalValue :: a,+ -- ^ the data we received with the event+ arrivalTime :: Double,+ -- ^ the simulation time at which the event has arrived+ arrivalDelay :: Maybe Double+ -- ^ the delay time which has passed from the time of+ -- arriving the previous event+ } deriving (Eq, Ord, Show)
Simulation/Aivika/Internal/Cont.hs view
@@ -1,671 +1,671 @@- --- | --- Module : Simulation.Aivika.Internal.Cont --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- The 'Cont' monad is a variation of the standard Cont monad --- and F# async workflow, where the result of applying --- the continuations is the 'Event' computation. --- -module Simulation.Aivika.Internal.Cont - (ContCancellation(..), - ContCancellationSource, - Cont(..), - ContParams, - newContCancellationSource, - contCancellationInitiated, - contCancellationInitiate, - contCancellationInitiating, - contCancellationBind, - contCancellationConnect, - invokeCont, - runCont, - rerunCont, - spawnCont, - contParallel, - contParallel_, - catchCont, - finallyCont, - throwCont, - resumeCont, - resumeECont, - contCanceled, - contFreeze, - contAwait) where - -import Data.IORef -import Data.Array -import Data.Array.IO.Safe -import Data.Monoid - -import Control.Exception -import Control.Monad -import Control.Monad.Trans -import Control.Applicative - -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 DisposableEvent -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 $ mconcat hs1 <> mconcat hs2 - --- | Connect the parent computation to the child one. -contCancellationConnect :: ContCancellationSource - -- ^ the parent - -> ContCancellation - -- ^ how to connect - -> ContCancellationSource - -- ^ the child - -> Event DisposableEvent - -- ^ 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 mempty - CancelInIsolation -> return mempty - h2 <- - case cancellation of - CancelTogether -> invokeEvent p m2 - CancelChildAfterParent -> return mempty - CancelParentAfterChild -> invokeEvent p m2 - CancelInIsolation -> return mempty - 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. -newtype Cont a = Cont (ContParams a -> Event ()) - --- | The continuation parameters. -data ContParams a = - ContParams { contCont :: a -> Event (), - contAux :: ContParamsAux } - --- | The auxiliary continuation parameters. -data ContParamsAux = - ContParamsAux { contECont :: SomeException -> Event (), - contCCont :: () -> Event (), - 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 - -instance DynamicsLift Cont where - liftDynamics = liftDC - -instance EventLift Cont where - liftEvent = liftEC - -instance Functor Cont where - fmap = liftM - -instance Applicative Cont where - pure = return - (<*>) = ap - -instance MonadIO Cont where - liftIO = liftIOC - --- | Invoke the computation. -invokeCont :: ContParams a -> Cont a -> Event () -{-# INLINE invokeCont #-} -invokeCont p (Cont m) = m p - --- | Cancel the computation. -cancelCont :: Point -> ContParams a -> IO () -{-# NOINLINE cancelCont #-} -cancelCont p c = - do contCancellationDeactivate (contCancelSource $ contAux c) - invokeEvent p $ (contCCont $ contAux c) () - -returnC :: a -> Cont a -{-# INLINE returnC #-} -returnC a = - Cont $ \c -> - Event $ \p -> - do z <- contCanceled c - if z - then cancelCont p c - else invokeEvent p $ contCont c a - -bindC :: Cont a -> (a -> Cont b) -> Cont b -{-# INLINE bindC #-} -bindC (Cont m) k = - Cont $ \c -> - Event $ \p -> - do z <- contCanceled c - if z - then cancelCont p c - else invokeEvent p $ m $ - let cont a = invokeCont c (k a) - in c { contCont = cont } - --- | Like @return a >>= k@. -callCont :: (a -> Cont b) -> a -> ContParams b -> Event () -callCont k a c = - Event $ \p -> - do z <- contCanceled c - if z - then cancelCont p c - else invokeEvent p $ invokeCont c (k a) - --- | Exception handling within 'Cont' computations. -catchCont :: Exception e => Cont a -> (e -> Cont a) -> Cont a -catchCont (Cont m) h = - Cont $ \c0 -> - Event $ \p -> - do let c = c0 { contAux = (contAux c0) { contCatchFlag = True } } - z <- contCanceled c - if z - then cancelCont p c - else invokeEvent p $ m $ - let econt e0 = - case fromException e0 of - Just e -> callCont h e c - Nothing -> (contECont . contAux $ c) e0 - in c { contAux = (contAux c) { contECont = econt } } - --- | A computation with finalization part. -finallyCont :: Cont a -> Cont b -> Cont a -finallyCont (Cont m) (Cont m') = - Cont $ \c0 -> - Event $ \p -> - do let c = c0 { contAux = (contAux c0) { contCatchFlag = True } } - z <- contCanceled c - if z - then cancelCont p c - else invokeEvent p $ m $ - let cont a = - Event $ \p -> - invokeEvent p $ m' $ - let cont b = contCont c a - in c { contCont = cont } - econt e = - Event $ \p -> - invokeEvent p $ m' $ - let cont b = (contECont . contAux $ c) e - in c { contCont = cont } - ccont () = - Event $ \p -> - invokeEvent p $ m' $ - let cont b = (contCCont . contAux $ c) () - econt e = (contCCont . contAux $ c) () - in c { contCont = cont, - contAux = (contAux c) { contECont = econt } } - in c { contCont = cont, - contAux = (contAux c) { contECont = econt, - contCCont = ccont } } - --- | Throw the exception with the further exception handling. --- --- By some reason, an exception raised with help of the standard 'throw' function --- is not handled properly within 'Cont' computation, altough it will be still handled --- if it will be wrapped in the 'IO' monad. Therefore, you should use specialised --- functions like the stated one that use the 'throw' function but within the 'IO' computation, --- which allows already handling the exception. -throwCont :: IOException -> Cont a -throwCont = liftIO . throw - --- | 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 ()) - -- ^ the main branch - -> (SomeException -> Event ()) - -- ^ the branch for handing exceptions - -> (() -> Event ()) - -- ^ the branch for cancellation - -> ContCancellationSource - -- ^ the cancellation source - -> Bool - -- ^ whether to support the exception handling from the beginning - -> Event () -runCont (Cont m) cont econt ccont cancelSource catchFlag = - m ContParams { contCont = cont, - contAux = - ContParamsAux { contECont = econt, - contCCont = ccont, - 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) = - Cont $ \c -> - Event $ \p -> - if contCatchFlag . contAux $ c - then liftIOWithCatch (m $ pointRun p) p c - else liftIOWithoutCatch (m $ pointRun p) p c - --- | Lift the 'Dynamics' computation. -liftDC :: Dynamics a -> Cont a -liftDC (Dynamics m) = - Cont $ \c -> - Event $ \p -> - if contCatchFlag . contAux $ c - then liftIOWithCatch (m p) p c - else liftIOWithoutCatch (m p) p c - --- | Lift the 'Event' computation. -liftEC :: Event a -> Cont a -liftEC (Event m) = - Cont $ \c -> - Event $ \p -> - if contCatchFlag . contAux $ c - then liftIOWithCatch (m p) p c - else liftIOWithoutCatch (m p) p c - --- | Lift the IO computation. -liftIOC :: IO a -> Cont a -liftIOC m = - Cont $ \c -> - Event $ \p -> - if contCatchFlag . contAux $ c - then liftIOWithCatch m p c - else liftIOWithoutCatch m p c - -liftIOWithoutCatch :: IO a -> Point -> ContParams a -> IO () -{-# INLINE liftIOWithoutCatch #-} -liftIOWithoutCatch m p c = - do z <- contCanceled c - if z - then cancelCont p c - else do a <- m - invokeEvent p $ contCont c a - -liftIOWithCatch :: IO a -> Point -> ContParams a -> IO () -{-# NOINLINE liftIOWithCatch #-} -liftIOWithCatch m p c = - do z <- contCanceled c - if z - then cancelCont p c - else do aref <- newIORef undefined - eref <- newIORef Nothing - catch (m >>= writeIORef aref) - (writeIORef eref . Just) - e <- readIORef eref - case e of - Nothing -> - do a <- readIORef aref - -- tail recursive - invokeEvent p $ contCont c a - Just e -> - -- tail recursive - invokeEvent p $ (contECont . contAux) c e - --- | Resume the computation by the specified parameters. -resumeCont :: ContParams a -> a -> Event () -{-# INLINE resumeCont #-} -resumeCont c a = - Event $ \p -> - do z <- contCanceled c - if z - then cancelCont p c - else invokeEvent p $ contCont c a - --- | Resume the exception handling by the specified parameters. -resumeECont :: ContParams a -> SomeException -> 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 = 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 $ disposeEvent 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 $ disposeEvent 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 $ disposeEvent hs -- unbind the cancellation source - invokeEvent p $ resumeCont c a - econt e = - Event $ \p -> - do invokeEvent p $ disposeEvent hs -- unbind the cancellation source - invokeEvent p $ resumeECont c e - ccont e = - Event $ \p -> - do invokeEvent p $ disposeEvent 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 $ disposeEvent hs -- unbind the cancellation source - -- do nothing and it will finish the computation - econt e = - Event $ \p -> - do invokeEvent p $ disposeEvent hs -- unbind the cancellation source - invokeEvent p $ throwEvent e -- this is all we can do - ccont e = - Event $ \p -> - do invokeEvent p $ disposeEvent 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 $ disposeEvent 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 $ disposeEvent 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: contAwait." - Just x -> - do invokeEvent p $ disposeEvent x - c <- invokeEvent p c - case c of - Nothing -> return () - Just c -> - invokeEvent p $ resumeCont c a - writeIORef r $ Just h ++-- |+-- Module : Simulation.Aivika.Internal.Cont+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- The 'Cont' monad is a variation of the standard Cont monad +-- and F# async workflow, where the result of applying +-- the continuations is the 'Event' computation.+--+module Simulation.Aivika.Internal.Cont+ (ContCancellation(..),+ ContCancellationSource,+ Cont(..),+ ContParams,+ newContCancellationSource,+ contCancellationInitiated,+ contCancellationInitiate,+ contCancellationInitiating,+ contCancellationBind,+ contCancellationConnect,+ invokeCont,+ runCont,+ rerunCont,+ spawnCont,+ contParallel,+ contParallel_,+ catchCont,+ finallyCont,+ throwCont,+ resumeCont,+ resumeECont,+ contCanceled,+ contFreeze,+ contAwait) where++import Data.IORef+import Data.Array+import Data.Array.IO.Safe+import Data.Monoid++import Control.Exception+import Control.Monad+import Control.Monad.Trans+import Control.Applicative++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 DisposableEvent+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 $ mconcat hs1 <> mconcat hs2++-- | Connect the parent computation to the child one.+contCancellationConnect :: ContCancellationSource+ -- ^ the parent+ -> ContCancellation+ -- ^ how to connect+ -> ContCancellationSource+ -- ^ the child+ -> Event DisposableEvent+ -- ^ 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 mempty+ CancelInIsolation -> return mempty+ h2 <-+ case cancellation of+ CancelTogether -> invokeEvent p m2+ CancelChildAfterParent -> return mempty+ CancelParentAfterChild -> invokeEvent p m2+ CancelInIsolation -> return mempty+ 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.+newtype Cont a = Cont (ContParams a -> Event ())++-- | The continuation parameters.+data ContParams a = + ContParams { contCont :: a -> Event (), + contAux :: ContParamsAux }++-- | The auxiliary continuation parameters.+data ContParamsAux =+ ContParamsAux { contECont :: SomeException -> Event (),+ contCCont :: () -> Event (),+ 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++instance DynamicsLift Cont where+ liftDynamics = liftDC++instance EventLift Cont where+ liftEvent = liftEC++instance Functor Cont where+ fmap = liftM++instance Applicative Cont where+ pure = return+ (<*>) = ap++instance MonadIO Cont where+ liftIO = liftIOC ++-- | Invoke the computation.+invokeCont :: ContParams a -> Cont a -> Event ()+{-# INLINE invokeCont #-}+invokeCont p (Cont m) = m p++-- | Cancel the computation.+cancelCont :: Point -> ContParams a -> IO ()+{-# NOINLINE cancelCont #-}+cancelCont p c =+ do contCancellationDeactivate (contCancelSource $ contAux c)+ invokeEvent p $ (contCCont $ contAux c) ()++returnC :: a -> Cont a+{-# INLINE returnC #-}+returnC a = + Cont $ \c ->+ Event $ \p ->+ do z <- contCanceled c+ if z + then cancelCont p c+ else invokeEvent p $ contCont c a+ +bindC :: Cont a -> (a -> Cont b) -> Cont b+{-# INLINE bindC #-}+bindC (Cont m) k =+ Cont $ \c ->+ Event $ \p ->+ do z <- contCanceled c+ if z + then cancelCont p c+ else invokeEvent p $ m $ + let cont a = invokeCont c (k a)+ in c { contCont = cont }++-- | Like @return a >>= k@.+callCont :: (a -> Cont b) -> a -> ContParams b -> Event ()+callCont k a c =+ Event $ \p ->+ do z <- contCanceled c+ if z + then cancelCont p c+ else invokeEvent p $ invokeCont c (k a)++-- | Exception handling within 'Cont' computations.+catchCont :: Exception e => Cont a -> (e -> Cont a) -> Cont a+catchCont (Cont m) h = + Cont $ \c0 ->+ Event $ \p ->+ do let c = c0 { contAux = (contAux c0) { contCatchFlag = True } }+ z <- contCanceled c+ if z + then cancelCont p c+ else invokeEvent p $ m $+ let econt e0 =+ case fromException e0 of+ Just e -> callCont h e c+ Nothing -> (contECont . contAux $ c) e0+ in c { contAux = (contAux c) { contECont = econt } }+ +-- | A computation with finalization part.+finallyCont :: Cont a -> Cont b -> Cont a+finallyCont (Cont m) (Cont m') = + Cont $ \c0 ->+ Event $ \p ->+ do let c = c0 { contAux = (contAux c0) { contCatchFlag = True } }+ z <- contCanceled c+ if z + then cancelCont p c+ else invokeEvent p $ m $+ let cont a = + Event $ \p ->+ invokeEvent p $ m' $+ let cont b = contCont c a+ in c { contCont = cont }+ econt e =+ Event $ \p ->+ invokeEvent p $ m' $+ let cont b = (contECont . contAux $ c) e+ in c { contCont = cont }+ ccont () = + Event $ \p ->+ invokeEvent p $ m' $+ let cont b = (contCCont . contAux $ c) ()+ econt e = (contCCont . contAux $ c) ()+ in c { contCont = cont,+ contAux = (contAux c) { contECont = econt } }+ in c { contCont = cont,+ contAux = (contAux c) { contECont = econt,+ contCCont = ccont } }++-- | Throw the exception with the further exception handling.+--+-- By some reason, an exception raised with help of the standard 'throw' function+-- is not handled properly within 'Cont' computation, altough it will be still handled +-- if it will be wrapped in the 'IO' monad. Therefore, you should use specialised+-- functions like the stated one that use the 'throw' function but within the 'IO' computation,+-- which allows already handling the exception.+throwCont :: IOException -> Cont a+throwCont = liftIO . throw++-- | 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 ())+ -- ^ the main branch + -> (SomeException -> Event ())+ -- ^ the branch for handing exceptions+ -> (() -> Event ())+ -- ^ the branch for cancellation+ -> ContCancellationSource+ -- ^ the cancellation source+ -> Bool+ -- ^ whether to support the exception handling from the beginning+ -> Event ()+runCont (Cont m) cont econt ccont cancelSource catchFlag = + m ContParams { contCont = cont,+ contAux = + ContParamsAux { contECont = econt,+ contCCont = ccont,+ 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) = + Cont $ \c ->+ Event $ \p ->+ if contCatchFlag . contAux $ c+ then liftIOWithCatch (m $ pointRun p) p c+ else liftIOWithoutCatch (m $ pointRun p) p c+ +-- | Lift the 'Dynamics' computation.+liftDC :: Dynamics a -> Cont a+liftDC (Dynamics m) =+ Cont $ \c ->+ Event $ \p ->+ if contCatchFlag . contAux $ c+ then liftIOWithCatch (m p) p c+ else liftIOWithoutCatch (m p) p c+ +-- | Lift the 'Event' computation.+liftEC :: Event a -> Cont a+liftEC (Event m) =+ Cont $ \c ->+ Event $ \p ->+ if contCatchFlag . contAux $ c+ then liftIOWithCatch (m p) p c+ else liftIOWithoutCatch (m p) p c+ +-- | Lift the IO computation.+liftIOC :: IO a -> Cont a+liftIOC m =+ Cont $ \c ->+ Event $ \p ->+ if contCatchFlag . contAux $ c+ then liftIOWithCatch m p c+ else liftIOWithoutCatch m p c+ +liftIOWithoutCatch :: IO a -> Point -> ContParams a -> IO ()+{-# INLINE liftIOWithoutCatch #-}+liftIOWithoutCatch m p c =+ do z <- contCanceled c+ if z+ then cancelCont p c+ else do a <- m+ invokeEvent p $ contCont c a++liftIOWithCatch :: IO a -> Point -> ContParams a -> IO ()+{-# NOINLINE liftIOWithCatch #-}+liftIOWithCatch m p c =+ do z <- contCanceled c+ if z+ then cancelCont p c+ else do aref <- newIORef undefined+ eref <- newIORef Nothing+ catch (m >>= writeIORef aref) + (writeIORef eref . Just)+ e <- readIORef eref+ case e of+ Nothing -> + do a <- readIORef aref+ -- tail recursive+ invokeEvent p $ contCont c a+ Just e ->+ -- tail recursive+ invokeEvent p $ (contECont . contAux) c e++-- | Resume the computation by the specified parameters.+resumeCont :: ContParams a -> a -> Event ()+{-# INLINE resumeCont #-}+resumeCont c a = + Event $ \p ->+ do z <- contCanceled c+ if z+ then cancelCont p c+ else invokeEvent p $ contCont c a++-- | Resume the exception handling by the specified parameters.+resumeECont :: ContParams a -> SomeException -> 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 = 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 $ disposeEvent 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 $ disposeEvent 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 $ disposeEvent hs -- unbind the cancellation source+ invokeEvent p $ resumeCont c a+ econt e =+ Event $ \p ->+ do invokeEvent p $ disposeEvent hs -- unbind the cancellation source+ invokeEvent p $ resumeECont c e+ ccont e =+ Event $ \p ->+ do invokeEvent p $ disposeEvent 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 $ disposeEvent hs -- unbind the cancellation source+ -- do nothing and it will finish the computation+ econt e =+ Event $ \p ->+ do invokeEvent p $ disposeEvent hs -- unbind the cancellation source+ invokeEvent p $ throwEvent e -- this is all we can do+ ccont e =+ Event $ \p ->+ do invokeEvent p $ disposeEvent 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 $ disposeEvent 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 $ disposeEvent 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: contAwait."+ Just x ->+ do invokeEvent p $ disposeEvent 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
@@ -1,214 +1,214 @@- -{-# LANGUAGE RecursiveDo #-} - --- | --- Module : Simulation.Aivika.Internal.Dynamics --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- The module defines the 'Dynamics' monad representing a time varying polymorphic function. --- -module Simulation.Aivika.Internal.Dynamics - (-- * Dynamics - Dynamics(..), - DynamicsLift(..), - invokeDynamics, - runDynamicsInStartTime, - runDynamicsInStopTime, - runDynamicsInIntegTimes, - runDynamicsInTime, - runDynamicsInTimes, - -- * Error Handling - catchDynamics, - finallyDynamics, - throwDynamics, - -- * Simulation Time - time, - isTimeInteg, - integIteration, - integPhase) where - -import Control.Exception -import Control.Monad -import Control.Monad.Trans -import Control.Monad.Fix -import Control.Applicative - -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. -newtype Dynamics a = Dynamics (Point -> IO a) - -instance Monad Dynamics where - return = returnD - m >>= k = bindD m k - -returnD :: a -> Dynamics a -{-# INLINE returnD #-} -returnD a = Dynamics (\p -> return a) - -bindD :: Dynamics a -> (a -> Dynamics b) -> Dynamics b -{-# INLINE bindD #-} -bindD (Dynamics m) k = - Dynamics $ \p -> - do a <- m p - let Dynamics m' = k a - m' p - --- | Run the 'Dynamics' computation in the initial time point. -runDynamicsInStartTime :: Dynamics a -> Simulation a -runDynamicsInStartTime (Dynamics m) = - Simulation $ m . integStartPoint - --- | Run the 'Dynamics' computation in the final time point. -runDynamicsInStopTime :: Dynamics a -> Simulation a -runDynamicsInStopTime (Dynamics m) = - Simulation $ m . integStopPoint - --- | Run the 'Dynamics' computation in all integration time points. -runDynamicsInIntegTimes :: Dynamics a -> Simulation [IO a] -runDynamicsInIntegTimes (Dynamics m) = - Simulation $ return . map m . integPoints - --- | Run the 'Dynamics' computation in the specified time point. -runDynamicsInTime :: Double -> Dynamics a -> Simulation a -runDynamicsInTime t (Dynamics m) = - Simulation $ \r -> m $ pointAt r t - --- | Run the 'Dynamics' computation in the specified time points. -runDynamicsInTimes :: [Double] -> Dynamics a -> Simulation [IO a] -runDynamicsInTimes ts (Dynamics m) = - Simulation $ \r -> return $ map (m . pointAt r) ts - -instance Functor Dynamics where - fmap = liftMD - -instance Applicative Dynamics where - pure = return - (<*>) = ap - -instance Eq (Dynamics a) where - x == y = error "Can't compare dynamics." - -instance Show (Dynamics a) where - showsPrec _ x = showString "<< Dynamics >>" - -liftMD :: (a -> b) -> Dynamics a -> Dynamics b -{-# INLINE liftMD #-} -liftMD f (Dynamics x) = - Dynamics $ \p -> do { a <- x p; return $ f a } - -liftM2D :: (a -> b -> c) -> Dynamics a -> Dynamics b -> Dynamics c -{-# INLINE liftM2D #-} -liftM2D f (Dynamics x) (Dynamics y) = - Dynamics $ \p -> do { a <- x p; b <- y p; return $ f a b } - -instance (Num a) => Num (Dynamics a) where - x + y = liftM2D (+) x y - x - y = liftM2D (-) x y - x * y = liftM2D (*) x y - negate = liftMD negate - abs = liftMD abs - signum = liftMD signum - fromInteger i = return $ fromInteger i - -instance (Fractional a) => Fractional (Dynamics a) where - x / y = liftM2D (/) x y - recip = liftMD recip - fromRational t = return $ fromRational t - -instance (Floating a) => Floating (Dynamics a) where - pi = return pi - exp = liftMD exp - log = liftMD log - sqrt = liftMD sqrt - x ** y = liftM2D (**) x y - sin = liftMD sin - cos = liftMD cos - tan = liftMD tan - asin = liftMD asin - acos = liftMD acos - atan = liftMD atan - sinh = liftMD sinh - cosh = liftMD cosh - tanh = liftMD tanh - asinh = liftMD asinh - acosh = liftMD acosh - atanh = liftMD atanh - -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 computations. -class DynamicsLift m where - - -- | Lift the specified 'Dynamics' computation to another computation. - liftDynamics :: Dynamics a -> m a - -instance DynamicsLift Dynamics where - liftDynamics = id - --- | Exception handling within 'Dynamics' computations. -catchDynamics :: Exception e => Dynamics a -> (e -> Dynamics a) -> Dynamics a -catchDynamics (Dynamics m) h = - Dynamics $ \p -> - catch (m p) $ \e -> - let Dynamics m' = h e in m' p - --- | A computation with finalization part like the 'finally' function. -finallyDynamics :: Dynamics a -> Dynamics b -> Dynamics a -finallyDynamics (Dynamics m) (Dynamics m') = - Dynamics $ \p -> - finally (m p) (m' p) - --- | Like the standard 'throw' function. -throwDynamics :: Exception e => e -> Dynamics a -throwDynamics = throw - --- | Invoke the 'Dynamics' computation. -invokeDynamics :: Point -> Dynamics a -> IO a -{-# INLINE invokeDynamics #-} -invokeDynamics p (Dynamics m) = m p - -instance MonadFix Dynamics where - mfix f = - Dynamics $ \p -> - do { rec { a <- invokeDynamics p (f a) }; return a } - --- | Computation that returns the current simulation time. -time :: Dynamics Double -time = Dynamics $ return . pointTime - --- | Whether the current time is an integration time. -isTimeInteg :: Dynamics Bool -isTimeInteg = Dynamics $ \p -> return $ pointPhase p >= 0 - --- | Return the integration iteration closest to the current simulation time. -integIteration :: Dynamics Int -integIteration = Dynamics $ return . pointIteration - --- | Return the integration phase for the current simulation time. --- It is @(-1)@ for non-integration time points. -integPhase :: Dynamics Int -integPhase = Dynamics $ return . pointPhase ++{-# LANGUAGE RecursiveDo #-}++-- |+-- Module : Simulation.Aivika.Internal.Dynamics+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- The module defines the 'Dynamics' monad representing a time varying polymorphic function. +--+module Simulation.Aivika.Internal.Dynamics+ (-- * Dynamics+ Dynamics(..),+ DynamicsLift(..),+ invokeDynamics,+ runDynamicsInStartTime,+ runDynamicsInStopTime,+ runDynamicsInIntegTimes,+ runDynamicsInTime,+ runDynamicsInTimes,+ -- * Error Handling+ catchDynamics,+ finallyDynamics,+ throwDynamics,+ -- * Simulation Time+ time,+ isTimeInteg,+ integIteration,+ integPhase) where++import Control.Exception+import Control.Monad+import Control.Monad.Trans+import Control.Monad.Fix+import Control.Applicative++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.+newtype Dynamics a = Dynamics (Point -> IO a)++instance Monad Dynamics where+ return = returnD+ m >>= k = bindD m k++returnD :: a -> Dynamics a+{-# INLINE returnD #-}+returnD a = Dynamics (\p -> return a)++bindD :: Dynamics a -> (a -> Dynamics b) -> Dynamics b+{-# INLINE bindD #-}+bindD (Dynamics m) k = + Dynamics $ \p -> + do a <- m p+ let Dynamics m' = k a+ m' p++-- | Run the 'Dynamics' computation in the initial time point.+runDynamicsInStartTime :: Dynamics a -> Simulation a+runDynamicsInStartTime (Dynamics m) =+ Simulation $ m . integStartPoint++-- | Run the 'Dynamics' computation in the final time point.+runDynamicsInStopTime :: Dynamics a -> Simulation a+runDynamicsInStopTime (Dynamics m) =+ Simulation $ m . integStopPoint++-- | Run the 'Dynamics' computation in all integration time points.+runDynamicsInIntegTimes :: Dynamics a -> Simulation [IO a]+runDynamicsInIntegTimes (Dynamics m) =+ Simulation $ return . map m . integPoints++-- | Run the 'Dynamics' computation in the specified time point.+runDynamicsInTime :: Double -> Dynamics a -> Simulation a+runDynamicsInTime t (Dynamics m) =+ Simulation $ \r -> m $ pointAt r t++-- | Run the 'Dynamics' computation in the specified time points.+runDynamicsInTimes :: [Double] -> Dynamics a -> Simulation [IO a]+runDynamicsInTimes ts (Dynamics m) =+ Simulation $ \r -> return $ map (m . pointAt r) ts ++instance Functor Dynamics where+ fmap = liftMD++instance Applicative Dynamics where+ pure = return+ (<*>) = ap++instance Eq (Dynamics a) where+ x == y = error "Can't compare dynamics." ++instance Show (Dynamics a) where+ showsPrec _ x = showString "<< Dynamics >>"++liftMD :: (a -> b) -> Dynamics a -> Dynamics b+{-# INLINE liftMD #-}+liftMD f (Dynamics x) =+ Dynamics $ \p -> do { a <- x p; return $ f a }++liftM2D :: (a -> b -> c) -> Dynamics a -> Dynamics b -> Dynamics c+{-# INLINE liftM2D #-}+liftM2D f (Dynamics x) (Dynamics y) =+ Dynamics $ \p -> do { a <- x p; b <- y p; return $ f a b }++instance (Num a) => Num (Dynamics a) where+ x + y = liftM2D (+) x y+ x - y = liftM2D (-) x y+ x * y = liftM2D (*) x y+ negate = liftMD negate+ abs = liftMD abs+ signum = liftMD signum+ fromInteger i = return $ fromInteger i++instance (Fractional a) => Fractional (Dynamics a) where+ x / y = liftM2D (/) x y+ recip = liftMD recip+ fromRational t = return $ fromRational t++instance (Floating a) => Floating (Dynamics a) where+ pi = return pi+ exp = liftMD exp+ log = liftMD log+ sqrt = liftMD sqrt+ x ** y = liftM2D (**) x y+ sin = liftMD sin+ cos = liftMD cos+ tan = liftMD tan+ asin = liftMD asin+ acos = liftMD acos+ atan = liftMD atan+ sinh = liftMD sinh+ cosh = liftMD cosh+ tanh = liftMD tanh+ asinh = liftMD asinh+ acosh = liftMD acosh+ atanh = liftMD atanh++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 computations.+class DynamicsLift m where+ + -- | Lift the specified 'Dynamics' computation to another computation.+ liftDynamics :: Dynamics a -> m a++instance DynamicsLift Dynamics where+ liftDynamics = id+ +-- | Exception handling within 'Dynamics' computations.+catchDynamics :: Exception e => Dynamics a -> (e -> Dynamics a) -> Dynamics a+catchDynamics (Dynamics m) h =+ Dynamics $ \p -> + catch (m p) $ \e ->+ let Dynamics m' = h e in m' p+ +-- | A computation with finalization part like the 'finally' function.+finallyDynamics :: Dynamics a -> Dynamics b -> Dynamics a+finallyDynamics (Dynamics m) (Dynamics m') =+ Dynamics $ \p ->+ finally (m p) (m' p)++-- | Like the standard 'throw' function.+throwDynamics :: Exception e => e -> Dynamics a+throwDynamics = throw++-- | Invoke the 'Dynamics' computation.+invokeDynamics :: Point -> Dynamics a -> IO a+{-# INLINE invokeDynamics #-}+invokeDynamics p (Dynamics m) = m p++instance MonadFix Dynamics where+ mfix f = + Dynamics $ \p ->+ do { rec { a <- invokeDynamics p (f a) }; return a }++-- | Computation that returns the current simulation time.+time :: Dynamics Double+time = Dynamics $ return . pointTime ++-- | Whether the current time is an integration time.+isTimeInteg :: Dynamics Bool+isTimeInteg = Dynamics $ \p -> return $ pointPhase p >= 0++-- | Return the integration iteration closest to the current simulation time.+integIteration :: Dynamics Int+integIteration = Dynamics $ return . pointIteration++-- | Return the integration phase for the current simulation time.+-- It is @(-1)@ for non-integration time points.+integPhase :: Dynamics Int+integPhase = Dynamics $ return . pointPhase
Simulation/Aivika/Internal/Event.hs view
@@ -1,409 +1,399 @@- -{-# LANGUAGE RecursiveDo #-} - --- | --- Module : Simulation.Aivika.Internal.Event --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- The module defines the 'Event' monad which is very similar to the 'Dynamics' --- monad but only now the computation is strongly synchronized with the event queue. --- -module Simulation.Aivika.Internal.Event - (-- * Event Monad - Event(..), - EventLift(..), - EventProcessing(..), - invokeEvent, - runEvent, - runEventWith, - runEventInStartTime, - runEventInStopTime, - -- * Event Queue - enqueueEvent, - enqueueEventWithCancellation, - enqueueEventWithTimes, - enqueueEventWithPoints, - enqueueEventWithIntegTimes, - yieldEvent, - eventQueueCount, - -- * Cancelling Event - EventCancellation, - cancelEvent, - eventCancelled, - eventFinished, - -- * Error Handling - catchEvent, - finallyEvent, - throwEvent, - -- * Memoization - memoEvent, - memoEventInTime, - -- * Disposable - DisposableEvent(..)) where - -import Data.IORef -import Data.Monoid - -import Control.Exception -import Control.Monad -import Control.Monad.Trans -import Control.Monad.Fix -import Control.Applicative - -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 - --- | A value in the 'Event' monad represents a polymorphic time varying function --- which is strongly synchronized with the event queue. -newtype Event a = Event (Point -> IO a) - -instance Monad Event where - return = returnE - m >>= k = bindE m k - -returnE :: a -> Event a -{-# INLINE returnE #-} -returnE a = Event (\p -> return a) - -bindE :: Event a -> (a -> Event b) -> Event b -{-# INLINE bindE #-} -bindE (Event m) k = - Event $ \p -> - do a <- m p - let Event m' = k a - m' p - -instance Functor Event where - fmap = liftME - -instance Applicative Event where - pure = return - (<*>) = ap - -liftME :: (a -> b) -> Event a -> Event b -{-# INLINE liftME #-} -liftME f (Event x) = - Event $ \p -> do { a <- x p; return $ f a } - -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) = - Event $ \p -> m $ pointRun p - -liftDS :: Dynamics a -> Event a -{-# INLINE liftDS #-} -liftDS (Dynamics m) = - Event m - --- | A type class to lift the 'Event' computation to other computations. -class EventLift m where - - -- | Lift the specified 'Event' computation to another computation. - liftEvent :: Event a -> m a - -instance EventLift Event where - liftEvent = id - --- | Exception handling within 'Event' computations. -catchEvent :: Exception e => Event a -> (e -> Event a) -> Event a -catchEvent (Event m) h = - Event $ \p -> - catch (m p) $ \e -> - let Event m' = h e in m' p - --- | A computation with finalization part like the 'finally' function. -finallyEvent :: Event a -> Event b -> Event a -finallyEvent (Event m) (Event m') = - Event $ \p -> - finally (m p) (m' p) - --- | Like the standard 'throw' function. -throwEvent :: Exception e => e -> Event a -throwEvent = throw - --- | Invoke the 'Event' computation. -invokeEvent :: Point -> Event a -> IO a -{-# INLINE invokeEvent #-} -invokeEvent p (Event m) = m p - -instance MonadFix Event where - mfix f = - Event $ \p -> - do { rec { a <- invokeEvent p (f a) }; return a } - --- | Defines how the events are processed. -data EventProcessing = CurrentEvents - -- ^ either process all earlier and then current events, - -- or raise an error if the current simulation time is less - -- than the actual time of the event queue (safe within - -- the 'Event' computation as this is protected by the type system) - | EarlierEvents - -- ^ either process all earlier events not affecting - -- the events at the current simulation time, - -- or raise an error if the current simulation time is less - -- than the actual time of the event queue (safe within - -- the 'Event' computation as this is protected by the type system) - | CurrentEventsOrFromPast - -- ^ either process all earlier and then current events, - -- or do nothing if the current simulation time is less - -- than the actual time of the event queue - -- (do not use unless the documentation states the opposite) - | EarlierEventsOrFromPast - -- ^ either process all earlier events, - -- or do nothing if the current simulation time is less - -- than the actual time of the event queue - -- (do not use unless the documentation states the opposite) - deriving (Eq, Ord, Show) - --- | Enqueue the event which must be actuated at the specified time. --- --- The events are processed when calling the 'runEvent' function. So, --- if you want to insist on their immediate execution then you can apply --- something like --- --- @ --- liftDynamics $ runEvent IncludingCurrentEvents $ return () --- @ --- --- although this is generally not good idea. -enqueueEvent :: Double -> Event () -> Event () -enqueueEvent t (Event m) = - Event $ \p -> - let pq = queuePQ $ runEventQueue $ pointRun p - in PQ.enqueue pq t m - --- | Process the pending events. -processPendingEventsCore :: Bool -> Dynamics () -processPendingEventsCore includingCurrentEvents = Dynamics r where - r p = - do let q = runEventQueue $ pointRun p - f = queueBusy q - f' <- readIORef f - unless f' $ - do writeIORef f True - call q p - writeIORef f False - call q p = - do let pq = queuePQ q - r = pointRun p - f <- PQ.queueNull pq - unless f $ - do (t2, c2) <- PQ.queueFront pq - let t = queueTime q - t' <- readIORef t - when (t2 < t') $ - error "The time value is too small: processPendingEventsCore" - when ((t2 < pointTime p) || - (includingCurrentEvents && (t2 == pointTime p))) $ - do writeIORef t t2 - PQ.dequeue pq - let sc = pointSpecs p - t0 = spcStartTime sc - dt = spcDT sc - n2 = fromIntegral $ floor ((t2 - t0) / dt) - c2 $ p { pointTime = t2, - pointIteration = n2, - pointPhase = -1 } - call q p - --- | Process the pending events synchronously, i.e. without past. -processPendingEvents :: Bool -> Dynamics () -processPendingEvents includingCurrentEvents = Dynamics r where - r p = - do let q = runEventQueue $ pointRun p - t = queueTime q - t' <- readIORef t - if pointTime p < t' - then error $ - "The current time is less than " ++ - "the time in the queue: processPendingEvents" - else invokeDynamics p m - m = processPendingEventsCore includingCurrentEvents - --- | A memoized value. -processEventsIncludingCurrent = processPendingEvents True - --- | A memoized value. -processEventsIncludingEarlier = processPendingEvents False - --- | A memoized value. -processEventsIncludingCurrentCore = processPendingEventsCore True - --- | A memoized value. -processEventsIncludingEarlierCore = processPendingEventsCore True - --- | Process the events. -processEvents :: EventProcessing -> Dynamics () -processEvents CurrentEvents = processEventsIncludingCurrent -processEvents EarlierEvents = processEventsIncludingEarlier -processEvents CurrentEventsOrFromPast = processEventsIncludingCurrentCore -processEvents EarlierEventsOrFromPast = processEventsIncludingEarlierCore - --- | Run the 'Event' computation in the current simulation time --- within the 'Dynamics' computation involving all pending --- 'CurrentEvents' in the processing too. -runEvent :: Event a -> Dynamics a -runEvent = runEventWith CurrentEvents - --- | Run the 'Event' computation in the current simulation time --- within the 'Dynamics' computation specifying what pending events --- should be involved in the processing. -runEventWith :: EventProcessing -> Event a -> Dynamics a -runEventWith processing (Event e) = - Dynamics $ \p -> - do invokeDynamics p $ processEvents processing - e p - --- | Run the 'Event' computation in the start time involving all --- pending 'CurrentEvents' in the processing too. -runEventInStartTime :: Event a -> Simulation a -runEventInStartTime = runDynamicsInStartTime . runEvent - --- | Run the 'Event' computation in the stop time involving all --- pending 'CurrentEvents' in the processing too. -runEventInStopTime :: Event a -> Simulation a -runEventInStopTime = runDynamicsInStopTime . runEvent - --- | Return the number of pending events that should --- be yet actuated. -eventQueueCount :: Event Int -eventQueueCount = - Event $ PQ.queueCount . queuePQ . runEventQueue . pointRun - --- | Actuate the event handler in the specified time points. -enqueueEventWithTimes :: [Double] -> Event () -> Event () -enqueueEventWithTimes ts e = loop ts - where loop [] = return () - loop (t : ts) = enqueueEvent t $ e >> loop ts - --- | Actuate the event handler in the specified time points. -enqueueEventWithPoints :: [Point] -> Event () -> Event () -enqueueEventWithPoints xs (Event e) = loop xs - where loop [] = return () - loop (x : xs) = enqueueEvent (pointTime x) $ - Event $ \p -> - do e x -- N.B. we substitute the time point! - invokeEvent p $ loop xs - --- | Actuate the event handler in the integration time points. -enqueueEventWithIntegTimes :: Event () -> Event () -enqueueEventWithIntegTimes e = - Event $ \p -> - let points = integPoints $ pointRun p - in invokeEvent p $ enqueueEventWithPoints points e - --- | It allows cancelling the event. -data EventCancellation = - EventCancellation { cancelEvent :: Event (), - -- ^ Cancel the event. - eventCancelled :: Event Bool, - -- ^ Test whether the event was cancelled. - eventFinished :: Event Bool - -- ^ Test whether the event was processed and finished. - } - --- | Enqueue the event with an ability to cancel it. -enqueueEventWithCancellation :: Double -> Event () -> Event EventCancellation -enqueueEventWithCancellation t e = - Event $ \p -> - do cancelledRef <- newIORef False - cancellableRef <- newIORef True - finishedRef <- newIORef False - let cancel = - Event $ \p -> - do x <- readIORef cancellableRef - when x $ - 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 cancelledRef - unless x $ - do invokeEvent p e - writeIORef finishedRef True - return EventCancellation { cancelEvent = cancel, - 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 - --- | Memoize the 'Event' computation, always returning the same value --- in the same modeling time. After the time changes, the value is --- recalculated by demand. --- --- It is possible to implement this function efficiently, for the 'Event' --- computation is always synchronized with the event queue which time --- flows in one direction only. This synchronization is a key difference --- between the 'Event' and 'Dynamics' computations. -memoEventInTime :: Event a -> Simulation (Event a) -memoEventInTime m = - do ref <- liftIO $ newIORef Nothing - return $ Event $ \p -> - do x <- readIORef ref - case x of - Just (t, v) | t == pointTime p -> - return v - _ -> - do v <- invokeEvent p m - writeIORef ref (Just (pointTime p, v)) - return v - --- | Enqueue the event which must be actuated with the current modeling time but later. -yieldEvent :: Event () -> Event () -yieldEvent m = - Event $ \p -> - invokeEvent p $ - enqueueEvent (pointTime p) m - --- | Defines a computation disposing some entity. -newtype DisposableEvent = - DisposableEvent { disposeEvent :: Event () - -- ^ Dispose something within the 'Event' computation. - } - -instance Monoid DisposableEvent where - - mempty = DisposableEvent $ return () - mappend (DisposableEvent x) (DisposableEvent y) = DisposableEvent $ x >> y ++{-# LANGUAGE RecursiveDo #-}++-- |+-- Module : Simulation.Aivika.Internal.Event+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- The module defines the 'Event' monad which is very similar to the 'Dynamics'+-- monad but only now the computation is strongly synchronized with the event queue.+--+module Simulation.Aivika.Internal.Event+ (-- * Event Monad+ Event(..),+ EventLift(..),+ EventProcessing(..),+ invokeEvent,+ runEvent,+ runEventWith,+ runEventInStartTime,+ runEventInStopTime,+ -- * Event Queue+ enqueueEvent,+ enqueueEventWithCancellation,+ enqueueEventWithTimes,+ enqueueEventWithPoints,+ enqueueEventWithIntegTimes,+ yieldEvent,+ eventQueueCount,+ -- * Cancelling Event+ EventCancellation,+ cancelEvent,+ eventCancelled,+ eventFinished,+ -- * Error Handling+ catchEvent,+ finallyEvent,+ throwEvent,+ -- * Memoization+ memoEvent,+ memoEventInTime,+ -- * Disposable+ DisposableEvent(..)) where++import Data.IORef+import Data.Monoid++import Control.Exception+import Control.Monad+import Control.Monad.Trans+import Control.Monad.Fix+import Control.Applicative++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++-- | A value in the 'Event' monad represents a polymorphic time varying function+-- which is strongly synchronized with the event queue.+newtype Event a = Event (Point -> IO a)++instance Monad Event where+ return = returnE+ m >>= k = bindE m k++returnE :: a -> Event a+{-# INLINE returnE #-}+returnE a = Event (\p -> return a)++bindE :: Event a -> (a -> Event b) -> Event b+{-# INLINE bindE #-}+bindE (Event m) k = + Event $ \p -> + do a <- m p+ let Event m' = k a+ m' p++instance Functor Event where+ fmap = liftME++instance Applicative Event where+ pure = return+ (<*>) = ap++liftME :: (a -> b) -> Event a -> Event b+{-# INLINE liftME #-}+liftME f (Event x) =+ Event $ \p -> do { a <- x p; return $ f a }++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) =+ Event $ \p -> m $ pointRun p++liftDS :: Dynamics a -> Event a+{-# INLINE liftDS #-}+liftDS (Dynamics m) =+ Event m++-- | A type class to lift the 'Event' computation to other computations.+class EventLift m where+ + -- | Lift the specified 'Event' computation to another computation.+ liftEvent :: Event a -> m a++instance EventLift Event where+ liftEvent = id+ +-- | Exception handling within 'Event' computations.+catchEvent :: Exception e => Event a -> (e -> Event a) -> Event a+catchEvent (Event m) h =+ Event $ \p -> + catch (m p) $ \e ->+ let Event m' = h e in m' p+ +-- | A computation with finalization part like the 'finally' function.+finallyEvent :: Event a -> Event b -> Event a+finallyEvent (Event m) (Event m') =+ Event $ \p ->+ finally (m p) (m' p)++-- | Like the standard 'throw' function.+throwEvent :: Exception e => e -> Event a+throwEvent = throw++-- | Invoke the 'Event' computation.+invokeEvent :: Point -> Event a -> IO a+{-# INLINE invokeEvent #-}+invokeEvent p (Event m) = m p++instance MonadFix Event where+ mfix f = + Event $ \p ->+ do { rec { a <- invokeEvent p (f a) }; return a }++-- | Defines how the events are processed.+data EventProcessing = CurrentEvents+ -- ^ either process all earlier and then current events,+ -- or raise an error if the current simulation time is less+ -- than the actual time of the event queue (safe within+ -- the 'Event' computation as this is protected by the type system)+ | EarlierEvents+ -- ^ either process all earlier events not affecting+ -- the events at the current simulation time,+ -- or raise an error if the current simulation time is less+ -- than the actual time of the event queue (safe within+ -- the 'Event' computation as this is protected by the type system)+ | CurrentEventsOrFromPast+ -- ^ either process all earlier and then current events,+ -- or do nothing if the current simulation time is less+ -- than the actual time of the event queue+ -- (do not use unless the documentation states the opposite)+ | EarlierEventsOrFromPast+ -- ^ either process all earlier events,+ -- or do nothing if the current simulation time is less+ -- than the actual time of the event queue+ -- (do not use unless the documentation states the opposite)+ deriving (Eq, Ord, Show)++-- | Enqueue the event which must be actuated at the specified time.+enqueueEvent :: Double -> Event () -> Event ()+enqueueEvent t (Event m) =+ Event $ \p ->+ let pq = queuePQ $ runEventQueue $ pointRun p+ in PQ.enqueue pq t m++-- | Process the pending events.+processPendingEventsCore :: Bool -> Dynamics ()+processPendingEventsCore includingCurrentEvents = Dynamics r where+ r p =+ do let q = runEventQueue $ pointRun p+ f = queueBusy q+ f' <- readIORef f+ unless f' $+ do writeIORef f True+ call q p+ writeIORef f False+ call q p =+ do let pq = queuePQ q+ r = pointRun p+ f <- PQ.queueNull pq+ unless f $+ do (t2, c2) <- PQ.queueFront pq+ let t = queueTime q+ t' <- readIORef t+ when (t2 < t') $ + error "The time value is too small: processPendingEventsCore"+ when ((t2 < pointTime p) ||+ (includingCurrentEvents && (t2 == pointTime p))) $+ do writeIORef t t2+ PQ.dequeue pq+ let sc = pointSpecs p+ t0 = spcStartTime sc+ dt = spcDT sc+ n2 = fromIntegral $ floor ((t2 - t0) / dt)+ c2 $ p { pointTime = t2,+ pointIteration = n2,+ pointPhase = -1 }+ call q p++-- | Process the pending events synchronously, i.e. without past.+processPendingEvents :: Bool -> Dynamics ()+processPendingEvents includingCurrentEvents = Dynamics r where+ r p =+ do let q = runEventQueue $ pointRun p+ t = queueTime q+ t' <- readIORef t+ if pointTime p < t'+ then error $+ "The current time is less than " +++ "the time in the queue: processPendingEvents"+ else invokeDynamics p m+ m = processPendingEventsCore includingCurrentEvents++-- | A memoized value.+processEventsIncludingCurrent = processPendingEvents True++-- | A memoized value.+processEventsIncludingEarlier = processPendingEvents False++-- | A memoized value.+processEventsIncludingCurrentCore = processPendingEventsCore True++-- | A memoized value.+processEventsIncludingEarlierCore = processPendingEventsCore True++-- | Process the events.+processEvents :: EventProcessing -> Dynamics ()+processEvents CurrentEvents = processEventsIncludingCurrent+processEvents EarlierEvents = processEventsIncludingEarlier+processEvents CurrentEventsOrFromPast = processEventsIncludingCurrentCore+processEvents EarlierEventsOrFromPast = processEventsIncludingEarlierCore++-- | Run the 'Event' computation in the current simulation time+-- within the 'Dynamics' computation involving all pending+-- 'CurrentEvents' in the processing too.+runEvent :: Event a -> Dynamics a+runEvent = runEventWith CurrentEvents++-- | Run the 'Event' computation in the current simulation time+-- within the 'Dynamics' computation specifying what pending events +-- should be involved in the processing.+runEventWith :: EventProcessing -> Event a -> Dynamics a+runEventWith processing (Event e) =+ Dynamics $ \p ->+ do invokeDynamics p $ processEvents processing+ e p++-- | Run the 'Event' computation in the start time involving all+-- pending 'CurrentEvents' in the processing too.+runEventInStartTime :: Event a -> Simulation a+runEventInStartTime = runDynamicsInStartTime . runEvent++-- | Run the 'Event' computation in the stop time involving all+-- pending 'CurrentEvents' in the processing too.+runEventInStopTime :: Event a -> Simulation a+runEventInStopTime = runDynamicsInStopTime . runEvent++-- | Return the number of pending events that should+-- be yet actuated.+eventQueueCount :: Event Int+eventQueueCount =+ Event $ PQ.queueCount . queuePQ . runEventQueue . pointRun++-- | Actuate the event handler in the specified time points.+enqueueEventWithTimes :: [Double] -> Event () -> Event ()+enqueueEventWithTimes ts e = loop ts+ where loop [] = return ()+ loop (t : ts) = enqueueEvent t $ e >> loop ts+ +-- | Actuate the event handler in the specified time points.+enqueueEventWithPoints :: [Point] -> Event () -> Event ()+enqueueEventWithPoints xs (Event e) = loop xs+ where loop [] = return ()+ loop (x : xs) = enqueueEvent (pointTime x) $ + Event $ \p ->+ do e x -- N.B. we substitute the time point!+ invokeEvent p $ loop xs+ +-- | Actuate the event handler in the integration time points.+enqueueEventWithIntegTimes :: Event () -> Event ()+enqueueEventWithIntegTimes e =+ Event $ \p ->+ let points = integPoints $ pointRun p+ in invokeEvent p $ enqueueEventWithPoints points e++-- | It allows cancelling the event.+data EventCancellation =+ EventCancellation { cancelEvent :: Event (),+ -- ^ Cancel the event.+ eventCancelled :: Event Bool,+ -- ^ Test whether the event was cancelled.+ eventFinished :: Event Bool+ -- ^ Test whether the event was processed and finished.+ }++-- | Enqueue the event with an ability to cancel it.+enqueueEventWithCancellation :: Double -> Event () -> Event EventCancellation+enqueueEventWithCancellation t e =+ Event $ \p ->+ do cancelledRef <- newIORef False+ cancellableRef <- newIORef True+ finishedRef <- newIORef False+ let cancel =+ Event $ \p ->+ do x <- readIORef cancellableRef+ when x $+ 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 cancelledRef+ unless x $+ do invokeEvent p e+ writeIORef finishedRef True+ return EventCancellation { cancelEvent = cancel,+ 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++-- | Memoize the 'Event' computation, always returning the same value+-- in the same modeling time. After the time changes, the value is+-- recalculated by demand.+--+-- It is possible to implement this function efficiently, for the 'Event'+-- computation is always synchronized with the event queue which time+-- flows in one direction only. This synchronization is a key difference+-- between the 'Event' and 'Dynamics' computations.+memoEventInTime :: Event a -> Simulation (Event a)+memoEventInTime m =+ do ref <- liftIO $ newIORef Nothing+ return $ Event $ \p ->+ do x <- readIORef ref+ case x of+ Just (t, v) | t == pointTime p ->+ return v+ _ ->+ do v <- invokeEvent p m+ writeIORef ref (Just (pointTime p, v))+ return v++-- | Enqueue the event which must be actuated with the current modeling time but later.+yieldEvent :: Event () -> Event ()+yieldEvent m =+ Event $ \p ->+ invokeEvent p $+ enqueueEvent (pointTime p) m++-- | Defines a computation disposing some entity.+newtype DisposableEvent =+ DisposableEvent { disposeEvent :: Event ()+ -- ^ Dispose something within the 'Event' computation.+ }++instance Monoid DisposableEvent where++ mempty = DisposableEvent $ return ()+ mappend (DisposableEvent x) (DisposableEvent y) = DisposableEvent $ x >> y
Simulation/Aivika/Internal/Parameter.hs view
@@ -1,261 +1,261 @@- -{-# LANGUAGE RecursiveDo #-} - --- | --- Module : Simulation.Aivika.Internal.Parameter --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.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. --- --- In general, this monad is very useful for representing a computation which is external --- relative to the model itself. --- -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 Control.Exception -import Control.Concurrent.MVar - -import Control.Monad -import Control.Monad.Trans -import Control.Monad.Fix -import Control.Applicative - -import Data.IORef -import qualified Data.IntMap 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. --- --- In general, this monad is very useful for representing a computation which is external --- relative to the model itself. -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 Applicative Parameter where - pure = return - (<*>) = ap - -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 :: Exception e => Parameter a -> (e -> Parameter a) -> Parameter a -catchParameter (Parameter m) h = - Parameter $ \r -> - 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 -> - finally (m r) (m' r) - --- | Like the standard 'throw' function. -throwParameter :: Exception e => e -> 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 ++{-# LANGUAGE RecursiveDo #-}++-- |+-- Module : Simulation.Aivika.Internal.Parameter+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.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.+-- +-- In general, this monad is very useful for representing a computation which is external+-- relative to the model itself.+--+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 Control.Exception+import Control.Concurrent.MVar++import Control.Monad+import Control.Monad.Trans+import Control.Monad.Fix+import Control.Applicative++import Data.IORef+import qualified Data.IntMap 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.+-- +-- In general, this monad is very useful for representing a computation which is external+-- relative to the model itself.+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 Applicative Parameter where+ pure = return+ (<*>) = ap++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 :: Exception e => Parameter a -> (e -> Parameter a) -> Parameter a+catchParameter (Parameter m) h =+ Parameter $ \r -> + 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 ->+ finally (m r) (m' r)++-- | Like the standard 'throw' function.+throwParameter :: Exception e => e -> 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
@@ -1,630 +1,643 @@- --- | --- Module : Simulation.Aivika.Internal.Process --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- A value in the 'Process' monad represents a discontinuous process that --- can suspend in any simulation time point and then resume later in the same --- or another time point. --- --- The process of this type can involve the 'Event', 'Dynamics' and 'Simulation' --- computations. Moreover, a value in the @Process@ monad can be run within --- the @Event@ computation. --- --- A value of the 'ProcessId' type is just an identifier of such a process. --- -module Simulation.Aivika.Internal.Process - (-- * Process Monad - ProcessId, - Process(..), - ProcessLift(..), - invokeProcess, - -- * Running Process - runProcess, - runProcessUsingId, - runProcessInStartTime, - runProcessInStartTimeUsingId, - runProcessInStopTime, - runProcessInStopTimeUsingId, - -- * Spawning Processes - spawnProcess, - spawnProcessUsingId, - -- * Enqueuing Process - enqueueProcess, - enqueueProcessUsingId, - -- * Creating Process Identifier - newProcessId, - processId, - processUsingId, - -- * Holding, Interrupting, Passivating and Canceling Process - holdProcess, - interruptProcess, - processInterrupted, - passivateProcess, - processPassive, - reactivateProcess, - cancelProcessWithId, - cancelProcess, - processCancelled, - processCancelling, - whenCancellingProcess, - -- * Awaiting Signal - processAwait, - -- * Yield of Process - processYield, - -- * Process Timeout - timeoutProcess, - timeoutProcessUsingId, - -- * Parallelizing Processes - processParallel, - processParallelUsingIds, - processParallel_, - processParallelUsingIds_, - -- * Exception Handling - catchProcess, - finallyProcess, - throwProcess, - -- * Utilities - zipProcessParallel, - zip3ProcessParallel, - unzipProcess, - -- * Memoizing Process - memoProcess, - -- * Never Ending Process - neverProcess) where - -import Data.Maybe -import Data.IORef - -import Control.Exception -import Control.Monad -import Control.Monad.Trans -import Control.Applicative - -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, - processReactCont :: IORef (Maybe (ContParams ())), - processCancelSource :: ContCancellationSource, - processInterruptRef :: IORef Bool, - processInterruptCont :: IORef (Maybe (ContParams ())), - processInterruptVersion :: IORef Int } - --- | Specifies a discontinuous process that can suspend at any time --- 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 #-} -invokeProcess pid (Process m) = m pid - --- | Hold the process for the specified time period. -holdProcess :: Double -> Process () -holdProcess dt = - Process $ \pid -> - Cont $ \c -> - Event $ \p -> - do let x = processInterruptCont pid - writeIORef x $ Just c - writeIORef (processInterruptRef pid) False - v <- readIORef (processInterruptVersion pid) - invokeEvent p $ - enqueueEvent (pointTime p + dt) $ - Event $ \p -> - do v' <- readIORef (processInterruptVersion pid) - when (v == v') $ - do writeIORef x Nothing - invokeEvent p $ resumeCont c () - --- | Interrupt a process with the specified identifier if the process --- is held by computation 'holdProcess'. -interruptProcess :: ProcessId -> Event () -interruptProcess pid = - Event $ \p -> - do let x = processInterruptCont pid - a <- readIORef x - case a of - Nothing -> return () - Just c -> - do writeIORef x Nothing - writeIORef (processInterruptRef pid) True - modifyIORef (processInterruptVersion pid) $ (+) 1 - invokeEvent p $ enqueueEvent (pointTime p) $ resumeCont c () - --- | Test whether the process with the specified identifier was interrupted. -processInterrupted :: ProcessId -> Event Bool -processInterrupted pid = - Event $ \p -> - readIORef (processInterruptRef pid) - --- | Passivate the process. -passivateProcess :: Process () -passivateProcess = - Process $ \pid -> - Cont $ \c -> - Event $ \p -> - do let x = processReactCont pid - a <- readIORef x - case a of - Nothing -> writeIORef x $ Just c - Just _ -> error "Cannot passivate the process twice: passivateProcess" - --- | Test whether the process with the specified identifier is passivated. -processPassive :: ProcessId -> Event Bool -processPassive pid = - Event $ \p -> - do let x = processReactCont pid - a <- readIORef x - return $ isJust a - --- | Reactivate a process with the specified identifier. -reactivateProcess :: ProcessId -> Event () -reactivateProcess pid = - Event $ \p -> - do let x = processReactCont pid - a <- readIORef x - case a of - Nothing -> - return () - Just c -> - do writeIORef x Nothing - invokeEvent p $ enqueueEvent (pointTime p) $ resumeCont c () - --- | 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 = processCancelling 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 :: 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 - --- | Run the process in the start time immediately involving all pending --- 'CurrentEvents' in the computation too. -runProcessInStartTime :: Process () -> Simulation () -runProcessInStartTime = runEventInStartTime . runProcess - --- | Run the process in the start time immediately using the specified identifier --- and involving all pending 'CurrentEvents' in the computation too. -runProcessInStartTimeUsingId :: ProcessId -> Process () -> Simulation () -runProcessInStartTimeUsingId pid p = - runEventInStartTime $ runProcessUsingId pid p - --- | Run the process in the final simulation time immediately involving all --- pending 'CurrentEvents' in the computation too. -runProcessInStopTime :: Process () -> Simulation () -runProcessInStopTime = runEventInStopTime . runProcess - --- | Run the process in the final simulation time immediately using --- the specified identifier and involving all pending 'CurrentEvents' --- in the computation too. -runProcessInStopTimeUsingId :: ProcessId -> Process () -> Simulation () -runProcessInStopTimeUsingId pid p = - runEventInStopTime $ runProcessUsingId pid p - --- | Enqueue the process that will be then started at the specified time --- from the event queue. -enqueueProcess :: Double -> Process () -> Event () -enqueueProcess t p = - enqueueEvent t $ runProcess p - --- | Enqueue the process that will be then started at the specified time --- from the event queue. -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. -newProcessId :: Simulation ProcessId -newProcessId = - do x <- liftIO $ newIORef Nothing - y <- liftIO $ newIORef False - c <- newContCancellationSource - i <- liftIO $ newIORef False - z <- liftIO $ newIORef Nothing - v <- liftIO $ newIORef 0 - return ProcessId { processStarted = y, - processReactCont = x, - processCancelSource = c, - processInterruptRef = i, - processInterruptCont = z, - processInterruptVersion = v } - --- | Cancel a process with the specified identifier, interrupting it if needed. -cancelProcessWithId :: ProcessId -> Event () -cancelProcessWithId pid = contCancellationInitiate (processCancelSource pid) - --- | The process cancels itself. -cancelProcess :: Process a -cancelProcess = - do pid <- processId - liftEvent $ cancelProcessWithId pid - throwProcess $ - (error "The process must be cancelled already: cancelProcess." :: SomeException) - --- | Test whether the process with the specified identifier was cancelled. -processCancelled :: ProcessId -> Event Bool -processCancelled pid = contCancellationInitiated (processCancelSource pid) - --- | Return a signal that notifies about cancelling the process with --- the specified identifier. -processCancelling :: ProcessId -> Signal () -processCancelling pid = contCancellationInitiating (processCancelSource pid) - --- | Register a handler that will be invoked in case of cancelling the current process. -whenCancellingProcess :: Event () -> Process () -whenCancellingProcess h = - Process $ \pid -> - liftEvent $ - handleSignal_ (processCancelling pid) $ \() -> h - -instance Eq ProcessId where - x == y = processReactCont x == processReactCont y -- for the references are unique - -instance Monad Process where - return = returnP - m >>= k = bindP m k - -instance Functor Process where - fmap = liftM - -instance Applicative Process where - pure = return - (<*>) = ap - -instance ParameterLift Process where - liftParameter = liftPP - -instance SimulationLift Process where - liftSimulation = liftSP - -instance DynamicsLift Process where - liftDynamics = liftDP - -instance EventLift Process where - liftEvent = liftEP - -instance MonadIO Process where - liftIO = liftIOP - -returnP :: a -> Process a -{-# INLINE returnP #-} -returnP a = Process $ \pid -> return a - -bindP :: Process a -> (a -> Process b) -> Process b -{-# INLINE bindP #-} -bindP (Process m) k = - Process $ \pid -> - do a <- m pid - 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 - -liftDP :: Dynamics a -> Process a -{-# INLINE liftDP #-} -liftDP m = Process $ \pid -> liftDynamics m - -liftEP :: Event a -> Process a -{-# INLINE liftEP #-} -liftEP m = Process $ \pid -> liftEvent m - -liftIOP :: IO a -> Process a -{-# INLINE liftIOP #-} -liftIOP m = Process $ \pid -> liftIO m - --- | Exception handling within 'Process' computations. -catchProcess :: Exception e => Process a -> (e -> Process a) -> Process a -catchProcess (Process m) h = - Process $ \pid -> - catchCont (m pid) $ \e -> - let Process m' = h e in m' pid - --- | A computation with finalization part. -finallyProcess :: Process a -> Process b -> Process a -finallyProcess (Process m) (Process m') = - Process $ \pid -> - finallyCont (m pid) (m' pid) - --- | Throw the exception with the further exception handling. --- --- By some reason, an exception raised with help of the standard 'throw' function --- is not handled properly within 'Process' computation, altough it will be still handled --- if it will be wrapped in the 'IO' monad. Therefore, you should use specialised --- functions like the stated one that use the 'throw' function but within the 'IO' computation, --- which allows already handling the exception. -throwProcess :: Exception e => e -> 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 propagated --- 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 $ - cancelProcessWithId 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 (SomeException e)) -> throwProcess e - --- | Yield to allow other 'Process' and 'Event' computations to run --- at the current simulation time point. -processYield :: Process () -processYield = - Process $ \pid -> - Cont $ \c -> - Event $ \p -> - invokeEvent p $ - enqueueEvent (pointTime p) $ - resumeCont c () - --- | A computation that never computes the result. It behaves like a black hole for --- the discontinuous process, although such a process can still be canceled outside --- (see 'cancelProcessWithId'), but then only its finalization parts (see 'finallyProcess') --- will be called, usually, to release the resources acquired before. -neverProcess :: Process a -neverProcess = - Process $ \pid -> - Cont $ \c -> - let signal = processCancelling pid - in handleSignal_ signal $ \_ -> - resumeCont c $ error "It must never be computed: neverProcess" ++-- |+-- Module : Simulation.Aivika.Internal.Process+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- A value in the 'Process' monad represents a discontinuous process that +-- can suspend in any simulation time point and then resume later in the same +-- or another time point. +-- +-- The process of this type can involve the 'Event', 'Dynamics' and 'Simulation'+-- computations. Moreover, a value in the @Process@ monad can be run within+-- the @Event@ computation.+--+-- A value of the 'ProcessId' type is just an identifier of such a process.+--+module Simulation.Aivika.Internal.Process+ (-- * Process Monad+ ProcessId,+ Process(..),+ ProcessLift(..),+ invokeProcess,+ -- * Running Process+ runProcess,+ runProcessUsingId,+ runProcessInStartTime,+ runProcessInStartTimeUsingId,+ runProcessInStopTime,+ runProcessInStopTimeUsingId,+ -- * Spawning Processes+ spawnProcess,+ spawnProcessUsingId,+ spawnProcessWith,+ spawnProcessUsingIdWith,+ -- * Enqueuing Process+ enqueueProcess,+ enqueueProcessUsingId,+ -- * Creating Process Identifier+ newProcessId,+ processId,+ processUsingId,+ -- * Holding, Interrupting, Passivating and Canceling Process+ holdProcess,+ interruptProcess,+ processInterrupted,+ passivateProcess,+ processPassive,+ reactivateProcess,+ cancelProcessWithId,+ cancelProcess,+ processCancelled,+ processCancelling,+ whenCancellingProcess,+ -- * Awaiting Signal+ processAwait,+ -- * Yield of Process+ processYield,+ -- * Process Timeout+ timeoutProcess,+ timeoutProcessUsingId,+ -- * Parallelizing Processes+ processParallel,+ processParallelUsingIds,+ processParallel_,+ processParallelUsingIds_,+ -- * Exception Handling+ catchProcess,+ finallyProcess,+ throwProcess,+ -- * Utilities+ zipProcessParallel,+ zip3ProcessParallel,+ unzipProcess,+ -- * Memoizing Process+ memoProcess,+ -- * Never Ending Process+ neverProcess) where++import Data.Maybe+import Data.IORef++import Control.Exception+import Control.Monad+import Control.Monad.Trans+import Control.Applicative++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,+ processReactCont :: IORef (Maybe (ContParams ())), + processCancelSource :: ContCancellationSource,+ processInterruptRef :: IORef Bool, + processInterruptCont :: IORef (Maybe (ContParams ())), + processInterruptVersion :: IORef Int }++-- | Specifies a discontinuous process that can suspend at any time+-- 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 #-}+invokeProcess pid (Process m) = m pid++-- | Hold the process for the specified time period.+holdProcess :: Double -> Process ()+holdProcess dt =+ Process $ \pid ->+ Cont $ \c ->+ Event $ \p ->+ do let x = processInterruptCont pid+ writeIORef x $ Just c+ writeIORef (processInterruptRef pid) False+ v <- readIORef (processInterruptVersion pid)+ invokeEvent p $+ enqueueEvent (pointTime p + dt) $+ Event $ \p ->+ do v' <- readIORef (processInterruptVersion pid)+ when (v == v') $ + do writeIORef x Nothing+ invokeEvent p $ resumeCont c ()++-- | Interrupt a process with the specified identifier if the process+-- is held by computation 'holdProcess'.+interruptProcess :: ProcessId -> Event ()+interruptProcess pid =+ Event $ \p ->+ do let x = processInterruptCont pid+ a <- readIORef x+ case a of+ Nothing -> return ()+ Just c ->+ do writeIORef x Nothing+ writeIORef (processInterruptRef pid) True+ modifyIORef (processInterruptVersion pid) $ (+) 1+ invokeEvent p $ enqueueEvent (pointTime p) $ resumeCont c ()+ +-- | Test whether the process with the specified identifier was interrupted.+processInterrupted :: ProcessId -> Event Bool+processInterrupted pid =+ Event $ \p ->+ readIORef (processInterruptRef pid)++-- | Passivate the process.+passivateProcess :: Process ()+passivateProcess =+ Process $ \pid ->+ Cont $ \c ->+ Event $ \p ->+ do let x = processReactCont pid+ a <- readIORef x+ case a of+ Nothing -> writeIORef x $ Just c+ Just _ -> error "Cannot passivate the process twice: passivateProcess"++-- | Test whether the process with the specified identifier is passivated.+processPassive :: ProcessId -> Event Bool+processPassive pid =+ Event $ \p ->+ do let x = processReactCont pid+ a <- readIORef x+ return $ isJust a++-- | Reactivate a process with the specified identifier.+reactivateProcess :: ProcessId -> Event ()+reactivateProcess pid =+ Event $ \p ->+ do let x = processReactCont pid+ a <- readIORef x+ case a of+ Nothing -> + return ()+ Just c ->+ do writeIORef x Nothing+ invokeEvent p $ enqueueEvent (pointTime p) $ resumeCont c ()++-- | 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 = processCancelling 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 :: 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++-- | Run the process in the start time immediately involving all pending+-- 'CurrentEvents' in the computation too.+runProcessInStartTime :: Process () -> Simulation ()+runProcessInStartTime = runEventInStartTime . runProcess++-- | Run the process in the start time immediately using the specified identifier+-- and involving all pending 'CurrentEvents' in the computation too.+runProcessInStartTimeUsingId :: ProcessId -> Process () -> Simulation ()+runProcessInStartTimeUsingId pid p =+ runEventInStartTime $ runProcessUsingId pid p++-- | Run the process in the final simulation time immediately involving all+-- pending 'CurrentEvents' in the computation too.+runProcessInStopTime :: Process () -> Simulation ()+runProcessInStopTime = runEventInStopTime . runProcess++-- | Run the process in the final simulation time immediately using +-- the specified identifier and involving all pending 'CurrentEvents'+-- in the computation too.+runProcessInStopTimeUsingId :: ProcessId -> Process () -> Simulation ()+runProcessInStopTimeUsingId pid p =+ runEventInStopTime $ runProcessUsingId pid p++-- | Enqueue the process that will be then started at the specified time+-- from the event queue.+enqueueProcess :: Double -> Process () -> Event ()+enqueueProcess t p =+ enqueueEvent t $ runProcess p++-- | Enqueue the process that will be then started at the specified time+-- from the event queue.+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.+newProcessId :: Simulation ProcessId+newProcessId =+ do x <- liftIO $ newIORef Nothing+ y <- liftIO $ newIORef False+ c <- newContCancellationSource+ i <- liftIO $ newIORef False+ z <- liftIO $ newIORef Nothing+ v <- liftIO $ newIORef 0+ return ProcessId { processStarted = y,+ processReactCont = x, + processCancelSource = c, + processInterruptRef = i,+ processInterruptCont = z, + processInterruptVersion = v }++-- | Cancel a process with the specified identifier, interrupting it if needed.+cancelProcessWithId :: ProcessId -> Event ()+cancelProcessWithId pid = contCancellationInitiate (processCancelSource pid)++-- | The process cancels itself.+cancelProcess :: Process a+cancelProcess =+ do pid <- processId+ liftEvent $ cancelProcessWithId pid+ throwProcess $+ (error "The process must be cancelled already: cancelProcess." :: SomeException)++-- | Test whether the process with the specified identifier was cancelled.+processCancelled :: ProcessId -> Event Bool+processCancelled pid = contCancellationInitiated (processCancelSource pid)++-- | Return a signal that notifies about cancelling the process with +-- the specified identifier.+processCancelling :: ProcessId -> Signal ()+processCancelling pid = contCancellationInitiating (processCancelSource pid)++-- | Register a handler that will be invoked in case of cancelling the current process.+whenCancellingProcess :: Event () -> Process ()+whenCancellingProcess h =+ Process $ \pid ->+ liftEvent $+ handleSignal_ (processCancelling pid) $ \() -> h++instance Eq ProcessId where+ x == y = processReactCont x == processReactCont y -- for the references are unique++instance Monad Process where+ return = returnP+ m >>= k = bindP m k++instance Functor Process where+ fmap = liftM++instance Applicative Process where+ pure = return+ (<*>) = ap++instance ParameterLift Process where+ liftParameter = liftPP++instance SimulationLift Process where+ liftSimulation = liftSP+ +instance DynamicsLift Process where+ liftDynamics = liftDP+ +instance EventLift Process where+ liftEvent = liftEP+ +instance MonadIO Process where+ liftIO = liftIOP+ +returnP :: a -> Process a+{-# INLINE returnP #-}+returnP a = Process $ \pid -> return a++bindP :: Process a -> (a -> Process b) -> Process b+{-# INLINE bindP #-}+bindP (Process m) k = + Process $ \pid -> + do a <- m pid+ 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++liftDP :: Dynamics a -> Process a+{-# INLINE liftDP #-}+liftDP m = Process $ \pid -> liftDynamics m++liftEP :: Event a -> Process a+{-# INLINE liftEP #-}+liftEP m = Process $ \pid -> liftEvent m++liftIOP :: IO a -> Process a+{-# INLINE liftIOP #-}+liftIOP m = Process $ \pid -> liftIO m++-- | Exception handling within 'Process' computations.+catchProcess :: Exception e => Process a -> (e -> Process a) -> Process a+catchProcess (Process m) h =+ Process $ \pid ->+ catchCont (m pid) $ \e ->+ let Process m' = h e in m' pid+ +-- | A computation with finalization part.+finallyProcess :: Process a -> Process b -> Process a+finallyProcess (Process m) (Process m') =+ Process $ \pid ->+ finallyCont (m pid) (m' pid)++-- | Throw the exception with the further exception handling.+--+-- By some reason, an exception raised with help of the standard 'throw' function+-- is not handled properly within 'Process' computation, altough it will be still handled +-- if it will be wrapped in the 'IO' monad. Therefore, you should use specialised+-- functions like the stated one that use the 'throw' function but within the 'IO' computation,+-- which allows already handling the exception.+throwProcess :: Exception e => e -> 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 propagated+-- 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. In case of cancelling one of the processes,+-- other process will be cancelled too.+spawnProcess :: Process () -> Process ()+spawnProcess = spawnProcessWith CancelTogether++-- | Spawn the child process with the specified process identifier.+-- In case of cancelling one of the processes, other process will+-- be cancelled too.+spawnProcessUsingId :: ProcessId -> Process () -> Process ()+spawnProcessUsingId = spawnProcessUsingIdWith CancelTogether++-- | Spawn the child process specifying how the child and parent processes+-- should be cancelled in case of need.+spawnProcessWith :: ContCancellation -> Process () -> Process ()+spawnProcessWith cancellation x =+ do pid <- liftSimulation newProcessId+ spawnProcessUsingIdWith cancellation pid x++-- | Spawn the child process specifying how the child and parent processes+-- should be cancelled in case of need.+spawnProcessUsingIdWith :: ContCancellation -> ProcessId -> Process () -> Process ()+spawnProcessUsingIdWith 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+ spawnProcessUsingIdWith CancelChildAfterParent timeoutPid $+ finallyProcess+ (holdProcess timeout)+ (liftEvent $+ cancelProcessWithId pid)+ spawnProcessUsingIdWith 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 (SomeException e)) -> throwProcess e++-- | Yield to allow other 'Process' and 'Event' computations to run+-- at the current simulation time point.+processYield :: Process ()+processYield =+ Process $ \pid ->+ Cont $ \c ->+ Event $ \p ->+ invokeEvent p $+ enqueueEvent (pointTime p) $+ resumeCont c ()++-- | A computation that never computes the result. It behaves like a black hole for+-- the discontinuous process, although such a process can still be canceled outside+-- (see 'cancelProcessWithId'), but then only its finalization parts (see 'finallyProcess')+-- will be called, usually, to release the resources acquired before.+neverProcess :: Process a+neverProcess =+ Process $ \pid ->+ Cont $ \c ->+ let signal = processCancelling pid+ in handleSignal_ signal $ \_ ->+ resumeCont c $ error "It must never be computed: neverProcess"
Simulation/Aivika/Internal/Signal.hs view
@@ -1,380 +1,380 @@- --- | --- Module : Simulation.Aivika.Internal.Signal --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- This module defines the signal which we can subscribe handlers to. --- These handlers can be disposed. The signal is triggered in the --- current time point actuating the corresponded computations from --- the handlers. --- - -module Simulation.Aivika.Internal.Signal - (-- * Handling and Triggering Signal - Signal(..), - handleSignal_, - SignalSource, - newSignalSource, - publishSignal, - triggerSignal, - -- * Useful Combinators - mapSignal, - mapSignalM, - apSignal, - filterSignal, - filterSignalM, - emptySignal, - merge2Signals, - merge3Signals, - merge4Signals, - merge5Signals, - -- * Signal Arriving - arrivalSignal, - -- * Creating Signal in Time Points - newSignalInTimes, - newSignalInIntegTimes, - newSignalInStartTime, - newSignalInStopTime, - -- * Signal History - SignalHistory, - signalHistorySignal, - newSignalHistory, - newSignalHistoryStartingWith, - 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 Simulation.Aivika.Internal.Arrival - -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, - -- ^ Publish the signal. - triggerSignal :: a -> Event () - -- ^ Trigger the signal actuating - -- all its handlers at the current - -- simulation time point. - } - --- | The signal that can have disposable handlers. -data Signal a = - Signal { handleSignal :: (a -> Event ()) -> Event DisposableEvent - -- ^ Subscribe the handler to the specified - -- signal and return a nested computation - -- within a disposable object that, being applied, - -- unsubscribes the handler from this signal. - } - --- | The queue of signal handlers. -data SignalHandlerQueue a = - SignalHandlerQueue { queueList :: IORef [SignalHandler a] } - --- | It contains the information about the disposable queue handler. -data SignalHandler a = - SignalHandler { handlerComp :: a -> Event (), - handlerRef :: IORef () } - -instance Eq (SignalHandler a) where - x == y = (handlerRef x) == (handlerRef y) - --- | Subscribe the handler to the specified signal forever. --- To subscribe the disposable handlers, use function 'handleSignal'. -handleSignal_ :: Signal a -> (a -> Event ()) -> Event () -handleSignal_ signal h = - do x <- handleSignal signal h - return () - --- | Create a new signal source. -newSignalSource :: Simulation (SignalSource a) -newSignalSource = - Simulation $ \r -> - do list <- newIORef [] - let queue = SignalHandlerQueue { queueList = list } - signal = Signal { handleSignal = handle } - source = SignalSource { publishSignal = signal, - triggerSignal = trigger } - handle h = - Event $ \p -> - do x <- enqueueSignalHandler queue h - return $ - DisposableEvent $ - Event $ \p -> dequeueSignalHandler queue x - trigger a = - Event $ \p -> triggerSignalHandlers queue a p - return source - --- | Trigger all next signal handlers. -triggerSignalHandlers :: SignalHandlerQueue a -> a -> Point -> IO () -{-# INLINE triggerSignalHandlers #-} -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 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 = - modifyIORef (queueList q) (delete h) - -instance Functor Signal where - fmap = mapSignal - -instance Monoid (Signal a) where - - mempty = emptySignal - - mappend = merge2Signals - - mconcat [] = emptySignal - mconcat [x1] = x1 - mconcat [x1, x2] = merge2Signals x1 x2 - mconcat [x1, x2, x3] = merge3Signals x1 x2 x3 - mconcat [x1, x2, x3, x4] = merge4Signals x1 x2 x3 x4 - mconcat [x1, x2, x3, x4, x5] = merge5Signals x1 x2 x3 x4 x5 - mconcat (x1 : x2 : x3 : x4 : x5 : xs) = - mconcat $ merge5Signals x1 x2 x3 x4 x5 : xs - --- | Map the signal according the specified function. -mapSignal :: (a -> b) -> Signal a -> Signal b -mapSignal f m = - Signal { handleSignal = \h -> - handleSignal m $ h . f } - --- | Filter only those signal values that satisfy to --- the specified predicate. -filterSignal :: (a -> Bool) -> Signal a -> Signal a -filterSignal p m = - Signal { handleSignal = \h -> - handleSignal m $ \a -> - when (p a) $ h a } - --- | Filter only those signal values that satisfy to --- the specified predicate. -filterSignalM :: (a -> Event Bool) -> Signal a -> Signal a -filterSignalM p m = - Signal { handleSignal = \h -> - handleSignal m $ \a -> - do x <- p a - when x $ h a } - --- | Merge two signals. -merge2Signals :: Signal a -> Signal a -> Signal a -merge2Signals m1 m2 = - Signal { handleSignal = \h -> - do x1 <- handleSignal m1 h - x2 <- handleSignal m2 h - return $ x1 <> x2 } - --- | Merge three signals. -merge3Signals :: Signal a -> Signal a -> Signal a -> Signal a -merge3Signals m1 m2 m3 = - Signal { handleSignal = \h -> - do x1 <- handleSignal m1 h - x2 <- handleSignal m2 h - x3 <- handleSignal m3 h - return $ x1 <> x2 <> x3 } - --- | Merge four signals. -merge4Signals :: Signal a -> Signal a -> Signal a -> - Signal a -> Signal a -merge4Signals m1 m2 m3 m4 = - Signal { handleSignal = \h -> - do x1 <- handleSignal m1 h - x2 <- handleSignal m2 h - x3 <- handleSignal m3 h - x4 <- handleSignal m4 h - return $ x1 <> x2 <> x3 <> x4 } - --- | Merge five signals. -merge5Signals :: Signal a -> Signal a -> Signal a -> - Signal a -> Signal a -> Signal a -merge5Signals m1 m2 m3 m4 m5 = - Signal { handleSignal = \h -> - do x1 <- handleSignal m1 h - x2 <- handleSignal m2 h - x3 <- handleSignal m3 h - x4 <- handleSignal m4 h - x5 <- handleSignal m5 h - return $ x1 <> x2 <> x3 <> x4 <> x5 } - --- | Compose the signal. -mapSignalM :: (a -> Event b) -> Signal a -> Signal b -mapSignalM f m = - Signal { handleSignal = \h -> - handleSignal m (f >=> h) } - --- | Transform the signal. -apSignal :: Event (a -> b) -> Signal a -> Signal b -apSignal f m = - Signal { handleSignal = \h -> - handleSignal m $ \a -> do { x <- f; h (x a) } } - --- | An empty signal which is never triggered. -emptySignal :: Signal a -emptySignal = - Signal { handleSignal = \h -> return mempty } - --- | 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 = - newSignalHistoryStartingWith Nothing - --- | Create a history of the signal values starting with --- the optional initial value. -newSignalHistoryStartingWith :: Maybe a -> Signal a -> Event (SignalHistory a) -newSignalHistoryStartingWith init signal = - Event $ \p -> - do ts <- UV.newVector - xs <- V.newVector - case init of - Nothing -> return () - Just a -> - do UV.appendVector ts (pointTime p) - V.appendVector xs a - invokeEvent p $ - handleSignal_ signal $ \a -> - Event $ \p -> - do UV.appendVector ts (pointTime p) - 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) } - --- | Transform a signal so that the resulting signal returns a sequence of arrivals --- saving the information about the time points at which the original signal was received. -arrivalSignal :: Signal a -> Signal (Arrival a) -arrivalSignal m = - Signal { handleSignal = \h -> - Event $ \p -> - do r <- newIORef Nothing - invokeEvent p $ - handleSignal m $ \a -> - Event $ \p -> - do t0 <- readIORef r - let t = pointTime p - writeIORef r (Just t) - invokeEvent p $ - h Arrival { arrivalValue = a, - arrivalTime = t, - arrivalDelay = - case t0 of - Nothing -> Nothing - Just t0 -> Just (t - t0) } - } ++-- |+-- Module : Simulation.Aivika.Internal.Signal+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- This module defines the signal which we can subscribe handlers to. +-- These handlers can be disposed. The signal is triggered in the +-- current time point actuating the corresponded computations from +-- the handlers. +--++module Simulation.Aivika.Internal.Signal+ (-- * Handling and Triggering Signal+ Signal(..),+ handleSignal_,+ SignalSource,+ newSignalSource,+ publishSignal,+ triggerSignal,+ -- * Useful Combinators+ mapSignal,+ mapSignalM,+ apSignal,+ filterSignal,+ filterSignalM,+ emptySignal,+ merge2Signals,+ merge3Signals,+ merge4Signals,+ merge5Signals,+ -- * Signal Arriving+ arrivalSignal,+ -- * Creating Signal in Time Points+ newSignalInTimes,+ newSignalInIntegTimes,+ newSignalInStartTime,+ newSignalInStopTime,+ -- * Signal History+ SignalHistory,+ signalHistorySignal,+ newSignalHistory,+ newSignalHistoryStartingWith,+ 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 Simulation.Aivika.Internal.Arrival++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,+ -- ^ Publish the signal.+ triggerSignal :: a -> Event ()+ -- ^ Trigger the signal actuating + -- all its handlers at the current + -- simulation time point.+ }+ +-- | The signal that can have disposable handlers. +data Signal a =+ Signal { handleSignal :: (a -> Event ()) -> Event DisposableEvent+ -- ^ Subscribe the handler to the specified + -- signal and return a nested computation+ -- within a disposable object that, being applied,+ -- unsubscribes the handler from this signal.+ }+ +-- | The queue of signal handlers.+data SignalHandlerQueue a =+ SignalHandlerQueue { queueList :: IORef [SignalHandler a] }+ +-- | It contains the information about the disposable queue handler.+data SignalHandler a =+ SignalHandler { handlerComp :: a -> Event (),+ handlerRef :: IORef () }++instance Eq (SignalHandler a) where+ x == y = (handlerRef x) == (handlerRef y)++-- | Subscribe the handler to the specified signal forever.+-- To subscribe the disposable handlers, use function 'handleSignal'.+handleSignal_ :: Signal a -> (a -> Event ()) -> Event ()+handleSignal_ signal h = + do x <- handleSignal signal h+ return ()+ +-- | Create a new signal source.+newSignalSource :: Simulation (SignalSource a)+newSignalSource =+ Simulation $ \r ->+ do list <- newIORef []+ let queue = SignalHandlerQueue { queueList = list }+ signal = Signal { handleSignal = handle }+ source = SignalSource { publishSignal = signal, + triggerSignal = trigger }+ handle h =+ Event $ \p ->+ do x <- enqueueSignalHandler queue h+ return $+ DisposableEvent $+ Event $ \p -> dequeueSignalHandler queue x+ trigger a =+ Event $ \p -> triggerSignalHandlers queue a p+ return source++-- | Trigger all next signal handlers.+triggerSignalHandlers :: SignalHandlerQueue a -> a -> Point -> IO ()+{-# INLINE triggerSignalHandlers #-}+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 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 = + modifyIORef (queueList q) (delete h)++instance Functor Signal where+ fmap = mapSignal+ +instance Monoid (Signal a) where + + mempty = emptySignal+ + mappend = merge2Signals+ + mconcat [] = emptySignal+ mconcat [x1] = x1+ mconcat [x1, x2] = merge2Signals x1 x2+ mconcat [x1, x2, x3] = merge3Signals x1 x2 x3+ mconcat [x1, x2, x3, x4] = merge4Signals x1 x2 x3 x4+ mconcat [x1, x2, x3, x4, x5] = merge5Signals x1 x2 x3 x4 x5+ mconcat (x1 : x2 : x3 : x4 : x5 : xs) = + mconcat $ merge5Signals x1 x2 x3 x4 x5 : xs+ +-- | Map the signal according the specified function.+mapSignal :: (a -> b) -> Signal a -> Signal b+mapSignal f m =+ Signal { handleSignal = \h -> + handleSignal m $ h . f }++-- | Filter only those signal values that satisfy to +-- the specified predicate.+filterSignal :: (a -> Bool) -> Signal a -> Signal a+filterSignal p m =+ Signal { handleSignal = \h ->+ handleSignal m $ \a ->+ when (p a) $ h a }+ +-- | Filter only those signal values that satisfy to +-- the specified predicate.+filterSignalM :: (a -> Event Bool) -> Signal a -> Signal a+filterSignalM p m =+ Signal { handleSignal = \h ->+ handleSignal m $ \a ->+ do x <- p a+ when x $ h a }+ +-- | Merge two signals.+merge2Signals :: Signal a -> Signal a -> Signal a+merge2Signals m1 m2 =+ Signal { handleSignal = \h ->+ do x1 <- handleSignal m1 h+ x2 <- handleSignal m2 h+ return $ x1 <> x2 }++-- | Merge three signals.+merge3Signals :: Signal a -> Signal a -> Signal a -> Signal a+merge3Signals m1 m2 m3 =+ Signal { handleSignal = \h ->+ do x1 <- handleSignal m1 h+ x2 <- handleSignal m2 h+ x3 <- handleSignal m3 h+ return $ x1 <> x2 <> x3 }++-- | Merge four signals.+merge4Signals :: Signal a -> Signal a -> Signal a -> + Signal a -> Signal a+merge4Signals m1 m2 m3 m4 =+ Signal { handleSignal = \h ->+ do x1 <- handleSignal m1 h+ x2 <- handleSignal m2 h+ x3 <- handleSignal m3 h+ x4 <- handleSignal m4 h+ return $ x1 <> x2 <> x3 <> x4 }+ +-- | Merge five signals.+merge5Signals :: Signal a -> Signal a -> Signal a -> + Signal a -> Signal a -> Signal a+merge5Signals m1 m2 m3 m4 m5 =+ Signal { handleSignal = \h ->+ do x1 <- handleSignal m1 h+ x2 <- handleSignal m2 h+ x3 <- handleSignal m3 h+ x4 <- handleSignal m4 h+ x5 <- handleSignal m5 h+ return $ x1 <> x2 <> x3 <> x4 <> x5 }++-- | Compose the signal.+mapSignalM :: (a -> Event b) -> Signal a -> Signal b+mapSignalM f m =+ Signal { handleSignal = \h ->+ handleSignal m (f >=> h) }+ +-- | Transform the signal.+apSignal :: Event (a -> b) -> Signal a -> Signal b+apSignal f m =+ Signal { handleSignal = \h ->+ handleSignal m $ \a -> do { x <- f; h (x a) } }++-- | An empty signal which is never triggered.+emptySignal :: Signal a+emptySignal =+ Signal { handleSignal = \h -> return mempty }+ +-- | 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 =+ newSignalHistoryStartingWith Nothing++-- | Create a history of the signal values starting with+-- the optional initial value.+newSignalHistoryStartingWith :: Maybe a -> Signal a -> Event (SignalHistory a)+newSignalHistoryStartingWith init signal =+ Event $ \p ->+ do ts <- UV.newVector+ xs <- V.newVector+ case init of+ Nothing -> return ()+ Just a ->+ do UV.appendVector ts (pointTime p)+ V.appendVector xs a+ invokeEvent p $+ handleSignal_ signal $ \a ->+ Event $ \p ->+ do UV.appendVector ts (pointTime p)+ 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) }++-- | Transform a signal so that the resulting signal returns a sequence of arrivals+-- saving the information about the time points at which the original signal was received.+arrivalSignal :: Signal a -> Signal (Arrival a)+arrivalSignal m = + Signal { handleSignal = \h ->+ Event $ \p ->+ do r <- newIORef Nothing+ invokeEvent p $+ handleSignal m $ \a ->+ Event $ \p ->+ do t0 <- readIORef r+ let t = pointTime p+ writeIORef r (Just t)+ invokeEvent p $+ h Arrival { arrivalValue = a,+ arrivalTime = t,+ arrivalDelay =+ case t0 of+ Nothing -> Nothing+ Just t0 -> Just (t - t0) }+ }
Simulation/Aivika/Internal/Simulation.hs view
@@ -1,161 +1,161 @@- -{-# LANGUAGE RecursiveDo #-} - --- | --- Module : Simulation.Aivika.Internal.Simulation --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- The module defines the 'Simulation' monad that represents a computation --- within the simulation run. --- -module Simulation.Aivika.Internal.Simulation - (-- * Simulation - Simulation(..), - SimulationLift(..), - invokeSimulation, - runSimulation, - runSimulations, - -- * Error Handling - catchSimulation, - finallySimulation, - throwSimulation, - -- * Utilities - simulationEventQueue, - -- * Memoization - memoSimulation) where - -import Control.Exception -import Control.Monad -import Control.Monad.Trans -import Control.Monad.Fix -import Control.Applicative - -import Data.IORef - -import Simulation.Aivika.Generator -import Simulation.Aivika.Internal.Specs -import Simulation.Aivika.Internal.Parameter - --- | A value in the 'Simulation' monad represents a computation --- within the simulation run. -newtype Simulation a = Simulation (Run -> IO a) - -instance Monad Simulation where - return = returnS - m >>= k = bindS m k - -returnS :: a -> Simulation a -{-# INLINE returnS #-} -returnS a = Simulation (\r -> return a) - -bindS :: Simulation a -> (a -> Simulation b) -> Simulation b -{-# INLINE bindS #-} -bindS (Simulation m) k = - Simulation $ \r -> - do a <- m r - let Simulation m' = k a - m' r - --- | Run the simulation using the specified specs. -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, - 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, - runGenerator = g } - --- | Return the event queue. -simulationEventQueue :: Simulation EventQueue -simulationEventQueue = Simulation $ return . runEventQueue - -instance Functor Simulation where - fmap = liftMS - -instance Applicative Simulation where - pure = return - (<*>) = ap - -liftMS :: (a -> b) -> Simulation a -> Simulation b -{-# INLINE liftMS #-} -liftMS f (Simulation x) = - Simulation $ \r -> do { a <- x r; return $ f a } - -instance MonadIO Simulation where - liftIO m = Simulation $ const m - --- | A type class to lift the simulation computations to other computations. -class SimulationLift m where - - -- | 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 :: Exception e => Simulation a -> (e -> Simulation a) -> Simulation a -catchSimulation (Simulation m) h = - Simulation $ \r -> - catch (m r) $ \e -> - let Simulation m' = h e in m' r - --- | A computation with finalization part like the 'finally' function. -finallySimulation :: Simulation a -> Simulation b -> Simulation a -finallySimulation (Simulation m) (Simulation m') = - Simulation $ \r -> - finally (m r) (m' r) - --- | Like the standard 'throw' function. -throwSimulation :: Exception e => e -> Simulation a -throwSimulation = throw - --- | Invoke the 'Simulation' computation. -invokeSimulation :: Run -> Simulation a -> IO a -{-# INLINE invokeSimulation #-} -invokeSimulation r (Simulation m) = m r - -instance MonadFix Simulation where - 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 ++{-# LANGUAGE RecursiveDo #-}++-- |+-- Module : Simulation.Aivika.Internal.Simulation+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- The module defines the 'Simulation' monad that represents a computation+-- within the simulation run.+-- +module Simulation.Aivika.Internal.Simulation+ (-- * Simulation+ Simulation(..),+ SimulationLift(..),+ invokeSimulation,+ runSimulation,+ runSimulations,+ -- * Error Handling+ catchSimulation,+ finallySimulation,+ throwSimulation,+ -- * Utilities+ simulationEventQueue,+ -- * Memoization+ memoSimulation) where++import Control.Exception+import Control.Monad+import Control.Monad.Trans+import Control.Monad.Fix+import Control.Applicative++import Data.IORef++import Simulation.Aivika.Generator+import Simulation.Aivika.Internal.Specs+import Simulation.Aivika.Internal.Parameter++-- | A value in the 'Simulation' monad represents a computation+-- within the simulation run.+newtype Simulation a = Simulation (Run -> IO a)++instance Monad Simulation where+ return = returnS+ m >>= k = bindS m k++returnS :: a -> Simulation a+{-# INLINE returnS #-}+returnS a = Simulation (\r -> return a)++bindS :: Simulation a -> (a -> Simulation b) -> Simulation b+{-# INLINE bindS #-}+bindS (Simulation m) k = + Simulation $ \r -> + do a <- m r+ let Simulation m' = k a+ m' r++-- | Run the simulation using the specified specs.+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,+ 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,+ runGenerator = g }++-- | Return the event queue.+simulationEventQueue :: Simulation EventQueue+simulationEventQueue = Simulation $ return . runEventQueue++instance Functor Simulation where+ fmap = liftMS++instance Applicative Simulation where+ pure = return+ (<*>) = ap++liftMS :: (a -> b) -> Simulation a -> Simulation b+{-# INLINE liftMS #-}+liftMS f (Simulation x) =+ Simulation $ \r -> do { a <- x r; return $ f a }++instance MonadIO Simulation where+ liftIO m = Simulation $ const m++-- | A type class to lift the simulation computations to other computations.+class SimulationLift m where+ + -- | 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 :: Exception e => Simulation a -> (e -> Simulation a) -> Simulation a+catchSimulation (Simulation m) h =+ Simulation $ \r -> + catch (m r) $ \e ->+ let Simulation m' = h e in m' r+ +-- | A computation with finalization part like the 'finally' function.+finallySimulation :: Simulation a -> Simulation b -> Simulation a+finallySimulation (Simulation m) (Simulation m') =+ Simulation $ \r ->+ finally (m r) (m' r)++-- | Like the standard 'throw' function.+throwSimulation :: Exception e => e -> Simulation a+throwSimulation = throw++-- | Invoke the 'Simulation' computation.+invokeSimulation :: Run -> Simulation a -> IO a+{-# INLINE invokeSimulation #-}+invokeSimulation r (Simulation m) = m r++instance MonadFix Simulation where+ 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
@@ -1,223 +1,223 @@- --- | --- Module : Simulation.Aivika.Internal.Specs --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- It defines the simulation specs and related stuff. -module Simulation.Aivika.Internal.Specs - (Specs(..), - Method(..), - Run(..), - Point(..), - EventQueue(..), - newEventQueue, - basicTime, - integIterationBnds, - integIterationHiBnd, - integIterationLoBnd, - integPhaseBnds, - integPhaseHiBnd, - integPhaseLoBnd, - integTimes, - integPoints, - integStartPoint, - integStopPoint, - pointAt) where - -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 - spcGeneratorType :: GeneratorType - -- ^ the type of the random number generator - } - --- | It defines the integration method. -data Method = Euler -- ^ Euler's method - | RungeKutta2 -- ^ the 2nd order Runge-Kutta method - | RungeKutta4 -- ^ the 4th order Runge-Kutta method - deriving (Eq, Ord, Show) - --- | It indentifies the simulation run. -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 - runGenerator :: Generator -- ^ the random number generator - } - --- | It defines the simulation point appended with the additional information. -data Point = Point { pointSpecs :: Specs, -- ^ the simulation specs - pointRun :: Run, -- ^ the simulation run - pointTime :: Double, -- ^ the current time - pointIteration :: Int, -- ^ the current iteration - pointPhase :: Int -- ^ the current phase - } - --- | It represents the event queue. -data EventQueue = EventQueue { queuePQ :: PQ.PriorityQueue (Point -> IO ()), - -- ^ the underlying priority queue - queueBusy :: IORef Bool, - -- ^ whether the queue is currently processing events - queueTime :: IORef Double - -- ^ the actual time of the event queue - } - --- | Create a new event queue by the specified specs. -newEventQueue :: Specs -> IO EventQueue -newEventQueue specs = - do f <- newIORef False - t <- newIORef $ spcStartTime specs - pq <- PQ.newQueue - return EventQueue { queuePQ = pq, - queueBusy = f, - queueTime = t } - --- | Returns the integration iterations starting from zero. -integIterations :: Specs -> [Int] -integIterations sc = [i1 .. i2] where - i1 = integIterationLoBnd sc - i2 = integIterationHiBnd sc - --- | Returns the first and last integration iterations. -integIterationBnds :: Specs -> (Int, Int) -integIterationBnds sc = (i1, i2) where - i1 = integIterationLoBnd sc - i2 = integIterationHiBnd sc - --- | Returns the first integration iteration, i.e. zero. -integIterationLoBnd :: Specs -> Int -integIterationLoBnd sc = 0 - --- | Returns the last integration iteration. -integIterationHiBnd :: Specs -> Int -integIterationHiBnd sc = - let n = round ((spcStopTime sc - - spcStartTime sc) / spcDT sc) - in if n < 0 - then - error $ - "The iteration number in the stop time has a negative value. " ++ - "Either the simulation specs are incorrect, " ++ - "or a floating point overflow occurred, " ++ - "for example, when using a too small integration time step. " ++ - "You have to define this time step regardless of " ++ - "whether you actually use it or not, " ++ - "for Aivika allows combining the ordinary differential equations " ++ - "with the discrete event simulation within one model. " ++ - "So, if you are still using the 32-bit architecture and " ++ - "you do need a small integration time step " ++ - "for integrating the equations " ++ - "then you might think of using the 64-bit architecture. " ++ - "Although you could probably just forget " ++ - "to increase the time step " ++ - "after increasing the stop time: integIterationHiBnd" - else n - --- | Returns the phases for the specified simulation specs starting from zero. -integPhases :: Specs -> [Int] -integPhases sc = - case spcMethod sc of - Euler -> [0] - RungeKutta2 -> [0, 1] - RungeKutta4 -> [0, 1, 2, 3] - --- | Returns the first and last integration phases. -integPhaseBnds :: Specs -> (Int, Int) -integPhaseBnds sc = - case spcMethod sc of - Euler -> (0, 0) - RungeKutta2 -> (0, 1) - RungeKutta4 -> (0, 3) - --- | Returns the first integration phase, i.e. zero. -integPhaseLoBnd :: Specs -> Int -integPhaseLoBnd sc = 0 - --- | Returns the last integration phase, 0 for Euler's method, 1 for RK2 and 3 for RK4. -integPhaseHiBnd :: Specs -> Int -integPhaseHiBnd sc = - case spcMethod sc of - Euler -> 0 - RungeKutta2 -> 1 - RungeKutta4 -> 3 - --- | Returns a simulation time for the integration point specified by --- the specs, iteration and phase. -basicTime :: Specs -> Int -> Int -> Double -basicTime sc n ph = - if ph < 0 then - error "Incorrect phase: basicTime" - else - spcStartTime sc + n' * spcDT sc + delta (spcMethod sc) ph - where n' = fromIntegral n - delta Euler 0 = 0 - delta RungeKutta2 0 = 0 - delta RungeKutta2 1 = spcDT sc - delta RungeKutta4 0 = 0 - delta RungeKutta4 1 = spcDT sc / 2 - delta RungeKutta4 2 = spcDT sc / 2 - delta RungeKutta4 3 = spcDT sc - --- | Return the integration time values. -integTimes :: Specs -> [Double] -integTimes sc = map t [nl .. nu] - where (nl, nu) = integIterationBnds sc - t n = basicTime sc n 0 - --- | Return the integration time points. -integPoints :: Run -> [Point] -integPoints r = points - where sc = runSpecs r - (nl, nu) = integIterationBnds sc - points = map point [nl .. nu] - point n = Point { pointSpecs = sc, - pointRun = r, - pointTime = basicTime sc n 0, - pointIteration = n, - pointPhase = 0 } - --- | Return the start time point. -integStartPoint :: Run -> Point -integStartPoint r = point nl - where sc = runSpecs r - (nl, nu) = integIterationBnds sc - point n = Point { pointSpecs = sc, - pointRun = r, - pointTime = basicTime sc n 0, - pointIteration = n, - pointPhase = 0 } - --- | Return the stop time point. -integStopPoint :: Run -> Point -integStopPoint r = point nu - where sc = runSpecs r - (nl, nu) = integIterationBnds sc - point n = Point { pointSpecs = sc, - pointRun = r, - pointTime = basicTime sc n 0, - pointIteration = n, - pointPhase = 0 } - --- | Return the point at the specified time. -pointAt :: Run -> Double -> Point -pointAt r t = p - where sc = runSpecs r - t0 = spcStartTime sc - dt = spcDT sc - n = fromIntegral $ floor ((t - t0) / dt) - p = Point { pointSpecs = sc, - pointRun = r, - pointTime = t, - pointIteration = n, - pointPhase = -1 } ++-- |+-- Module : Simulation.Aivika.Internal.Specs+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- It defines the simulation specs and related stuff.+module Simulation.Aivika.Internal.Specs+ (Specs(..),+ Method(..),+ Run(..),+ Point(..),+ EventQueue(..),+ newEventQueue,+ basicTime,+ integIterationBnds,+ integIterationHiBnd,+ integIterationLoBnd,+ integPhaseBnds,+ integPhaseHiBnd,+ integPhaseLoBnd,+ integTimes,+ integPoints,+ integStartPoint,+ integStopPoint,+ pointAt) where++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+ spcGeneratorType :: GeneratorType+ -- ^ the type of the random number generator+ }++-- | It defines the integration method.+data Method = Euler -- ^ Euler's method+ | RungeKutta2 -- ^ the 2nd order Runge-Kutta method+ | RungeKutta4 -- ^ the 4th order Runge-Kutta method+ deriving (Eq, Ord, Show)++-- | It indentifies the simulation run.+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+ runGenerator :: Generator -- ^ the random number generator+ }++-- | It defines the simulation point appended with the additional information.+data Point = Point { pointSpecs :: Specs, -- ^ the simulation specs+ pointRun :: Run, -- ^ the simulation run+ pointTime :: Double, -- ^ the current time+ pointIteration :: Int, -- ^ the current iteration+ pointPhase :: Int -- ^ the current phase+ }++-- | It represents the event queue.+data EventQueue = EventQueue { queuePQ :: PQ.PriorityQueue (Point -> IO ()),+ -- ^ the underlying priority queue+ queueBusy :: IORef Bool,+ -- ^ whether the queue is currently processing events+ queueTime :: IORef Double+ -- ^ the actual time of the event queue+ }++-- | Create a new event queue by the specified specs.+newEventQueue :: Specs -> IO EventQueue+newEventQueue specs = + do f <- newIORef False+ t <- newIORef $ spcStartTime specs+ pq <- PQ.newQueue+ return EventQueue { queuePQ = pq,+ queueBusy = f,+ queueTime = t }++-- | Returns the integration iterations starting from zero.+integIterations :: Specs -> [Int]+integIterations sc = [i1 .. i2] where+ i1 = integIterationLoBnd sc+ i2 = integIterationHiBnd sc++-- | Returns the first and last integration iterations.+integIterationBnds :: Specs -> (Int, Int)+integIterationBnds sc = (i1, i2) where+ i1 = integIterationLoBnd sc+ i2 = integIterationHiBnd sc++-- | Returns the first integration iteration, i.e. zero.+integIterationLoBnd :: Specs -> Int+integIterationLoBnd sc = 0++-- | Returns the last integration iteration.+integIterationHiBnd :: Specs -> Int+integIterationHiBnd sc =+ let n = round ((spcStopTime sc - + spcStartTime sc) / spcDT sc)+ in if n < 0+ then+ error $+ "The iteration number in the stop time has a negative value. " +++ "Either the simulation specs are incorrect, " +++ "or a floating point overflow occurred, " +++ "for example, when using a too small integration time step. " +++ "You have to define this time step regardless of " +++ "whether you actually use it or not, " +++ "for Aivika allows combining the ordinary differential equations " +++ "with the discrete event simulation within one model. " +++ "So, if you are still using the 32-bit architecture and " +++ "you do need a small integration time step " +++ "for integrating the equations " +++ "then you might think of using the 64-bit architecture. " +++ "Although you could probably just forget " +++ "to increase the time step " +++ "after increasing the stop time: integIterationHiBnd"+ else n++-- | Returns the phases for the specified simulation specs starting from zero.+integPhases :: Specs -> [Int]+integPhases sc = + case spcMethod sc of+ Euler -> [0]+ RungeKutta2 -> [0, 1]+ RungeKutta4 -> [0, 1, 2, 3]++-- | Returns the first and last integration phases.+integPhaseBnds :: Specs -> (Int, Int)+integPhaseBnds sc = + case spcMethod sc of+ Euler -> (0, 0)+ RungeKutta2 -> (0, 1)+ RungeKutta4 -> (0, 3)++-- | Returns the first integration phase, i.e. zero.+integPhaseLoBnd :: Specs -> Int+integPhaseLoBnd sc = 0+ +-- | Returns the last integration phase, 0 for Euler's method, 1 for RK2 and 3 for RK4.+integPhaseHiBnd :: Specs -> Int+integPhaseHiBnd sc = + case spcMethod sc of+ Euler -> 0+ RungeKutta2 -> 1+ RungeKutta4 -> 3++-- | Returns a simulation time for the integration point specified by +-- the specs, iteration and phase.+basicTime :: Specs -> Int -> Int -> Double+basicTime sc n ph =+ if ph < 0 then + error "Incorrect phase: basicTime"+ else+ spcStartTime sc + n' * spcDT sc + delta (spcMethod sc) ph + where n' = fromIntegral n+ delta Euler 0 = 0+ delta RungeKutta2 0 = 0+ delta RungeKutta2 1 = spcDT sc+ delta RungeKutta4 0 = 0+ delta RungeKutta4 1 = spcDT sc / 2+ delta RungeKutta4 2 = spcDT sc / 2+ delta RungeKutta4 3 = spcDT sc++-- | Return the integration time values.+integTimes :: Specs -> [Double]+integTimes sc = map t [nl .. nu]+ where (nl, nu) = integIterationBnds sc+ t n = basicTime sc n 0++-- | Return the integration time points.+integPoints :: Run -> [Point]+integPoints r = points+ where sc = runSpecs r+ (nl, nu) = integIterationBnds sc+ points = map point [nl .. nu]+ point n = Point { pointSpecs = sc,+ pointRun = r,+ pointTime = basicTime sc n 0,+ pointIteration = n,+ pointPhase = 0 }++-- | Return the start time point.+integStartPoint :: Run -> Point+integStartPoint r = point nl+ where sc = runSpecs r+ (nl, nu) = integIterationBnds sc+ point n = Point { pointSpecs = sc,+ pointRun = r,+ pointTime = basicTime sc n 0,+ pointIteration = n,+ pointPhase = 0 }++-- | Return the stop time point.+integStopPoint :: Run -> Point+integStopPoint r = point nu+ where sc = runSpecs r+ (nl, nu) = integIterationBnds sc+ point n = Point { pointSpecs = sc,+ pointRun = r,+ pointTime = basicTime sc n 0,+ pointIteration = n,+ pointPhase = 0 }++-- | Return the point at the specified time.+pointAt :: Run -> Double -> Point+pointAt r t = p+ where sc = runSpecs r+ t0 = spcStartTime sc+ dt = spcDT sc+ n = fromIntegral $ floor ((t - t0) / dt)+ p = Point { pointSpecs = sc,+ pointRun = r,+ pointTime = t,+ pointIteration = n,+ pointPhase = -1 }
Simulation/Aivika/Net.hs view
@@ -1,242 +1,249 @@- --- | --- Module : Simulation.Aivika.Net --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- The module defines a 'Net' arrow that can be applied to modeling the queue networks --- like the 'Processor' arrow from another module. Only the former has a more efficient --- implementation of the 'Arrow' interface than the latter, although at the cost of --- some decreasing in generality. --- --- While the @Processor@ type is just a function that transforms the input 'Stream' into another, --- the @Net@ type is actually an automaton that has an implementation very similar to that one --- which the 'Circuit' type has, only the computations occur in the 'Process' monad. But unlike --- the @Circuit@ type, the @Net@ type doesn't allow declaring recursive definitions, being based on --- continuations. --- --- In a nutshell, the @Net@ type is an interchangeable alternative to the @Processor@ type --- with its weaknesses and strengths. The @Net@ arrow is useful for constructing computations --- with help of the proc-notation to be transformed then to the @Processor@ computations that --- are more general in nature and more easy-to-use but which computations created with help of --- the proc-notation are not so efficient. --- -module Simulation.Aivika.Net - (-- * Net Arrow - Net(..), - -- * Net Primitives - emptyNet, - arrNet, - accumNet, - -- * Specifying Identifier - netUsingId, - -- * Arrival Net - arrivalNet, - -- * Delaying Net - delayNet, - -- * Interchanging Nets with Processors - netProcessor, - processorNet) where - -import qualified Control.Category as C -import Control.Arrow -import Control.Monad.Trans - -import Data.IORef - -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 -import Simulation.Aivika.Resource -import Simulation.Aivika.Processor -import Simulation.Aivika.Ref -import Simulation.Aivika.Circuit -import Simulation.Aivika.Internal.Arrival - --- | Represents the net as an automaton working within the 'Process' computation. -newtype Net a b = - Net { runNet :: a -> Process (b, Net a b) - -- ^ Run the net. - } - -instance C.Category Net where - - id = Net $ \a -> return (a, C.id) - - (.) = dot - where - (Net g) `dot` (Net f) = - Net $ \a -> - do (b, p1) <- f a - (c, p2) <- g b - return (c, p2 `dot` p1) - -instance Arrow Net where - - arr f = Net $ \a -> return (f a, arr f) - - first (Net f) = - Net $ \(b, d) -> - do (c, p) <- f b - return ((c, d), first p) - - second (Net f) = - Net $ \(d, b) -> - do (c, p) <- f b - return ((d, c), second p) - - (Net f) *** (Net g) = - Net $ \(b, b') -> - do (c, p1) <- f b - (c', p2) <- g b' - return ((c, c'), p1 *** p2) - - (Net f) &&& (Net g) = - Net $ \b -> - do (c, p1) <- f b - (c', p2) <- g b - return ((c, c'), p1 &&& p2) - -instance ArrowChoice Net where - - left x@(Net f) = - Net $ \ebd -> - case ebd of - Left b -> - do (c, p) <- f b - return (Left c, left p) - Right d -> - return (Right d, left x) - - right x@(Net f) = - Net $ \edb -> - case edb of - Right b -> - do (c, p) <- f b - return (Right c, right p) - Left d -> - return (Left d, right x) - - x@(Net f) +++ y@(Net g) = - Net $ \ebb' -> - case ebb' of - Left b -> - do (c, p1) <- f b - return (Left c, p1 +++ y) - Right b' -> - do (c', p2) <- g b' - return (Right c', x +++ p2) - - x@(Net f) ||| y@(Net g) = - Net $ \ebc -> - case ebc of - Left b -> - do (d, p1) <- f b - return (d, p1 ||| y) - Right b' -> - do (d, p2) <- g b' - return (d, x ||| p2) - --- | A net that never finishes its work. -emptyNet :: Net a b -emptyNet = Net $ const neverProcess - --- | Create a simple net by the specified handling function --- that runs the discontinuous process for each input value to get an output. -arrNet :: (a -> Process b) -> Net a b -arrNet f = - let x = - Net $ \a -> - do b <- f a - return (b, x) - in x - --- | Accumulator that outputs a value determined by the supplied function. -accumNet :: (acc -> a -> Process (acc, b)) -> acc -> Net a b -accumNet f acc = - Net $ \a -> - do (acc', b) <- f acc a - return (b, accumNet f acc') - --- | Create a net 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. -netUsingId :: ProcessId -> Net a b -> Net a b -netUsingId pid (Net f) = - Net $ processUsingId pid . f - --- | Transform the net to an equivalent processor (a rather cheap transformation). -netProcessor :: Net a b -> Processor a b -netProcessor = Processor . loop - where loop x as = - Cons $ - do (a, as') <- runStream as - (b, x') <- runNet x a - return (b, loop x' as') - --- | Transform the processor to a similar net (a more costly transformation). -processorNet :: Processor a b -> Net a b -processorNet x = - Net $ \a -> - do readingA <- liftSimulation $ newResourceWithMaxCount FCFS 0 (Just 1) - writingA <- liftSimulation $ newResourceWithMaxCount FCFS 1 (Just 1) - readingB <- liftSimulation $ newResourceWithMaxCount FCFS 0 (Just 1) - writingB <- liftSimulation $ newResourceWithMaxCount FCFS 1 (Just 1) - conting <- liftSimulation $ newResourceWithMaxCount FCFS 0 (Just 1) - refA <- liftIO $ newIORef Nothing - refB <- liftIO $ newIORef Nothing - let input = - do requestResource readingA - Just a <- liftIO $ readIORef refA - liftIO $ writeIORef refA Nothing - releaseResource writingA - return (a, Cons input) - consume bs = - do (b, bs') <- runStream bs - requestResource writingB - liftIO $ writeIORef refB (Just b) - releaseResource readingB - requestResource conting - consume bs' - loop a = - do requestResource writingA - liftIO $ writeIORef refA (Just a) - releaseResource readingA - requestResource readingB - Just b <- liftIO $ readIORef refB - liftIO $ writeIORef refB Nothing - releaseResource writingB - return (b, Net $ \a -> releaseResource conting >> loop a) - spawnProcess CancelTogether $ - consume $ runProcessor x (Cons input) - loop a - --- | A net that adds the information about the time points at which --- the values were received. -arrivalNet :: Net a (Arrival a) -arrivalNet = - let loop t0 = - Net $ \a -> - do t <- liftDynamics time - let b = Arrival { arrivalValue = a, - arrivalTime = t, - arrivalDelay = - case t0 of - Nothing -> Nothing - Just t0 -> Just (t - t0) } - return (b, loop $ Just t) - in loop Nothing - --- | Delay the input by one step using the specified initial value. -delayNet :: a -> Net a a -delayNet a0 = - Net $ \a -> - return (a0, delayNet a) ++-- |+-- Module : Simulation.Aivika.Net+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- The module defines a 'Net' arrow that can be applied to modeling the queue networks+-- like the 'Processor' arrow from another module. Only the former has a more efficient+-- implementation of the 'Arrow' interface than the latter, although at the cost of+-- some decreasing in generality.+--+-- While the @Processor@ type is just a function that transforms the input 'Stream' into another,+-- the @Net@ type is actually an automaton that has an implementation very similar to that one+-- which the 'Circuit' type has, only the computations occur in the 'Process' monad. But unlike+-- the @Circuit@ type, the @Net@ type doesn't allow declaring recursive definitions, being based on+-- continuations.+--+-- In a nutshell, the @Net@ type is an interchangeable alternative to the @Processor@ type+-- with its weaknesses and strengths. The @Net@ arrow is useful for constructing computations+-- with help of the proc-notation to be transformed then to the @Processor@ computations that+-- are more general in nature and more easy-to-use but which computations created with help of+-- the proc-notation are not so efficient.+--+module Simulation.Aivika.Net+ (-- * Net Arrow+ Net(..),+ iterateNet,+ -- * Net Primitives+ emptyNet,+ arrNet,+ accumNet,+ -- * Specifying Identifier+ netUsingId,+ -- * Arrival Net+ arrivalNet,+ -- * Delaying Net+ delayNet,+ -- * Interchanging Nets with Processors+ netProcessor,+ processorNet) where++import qualified Control.Category as C+import Control.Arrow+import Control.Monad.Trans++import Data.IORef++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+import Simulation.Aivika.Resource+import Simulation.Aivika.Processor+import Simulation.Aivika.Ref+import Simulation.Aivika.Circuit+import Simulation.Aivika.Internal.Arrival++-- | Represents the net as an automaton working within the 'Process' computation.+newtype Net a b =+ Net { runNet :: a -> Process (b, Net a b)+ -- ^ Run the net.+ }++instance C.Category Net where++ id = Net $ \a -> return (a, C.id)++ (.) = dot+ where + (Net g) `dot` (Net f) =+ Net $ \a ->+ do (b, p1) <- f a+ (c, p2) <- g b+ return (c, p2 `dot` p1)++instance Arrow Net where++ arr f = Net $ \a -> return (f a, arr f)++ first (Net f) =+ Net $ \(b, d) ->+ do (c, p) <- f b+ return ((c, d), first p)++ second (Net f) =+ Net $ \(d, b) ->+ do (c, p) <- f b+ return ((d, c), second p)++ (Net f) *** (Net g) =+ Net $ \(b, b') ->+ do (c, p1) <- f b+ (c', p2) <- g b'+ return ((c, c'), p1 *** p2)+ + (Net f) &&& (Net g) =+ Net $ \b ->+ do (c, p1) <- f b+ (c', p2) <- g b+ return ((c, c'), p1 &&& p2)++instance ArrowChoice Net where++ left x@(Net f) =+ Net $ \ebd ->+ case ebd of+ Left b ->+ do (c, p) <- f b+ return (Left c, left p)+ Right d ->+ return (Right d, left x)++ right x@(Net f) =+ Net $ \edb ->+ case edb of+ Right b ->+ do (c, p) <- f b+ return (Right c, right p)+ Left d ->+ return (Left d, right x)++ x@(Net f) +++ y@(Net g) =+ Net $ \ebb' ->+ case ebb' of+ Left b ->+ do (c, p1) <- f b+ return (Left c, p1 +++ y)+ Right b' ->+ do (c', p2) <- g b'+ return (Right c', x +++ p2)++ x@(Net f) ||| y@(Net g) =+ Net $ \ebc ->+ case ebc of+ Left b ->+ do (d, p1) <- f b+ return (d, p1 ||| y)+ Right b' ->+ do (d, p2) <- g b'+ return (d, x ||| p2)++-- | A net that never finishes its work.+emptyNet :: Net a b+emptyNet = Net $ const neverProcess++-- | Create a simple net by the specified handling function+-- that runs the discontinuous process for each input value to get an output.+arrNet :: (a -> Process b) -> Net a b+arrNet f =+ let x =+ Net $ \a ->+ do b <- f a+ return (b, x)+ in x++-- | Accumulator that outputs a value determined by the supplied function.+accumNet :: (acc -> a -> Process (acc, b)) -> acc -> Net a b+accumNet f acc =+ Net $ \a ->+ do (acc', b) <- f acc a+ return (b, accumNet f acc') ++-- | Create a net 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.+netUsingId :: ProcessId -> Net a b -> Net a b+netUsingId pid (Net f) =+ Net $ processUsingId pid . f++-- | Transform the net to an equivalent processor (a rather cheap transformation).+netProcessor :: Net a b -> Processor a b+netProcessor = Processor . loop+ where loop x as =+ Cons $+ do (a, as') <- runStream as+ (b, x') <- runNet x a+ return (b, loop x' as')++-- | Transform the processor to a similar net (a more costly transformation).+processorNet :: Processor a b -> Net a b+processorNet x =+ Net $ \a ->+ do readingA <- liftSimulation $ newResourceWithMaxCount FCFS 0 (Just 1)+ writingA <- liftSimulation $ newResourceWithMaxCount FCFS 1 (Just 1)+ readingB <- liftSimulation $ newResourceWithMaxCount FCFS 0 (Just 1)+ writingB <- liftSimulation $ newResourceWithMaxCount FCFS 1 (Just 1)+ conting <- liftSimulation $ newResourceWithMaxCount FCFS 0 (Just 1)+ refA <- liftIO $ newIORef Nothing+ refB <- liftIO $ newIORef Nothing+ let input =+ do requestResource readingA+ Just a <- liftIO $ readIORef refA+ liftIO $ writeIORef refA Nothing+ releaseResource writingA+ return (a, Cons input)+ consume bs =+ do (b, bs') <- runStream bs+ requestResource writingB+ liftIO $ writeIORef refB (Just b)+ releaseResource readingB+ requestResource conting+ consume bs'+ loop a =+ do requestResource writingA+ liftIO $ writeIORef refA (Just a)+ releaseResource readingA+ requestResource readingB+ Just b <- liftIO $ readIORef refB+ liftIO $ writeIORef refB Nothing+ releaseResource writingB+ return (b, Net $ \a -> releaseResource conting >> loop a)+ spawnProcess $+ consume $ runProcessor x (Cons input)+ loop a++-- | A net that adds the information about the time points at which +-- the values were received.+arrivalNet :: Net a (Arrival a)+arrivalNet =+ let loop t0 =+ Net $ \a ->+ do t <- liftDynamics time+ let b = Arrival { arrivalValue = a,+ arrivalTime = t,+ arrivalDelay = + case t0 of+ Nothing -> Nothing+ Just t0 -> Just (t - t0) }+ return (b, loop $ Just t)+ in loop Nothing++-- | Delay the input by one step using the specified initial value.+delayNet :: a -> Net a a+delayNet a0 =+ Net $ \a ->+ return (a0, delayNet a)++-- | Iterate infinitely using the specified initial value.+iterateNet :: Net a a -> a -> Process ()+iterateNet (Net f) a =+ do (a', x) <- f a+ iterateNet x a'
Simulation/Aivika/Parameter.hs view
@@ -1,38 +1,38 @@--- | --- Module : Simulation.Aivika.Parameter --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.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. --- --- In general, this monad is very useful for representing a computation which is external --- relative to the model itself. --- -module Simulation.Aivika.Parameter - (-- * Parameter - Parameter, - ParameterLift(..), - runParameter, - runParameters, - -- * Error Handling - catchParameter, - finallyParameter, - throwParameter, - -- * Predefined Parameters - simulationIndex, - simulationCount, - simulationSpecs, - generatorParameter, - starttime, - stoptime, - dt, - -- * Memoization - memoParameter, - -- * Utilities - tableParameter) where - -import Simulation.Aivika.Internal.Parameter +-- |+-- Module : Simulation.Aivika.Parameter+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.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.+--+-- In general, this monad is very useful for representing a computation which is external+-- relative to the model itself.+-- +module Simulation.Aivika.Parameter+ (-- * Parameter+ Parameter,+ ParameterLift(..),+ runParameter,+ runParameters,+ -- * Error Handling+ catchParameter,+ finallyParameter,+ throwParameter,+ -- * Predefined Parameters+ simulationIndex,+ simulationCount,+ simulationSpecs,+ generatorParameter,+ starttime,+ stoptime,+ dt,+ -- * Memoization+ memoParameter,+ -- * Utilities+ tableParameter) where++import Simulation.Aivika.Internal.Parameter
Simulation/Aivika/Parameter/Random.hs view
@@ -1,123 +1,123 @@- --- | --- Module : Simulation.Aivika.Parameter.Random --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- This module defines the random parameters of simulation experiments. --- --- To create a parameter that would return the same value within the simulation run, --- you should memoize the computation with help of 'memoParameter', 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 'memo0Dynamics' --- function for that computation, or just take the predefined function that does --- namely this. - -module Simulation.Aivika.Parameter.Random - (randomUniform, - randomUniformInt, - randomNormal, - randomExponential, - randomErlang, - randomPoisson, - randomBinomial, - randomTrue, - randomFalse) where - -import System.Random - -import Control.Monad.Trans - -import Simulation.Aivika.Generator -import Simulation.Aivika.Internal.Specs -import Simulation.Aivika.Internal.Parameter -import Simulation.Aivika.Dynamics -import Simulation.Aivika.Dynamics.Memo.Unboxed - --- | Computation that generates a new random number distributed uniformly. -randomUniform :: Double -- ^ minimum - -> Double -- ^ maximum - -> Parameter Double -randomUniform min max = - Parameter $ \r -> - let g = runGenerator r - in generateUniform g min max - --- | Computation that generates a new random integer number distributed uniformly. -randomUniformInt :: Int -- ^ minimum - -> Int -- ^ maximum - -> Parameter Int -randomUniformInt min max = - Parameter $ \r -> - let g = runGenerator r - in generateUniformInt g min max - --- | Computation that generates a new random number distributed normally. -randomNormal :: Double -- ^ mean - -> Double -- ^ deviation - -> Parameter Double -randomNormal mu nu = - Parameter $ \r -> - let g = runGenerator r - in generateNormal g mu nu - --- | Computation that returns a new exponential random number with the specified mean --- (the reciprocal of the rate). -randomExponential :: Double - -- ^ the mean (the reciprocal of the rate) - -> Parameter Double -randomExponential mu = - Parameter $ \r -> - let g = runGenerator r - in generateExponential g mu - --- | Computation that returns a new Erlang random number with the specified scale --- (the reciprocal of the rate) and integer shape. -randomErlang :: Double - -- ^ the scale (the reciprocal of the rate) - -> Int - -- ^ the shape - -> Parameter Double -randomErlang beta m = - Parameter $ \r -> - let g = runGenerator r - in generateErlang g beta m - --- | Computation that returns a new Poisson random number with the specified mean. -randomPoisson :: Double - -- ^ the mean - -> Parameter Int -randomPoisson mu = - Parameter $ \r -> - let g = runGenerator r - in generatePoisson g mu - --- | Computation that returns a new binomial random number with the specified --- probability and trials. -randomBinomial :: Double -- ^ the probability - -> Int -- ^ the number of trials - -> Parameter Int -randomBinomial prob trials = - Parameter $ \r -> - let g = runGenerator r - in generateBinomial g prob trials - --- | Computation that returns 'True' in case of success. -randomTrue :: Double -- ^ the probability of the success - -> Parameter Bool -randomTrue p = - do x <- randomUniform 0 1 - return (x <= p) - --- | Computation that returns 'False' in case of success. -randomFalse :: Double -- ^ the probability of the success - -> Parameter Bool -randomFalse p = - do x <- randomUniform 0 1 - return (x > p) ++-- |+-- Module : Simulation.Aivika.Parameter.Random+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- This module defines the random parameters of simulation experiments.+--+-- To create a parameter that would return the same value within the simulation run,+-- you should memoize the computation with help of 'memoParameter', 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 'memo0Dynamics'+-- function for that computation, or just take the predefined function that does+-- namely this.++module Simulation.Aivika.Parameter.Random+ (randomUniform,+ randomUniformInt,+ randomNormal,+ randomExponential,+ randomErlang,+ randomPoisson,+ randomBinomial,+ randomTrue,+ randomFalse) where++import System.Random++import Control.Monad.Trans++import Simulation.Aivika.Generator+import Simulation.Aivika.Internal.Specs+import Simulation.Aivika.Internal.Parameter+import Simulation.Aivika.Dynamics+import Simulation.Aivika.Dynamics.Memo.Unboxed++-- | Computation that generates a new random number distributed uniformly.+randomUniform :: Double -- ^ minimum+ -> Double -- ^ maximum+ -> Parameter Double+randomUniform min max =+ Parameter $ \r ->+ let g = runGenerator r+ in generateUniform g min max++-- | Computation that generates a new random integer number distributed uniformly.+randomUniformInt :: Int -- ^ minimum+ -> Int -- ^ maximum+ -> Parameter Int+randomUniformInt min max =+ Parameter $ \r ->+ let g = runGenerator r+ in generateUniformInt g min max++-- | Computation that generates a new random number distributed normally.+randomNormal :: Double -- ^ mean+ -> Double -- ^ deviation+ -> Parameter Double+randomNormal mu nu =+ Parameter $ \r ->+ let g = runGenerator r+ in generateNormal g mu nu++-- | Computation that returns a new exponential random number with the specified mean+-- (the reciprocal of the rate).+randomExponential :: Double+ -- ^ the mean (the reciprocal of the rate)+ -> Parameter Double+randomExponential mu =+ Parameter $ \r ->+ let g = runGenerator r+ in generateExponential g mu++-- | Computation that returns a new Erlang random number with the specified scale+-- (the reciprocal of the rate) and integer shape.+randomErlang :: Double+ -- ^ the scale (the reciprocal of the rate)+ -> Int+ -- ^ the shape+ -> Parameter Double+randomErlang beta m =+ Parameter $ \r ->+ let g = runGenerator r+ in generateErlang g beta m++-- | Computation that returns a new Poisson random number with the specified mean.+randomPoisson :: Double+ -- ^ the mean+ -> Parameter Int+randomPoisson mu =+ Parameter $ \r ->+ let g = runGenerator r+ in generatePoisson g mu++-- | Computation that returns a new binomial random number with the specified+-- probability and trials.+randomBinomial :: Double -- ^ the probability+ -> Int -- ^ the number of trials+ -> Parameter Int+randomBinomial prob trials =+ Parameter $ \r ->+ let g = runGenerator r+ in generateBinomial g prob trials++-- | Computation that returns 'True' in case of success.+randomTrue :: Double -- ^ the probability of the success+ -> Parameter Bool+randomTrue p =+ do x <- randomUniform 0 1+ return (x <= p)++-- | Computation that returns 'False' in case of success.+randomFalse :: Double -- ^ the probability of the success+ -> Parameter Bool+randomFalse p =+ do x <- randomUniform 0 1+ return (x > p)
Simulation/Aivika/PriorityQueue.hs view
@@ -1,163 +1,163 @@- --- | --- Module : Simulation.Aivika.PriorityQueue --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- An imperative heap-based priority queue. --- -module Simulation.Aivika.PriorityQueue - (PriorityQueue, - queueNull, - queueCount, - newQueue, - enqueue, - dequeue, - queueFront) where - -import Data.Array -import Data.Array.MArray.Safe -import Data.Array.IO.Safe -import Data.IORef -import Control.Monad - --- | The 'PriorityQueue' type represents an imperative heap-based --- priority queue. -data PriorityQueue a = - PriorityQueue { pqKeys :: IORef (IOUArray Int Double), - pqVals :: IORef (IOArray Int a), - pqSize :: IORef Int } - -increase :: PriorityQueue a -> Int -> IO () -increase pq capacity = - do let keyRef = pqKeys pq - valRef = pqVals pq - keys <- readIORef keyRef - vals <- readIORef valRef - (il, iu) <- getBounds keys - let len = (iu - il) + 1 - capacity' | len < 64 = max capacity ((len + 1) * 2) - | otherwise = max capacity ((len `div` 2) * 3) - il' = il - iu' = il + capacity' - 1 - keys' <- newArray_ (il', iu') - vals' <- newArray_ (il', iu') - mapM_ (\i -> do { k <- readArray keys i; writeArray keys' i k }) [il..iu] - mapM_ (\i -> do { v <- readArray vals i; writeArray vals' i v }) [il..iu] - writeIORef keyRef keys' - writeIORef valRef vals' - -siftUp :: IOUArray Int Double - -> IOArray Int a - -> Int -> Double -> a - -> IO () -siftUp keys vals i k v = - if i == 0 - then do writeArray keys i k - writeArray vals i v - else do let n = (i - 1) `div` 2 - kn <- readArray keys n - if k >= kn - then do writeArray keys i k - writeArray vals i v - else do vn <- readArray vals n - writeArray keys i kn - writeArray vals i vn - siftUp keys vals n k v - -siftDown :: IOUArray Int Double - -> IOArray Int a -> Int - -> Int -> Double -> a - -> IO () -siftDown keys vals size i k v = - if i >= (size `div` 2) - then do writeArray keys i k - writeArray vals i v - else do let n = 2 * i + 1 - n' = n + 1 - kn <- readArray keys n - if n' >= size - then if k <= kn - then do writeArray keys i k - writeArray vals i v - else do vn <- readArray vals n - writeArray keys i kn - writeArray vals i vn - siftDown keys vals size n k v - else do kn' <- readArray keys n' - let n'' = if kn > kn' then n' else n - kn'' = min kn' kn - if k <= kn'' - then do writeArray keys i k - writeArray vals i v - else do vn'' <- readArray vals n'' - writeArray keys i kn'' - writeArray vals i vn'' - siftDown keys vals size n'' k v - --- | Test whether the priority queue is empty. -queueNull :: PriorityQueue a -> IO Bool -queueNull pq = - do size <- readIORef (pqSize pq) - return $ size == 0 - --- | Return the number of elements in the priority queue. -queueCount :: PriorityQueue a -> IO Int -queueCount pq = readIORef (pqSize pq) - --- | Create a new priority queue. -newQueue :: IO (PriorityQueue a) -newQueue = - do keys <- newArray_ (0, 10) - vals <- newArray_ (0, 10) - keyRef <- newIORef keys - valRef <- newIORef vals - sizeRef <- newIORef 0 - return PriorityQueue { pqKeys = keyRef, - pqVals = valRef, - pqSize = sizeRef } - --- | Enqueue a new element with the specified priority. -enqueue :: PriorityQueue a -> Double -> a -> IO () -enqueue pq k v = - do i <- readIORef (pqSize pq) - keys <- readIORef (pqKeys pq) - (il, iu) <- getBounds keys - when (i >= iu - il) $ increase pq (i + 2) -- plus one element on the end - writeIORef (pqSize pq) (i + 1) - keys <- readIORef (pqKeys pq) -- it can be another! (side-effect) - vals <- readIORef (pqVals pq) - siftUp keys vals i k v - --- | Dequeue the element with the minimal priority. -dequeue :: PriorityQueue a -> IO () -dequeue pq = - do size <- readIORef (pqSize pq) - when (size == 0) $ error "Empty priority queue: dequeue" - let i = size - 1 - writeIORef (pqSize pq) i - keys <- readIORef (pqKeys pq) - vals <- readIORef (pqVals pq) - k <- readArray keys i - v <- readArray vals i - let k0 = 0.0 - v0 = undefined - -- k0 <- readArray keys size - -- v0 <- readArray vals size - writeArray keys i k0 - writeArray vals i v0 - siftDown keys vals i 0 k v - --- | Return the element with the minimal priority. -queueFront :: PriorityQueue a -> IO (Double, a) -queueFront pq = - do size <- readIORef (pqSize pq) - when (size == 0) $ error "Empty priority queue: front" - keys <- readIORef (pqKeys pq) - vals <- readIORef (pqVals pq) - k <- readArray keys 0 - v <- readArray vals 0 - return (k, v) ++-- |+-- Module : Simulation.Aivika.PriorityQueue+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- An imperative heap-based priority queue.+--+module Simulation.Aivika.PriorityQueue + (PriorityQueue, + queueNull, + queueCount,+ newQueue, + enqueue, + dequeue, + queueFront) where ++import Data.Array+import Data.Array.MArray.Safe+import Data.Array.IO.Safe+import Data.IORef+import Control.Monad++-- | The 'PriorityQueue' type represents an imperative heap-based +-- priority queue.+data PriorityQueue a = + PriorityQueue { pqKeys :: IORef (IOUArray Int Double),+ pqVals :: IORef (IOArray Int a),+ pqSize :: IORef Int }++increase :: PriorityQueue a -> Int -> IO ()+increase pq capacity = + do let keyRef = pqKeys pq+ valRef = pqVals pq+ keys <- readIORef keyRef+ vals <- readIORef valRef+ (il, iu) <- getBounds keys+ let len = (iu - il) + 1+ capacity' | len < 64 = max capacity ((len + 1) * 2)+ | otherwise = max capacity ((len `div` 2) * 3)+ il' = il+ iu' = il + capacity' - 1+ keys' <- newArray_ (il', iu')+ vals' <- newArray_ (il', iu')+ mapM_ (\i -> do { k <- readArray keys i; writeArray keys' i k }) [il..iu]+ mapM_ (\i -> do { v <- readArray vals i; writeArray vals' i v }) [il..iu]+ writeIORef keyRef keys'+ writeIORef valRef vals'++siftUp :: IOUArray Int Double + -> IOArray Int a+ -> Int -> Double -> a + -> IO ()+siftUp keys vals i k v =+ if i == 0 + then do writeArray keys i k+ writeArray vals i v+ else do let n = (i - 1) `div` 2+ kn <- readArray keys n+ if k >= kn + then do writeArray keys i k+ writeArray vals i v+ else do vn <- readArray vals n+ writeArray keys i kn+ writeArray vals i vn+ siftUp keys vals n k v++siftDown :: IOUArray Int Double + -> IOArray Int a -> Int+ -> Int -> Double -> a + -> IO ()+siftDown keys vals size i k v =+ if i >= (size `div` 2)+ then do writeArray keys i k+ writeArray vals i v+ else do let n = 2 * i + 1+ n' = n + 1+ kn <- readArray keys n+ if n' >= size + then if k <= kn+ then do writeArray keys i k+ writeArray vals i v+ else do vn <- readArray vals n+ writeArray keys i kn+ writeArray vals i vn+ siftDown keys vals size n k v+ else do kn' <- readArray keys n'+ let n'' = if kn > kn' then n' else n+ kn'' = min kn' kn+ if k <= kn''+ then do writeArray keys i k+ writeArray vals i v+ else do vn'' <- readArray vals n''+ writeArray keys i kn''+ writeArray vals i vn''+ siftDown keys vals size n'' k v++-- | Test whether the priority queue is empty.+queueNull :: PriorityQueue a -> IO Bool+queueNull pq =+ do size <- readIORef (pqSize pq)+ return $ size == 0++-- | Return the number of elements in the priority queue.+queueCount :: PriorityQueue a -> IO Int+queueCount pq = readIORef (pqSize pq)++-- | Create a new priority queue.+newQueue :: IO (PriorityQueue a)+newQueue =+ do keys <- newArray_ (0, 10)+ vals <- newArray_ (0, 10)+ keyRef <- newIORef keys+ valRef <- newIORef vals+ sizeRef <- newIORef 0+ return PriorityQueue { pqKeys = keyRef, + pqVals = valRef, + pqSize = sizeRef }++-- | Enqueue a new element with the specified priority.+enqueue :: PriorityQueue a -> Double -> a -> IO ()+enqueue pq k v =+ do i <- readIORef (pqSize pq)+ keys <- readIORef (pqKeys pq)+ (il, iu) <- getBounds keys+ when (i >= iu - il) $ increase pq (i + 2) -- plus one element on the end+ writeIORef (pqSize pq) (i + 1)+ keys <- readIORef (pqKeys pq) -- it can be another! (side-effect)+ vals <- readIORef (pqVals pq)+ siftUp keys vals i k v++-- | Dequeue the element with the minimal priority.+dequeue :: PriorityQueue a -> IO ()+dequeue pq =+ do size <- readIORef (pqSize pq)+ when (size == 0) $ error "Empty priority queue: dequeue"+ let i = size - 1+ writeIORef (pqSize pq) i+ keys <- readIORef (pqKeys pq)+ vals <- readIORef (pqVals pq)+ k <- readArray keys i+ v <- readArray vals i+ let k0 = 0.0+ v0 = undefined+ -- k0 <- readArray keys size+ -- v0 <- readArray vals size+ writeArray keys i k0+ writeArray vals i v0+ siftDown keys vals i 0 k v++-- | Return the element with the minimal priority.+queueFront :: PriorityQueue a -> IO (Double, a)+queueFront pq =+ do size <- readIORef (pqSize pq)+ when (size == 0) $ error "Empty priority queue: front"+ keys <- readIORef (pqKeys pq)+ vals <- readIORef (pqVals pq)+ k <- readArray keys 0+ v <- readArray vals 0+ return (k, v)
Simulation/Aivika/Process.hs view
@@ -1,82 +1,84 @@- --- | --- Module : Simulation.Aivika.Process --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- A value in the 'Process' monad represents a discontinuous process that --- can suspend in any simulation time point and then resume later in the same --- or another time point. --- --- The process of this type can involve the 'Event', 'Dynamics' and 'Simulation' --- computations. Moreover, a value in the @Process@ monad can be run within --- the @Event@ computation. --- --- 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 - (-- * Process Monad - ProcessId, - Process, - ProcessLift(..), - -- * Running Process - runProcess, - runProcessUsingId, - runProcessInStartTime, - runProcessInStartTimeUsingId, - runProcessInStopTime, - runProcessInStopTimeUsingId, - -- * Spawning Processes - spawnProcess, - spawnProcessUsingId, - -- * Enqueueing Process - enqueueProcess, - enqueueProcessUsingId, - -- * Creating Process Identifier - newProcessId, - processId, - processUsingId, - -- * Holding, Interrupting, Passivating and Canceling Process - holdProcess, - interruptProcess, - processInterrupted, - passivateProcess, - processPassive, - reactivateProcess, - cancelProcessWithId, - cancelProcess, - processCancelled, - processCancelling, - whenCancellingProcess, - -- * Awaiting Signal - processAwait, - -- * Yield of Process - processYield, - -- * Process Timeout - timeoutProcess, - timeoutProcessUsingId, - -- * Parallelizing Processes - processParallel, - processParallelUsingIds, - processParallel_, - processParallelUsingIds_, - -- * Exception Handling - catchProcess, - finallyProcess, - throwProcess, - -- * Utilities - zipProcessParallel, - zip3ProcessParallel, - unzipProcess, - -- * Memoizing Process - memoProcess, - -- * Never Ending Process - neverProcess) where - -import Simulation.Aivika.Internal.Process ++-- |+-- Module : Simulation.Aivika.Process+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- A value in the 'Process' monad represents a discontinuous process that +-- can suspend in any simulation time point and then resume later in the same +-- or another time point. +-- +-- The process of this type can involve the 'Event', 'Dynamics' and 'Simulation'+-- computations. Moreover, a value in the @Process@ monad can be run within+-- the @Event@ computation.+--+-- 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+ (-- * Process Monad+ ProcessId,+ Process,+ ProcessLift(..),+ -- * Running Process+ runProcess,+ runProcessUsingId,+ runProcessInStartTime,+ runProcessInStartTimeUsingId,+ runProcessInStopTime,+ runProcessInStopTimeUsingId,+ -- * Spawning Processes+ spawnProcess,+ spawnProcessUsingId,+ spawnProcessWith,+ spawnProcessUsingIdWith,+ -- * Enqueueing Process+ enqueueProcess,+ enqueueProcessUsingId,+ -- * Creating Process Identifier+ newProcessId,+ processId,+ processUsingId,+ -- * Holding, Interrupting, Passivating and Canceling Process+ holdProcess,+ interruptProcess,+ processInterrupted,+ passivateProcess,+ processPassive,+ reactivateProcess,+ cancelProcessWithId,+ cancelProcess,+ processCancelled,+ processCancelling,+ whenCancellingProcess,+ -- * Awaiting Signal+ processAwait,+ -- * Yield of Process+ processYield,+ -- * Process Timeout+ timeoutProcess,+ timeoutProcessUsingId,+ -- * Parallelizing Processes+ processParallel,+ processParallelUsingIds,+ processParallel_,+ processParallelUsingIds_,+ -- * Exception Handling+ catchProcess,+ finallyProcess,+ throwProcess,+ -- * Utilities+ zipProcessParallel,+ zip3ProcessParallel,+ unzipProcess,+ -- * Memoizing Process+ memoProcess,+ -- * Never Ending Process+ neverProcess) where++import Simulation.Aivika.Internal.Process
Simulation/Aivika/Processor.hs view
@@ -1,458 +1,458 @@- --- | --- Module : Simulation.Aivika.Processor --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- The processor of simulation data. --- -module Simulation.Aivika.Processor - (-- * Processor Type - Processor(..), - -- * Processor Primitives - emptyProcessor, - arrProcessor, - accumProcessor, - -- * Specifying Identifier - processorUsingId, - -- * Prefetch and Delay Processors - prefetchProcessor, - delayProcessor, - -- * Buffer Processor - bufferProcessor, - bufferProcessorLoop, - -- * Processing Queues - queueProcessor, - queueProcessorLoopMerging, - queueProcessorLoopSeq, - queueProcessorLoopParallel, - -- * Sequencing Processors - processorSeq, - -- * Parallelizing Processors - processorParallel, - processorQueuedParallel, - processorPrioritisingOutputParallel, - processorPrioritisingInputParallel, - processorPrioritisingInputOutputParallel, - -- * Arrival Processor - arrivalProcessor, - -- * Integrating with Signals - signalProcessor, - processorSignaling) 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 -import Simulation.Aivika.Signal -import Simulation.Aivika.Internal.Arrival - --- | 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) - -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) - --- | A processor that never finishes its work producing an 'emptyStream'. -emptyProcessor :: Processor a b -emptyProcessor = Processor $ const emptyStream - --- | Create a simple processor by the specified handling function --- that runs the discontinuous process for each input value to get the output. -arrProcessor :: (a -> Process b) -> Processor a b -arrProcessor = Processor . mapStreamM - --- | Accumulator that outputs a value determined by the supplied function. -accumProcessor :: (acc -> a -> Process (acc, b)) -> acc -> Processor a b -accumProcessor f acc = - Processor $ \xs -> Cons $ loop xs acc where - loop xs acc = - do (a, xs') <- runStream xs - (acc', b) <- f acc a - return (b, Cons $ loop xs' acc') - --- | 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, - EnqueueStrategy so) - => 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 $ splitStreamQueueing 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, - PriorityQueueStrategy so 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 $ splitStreamQueueing 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 pi, - EnqueueStrategy so) - => 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 pi, - PriorityQueueStrategy so 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 - --- | Launches the processors sequentially using the 'prefetchProcessor' between them --- to model an autonomous work of each of the processors specified. -processorSeq :: [Processor a a] -> Processor a a -processorSeq [] = emptyProcessor -processorSeq [p] = p -processorSeq (p : ps) = p >>> prefetchProcessor >>> processorSeq ps - --- | 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 processed repeatedly. 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 e b) - -- ^ process and then decide what values of type @e@ - -- should be processed in the loop (this is a condition) - -> Processor e c - -- ^ process in the loop and then return a value - -- of type @c@ to the input again (this is a loop body) - -> Processor a b -bufferProcessorLoop consume preoutput cond body = - Processor $ \xs -> - Cons $ - do (reverted, output) <- - liftSimulation $ - partitionEitherStream $ - runProcessor cond preoutput - spawnProcess CancelTogether - (consume xs $ runProcessor body 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 were 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 with a loop when some items --- can be processed and then 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 were no hanging items - -> Process c - -- ^ dequeue an item for the further processing - -> Processor c (Either f b) - -- ^ process and then decide what values of type @f@ - -- should be processed in the loop (this is a condition) - -> Processor f d - -- ^ process in the loop and then return a value - -- of type @d@ to the queue again (this is a loop body) - -> 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 with a loop when --- some items can be processed and then 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 were no hanging items - -> Process c - -- ^ dequeue an item for the further processing - -> Processor c (Either e b) - -- ^ process and then decide what values of type @e@ - -- should be processed in the loop (this is a condition) - -> Processor e a - -- ^ process in the loop and then return a value - -- of type @a@ to the queue again (this is a loop body) - -> Processor a b - -- ^ the buffering processor -queueProcessorLoopSeq = - queueProcessorLoopMerging mergeStreams - --- | Like 'queueProcessorLoopMerging' creates a queue processor with a loop when --- some items can be processed and then 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 were no hanging items - -> Process c - -- ^ dequeue an item for the further processing - -> Processor c (Either e b) - -- ^ process and then decide what values of type @e@ - -- should be processed in the loop (this is a condition) - -> Processor e a - -- ^ process in the loop and then return a value - -- of type @a@ to the queue again (this is a loop body) - -> 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) - --- | This is a prefetch processor that requests for one more data item from --- the input in advance while the latest item is not yet fully processed in --- the chain of streams, usually by other processors. --- --- You can think of this as the prefetched processor could place its latest --- data item in some temporary space for later use, which is very useful --- for modeling a sequence of separate and independent work places. -prefetchProcessor :: Processor a a -prefetchProcessor = Processor prefetchStream - --- | Convert the specified signal transform to a processor. --- --- The processor may return data with delay as the values are requested by demand. --- Consider using the 'arrivalSignal' function to provide with the information --- about the time points at which the signal was actually triggered. --- --- The point is that the 'Stream' used in the 'Processor' is requested outside, --- while the 'Signal' is triggered inside. They are different by nature. --- The former is passive, while the latter is active. --- --- Cancel the processor's process to unsubscribe from the signals provided. -signalProcessor :: (Signal a -> Signal b) -> Processor a b -signalProcessor f = - Processor $ \xs -> - Cons $ - do sa <- streamSignal xs - sb <- signalStream (f sa) - runStream sb - --- | Convert the specified processor to a signal transform. --- --- The processor may return data with delay as the values are requested by demand. --- Consider using the 'arrivalSignal' function to provide with the information --- about the time points at which the signal was actually triggered. --- --- The point is that the 'Stream' used in the 'Processor' is requested outside, --- while the 'Signal' is triggered inside. They are different by nature. --- The former is passive, while the latter is active. --- --- Cancel the returned process to unsubscribe from the signal specified. -processorSignaling :: Processor a b -> Signal a -> Process (Signal b) -processorSignaling (Processor f) sa = - do xs <- signalStream sa - let ys = f xs - streamSignal ys - --- | A processor that adds the information about the time points at which --- the original stream items were received by demand. -arrivalProcessor :: Processor a (Arrival a) -arrivalProcessor = Processor arrivalStream - --- | A processor that delays the input stream by one step using the specified initial value. -delayProcessor :: a -> Processor a a -delayProcessor a0 = Processor $ delayStream a0 ++-- |+-- Module : Simulation.Aivika.Processor+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- The processor of simulation data.+--+module Simulation.Aivika.Processor+ (-- * Processor Type+ Processor(..),+ -- * Processor Primitives+ emptyProcessor,+ arrProcessor,+ accumProcessor,+ -- * Specifying Identifier+ processorUsingId,+ -- * Prefetch and Delay Processors+ prefetchProcessor,+ delayProcessor,+ -- * Buffer Processor+ bufferProcessor,+ bufferProcessorLoop,+ -- * Processing Queues+ queueProcessor,+ queueProcessorLoopMerging,+ queueProcessorLoopSeq,+ queueProcessorLoopParallel,+ -- * Sequencing Processors+ processorSeq,+ -- * Parallelizing Processors+ processorParallel,+ processorQueuedParallel,+ processorPrioritisingOutputParallel,+ processorPrioritisingInputParallel,+ processorPrioritisingInputOutputParallel,+ -- * Arrival Processor+ arrivalProcessor,+ -- * Integrating with Signals+ signalProcessor,+ processorSignaling) 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+import Simulation.Aivika.Signal+import Simulation.Aivika.Internal.Arrival++-- | 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)++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)++-- | A processor that never finishes its work producing an 'emptyStream'.+emptyProcessor :: Processor a b+emptyProcessor = Processor $ const emptyStream++-- | Create a simple processor by the specified handling function+-- that runs the discontinuous process for each input value to get the output.+arrProcessor :: (a -> Process b) -> Processor a b+arrProcessor = Processor . mapStreamM++-- | Accumulator that outputs a value determined by the supplied function.+accumProcessor :: (acc -> a -> Process (acc, b)) -> acc -> Processor a b+accumProcessor f acc =+ Processor $ \xs -> Cons $ loop xs acc where+ loop xs acc =+ do (a, xs') <- runStream xs+ (acc', b) <- f acc a+ return (b, Cons $ loop xs' acc') ++-- | 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,+ EnqueueStrategy so)+ => 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 $ splitStreamQueueing 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,+ PriorityQueueStrategy so 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 $ splitStreamQueueing 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 pi,+ EnqueueStrategy so)+ => 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 pi,+ PriorityQueueStrategy so 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++-- | Launches the processors sequentially using the 'prefetchProcessor' between them+-- to model an autonomous work of each of the processors specified.+processorSeq :: [Processor a a] -> Processor a a+processorSeq [] = emptyProcessor+processorSeq [p] = p+processorSeq (p : ps) = p >>> prefetchProcessor >>> processorSeq ps++-- | 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 (consume xs)+ runStream output++-- | Like 'bufferProcessor' but allows creating a loop when some items+-- can be processed repeatedly. 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 e b)+ -- ^ process and then decide what values of type @e@+ -- should be processed in the loop (this is a condition)+ -> Processor e c+ -- ^ process in the loop and then return a value+ -- of type @c@ to the input again (this is a loop body)+ -> Processor a b+bufferProcessorLoop consume preoutput cond body =+ Processor $ \xs ->+ Cons $+ do (reverted, output) <-+ liftSimulation $+ partitionEitherStream $+ runProcessor cond preoutput+ spawnProcess + (consume xs $ runProcessor body 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 were 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 with a loop when some items +-- can be processed and then 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 were no hanging items+ -> Process c+ -- ^ dequeue an item for the further processing+ -> Processor c (Either f b)+ -- ^ process and then decide what values of type @f@+ -- should be processed in the loop (this is a condition)+ -> Processor f d+ -- ^ process in the loop and then return a value+ -- of type @d@ to the queue again (this is a loop body)+ -> 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 with a loop when+-- some items can be processed and then 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 were no hanging items+ -> Process c+ -- ^ dequeue an item for the further processing+ -> Processor c (Either e b)+ -- ^ process and then decide what values of type @e@+ -- should be processed in the loop (this is a condition)+ -> Processor e a+ -- ^ process in the loop and then return a value+ -- of type @a@ to the queue again (this is a loop body)+ -> Processor a b+ -- ^ the buffering processor+queueProcessorLoopSeq =+ queueProcessorLoopMerging mergeStreams++-- | Like 'queueProcessorLoopMerging' creates a queue processor with a loop when+-- some items can be processed and then 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 were no hanging items+ -> Process c+ -- ^ dequeue an item for the further processing+ -> Processor c (Either e b)+ -- ^ process and then decide what values of type @e@+ -- should be processed in the loop (this is a condition)+ -> Processor e a+ -- ^ process in the loop and then return a value+ -- of type @a@ to the queue again (this is a loop body)+ -> Processor a b+ -- ^ the buffering processor+queueProcessorLoopParallel enqueue dequeue =+ bufferProcessorLoop+ (\bs cs ->+ do spawnProcess $+ consumeStream enqueue bs+ spawnProcess $+ consumeStream enqueue cs)+ (repeatProcess dequeue)++-- | This is a prefetch processor that requests for one more data item from +-- the input in advance while the latest item is not yet fully processed in +-- the chain of streams, usually by other processors.+--+-- You can think of this as the prefetched processor could place its latest +-- data item in some temporary space for later use, which is very useful +-- for modeling a sequence of separate and independent work places.+prefetchProcessor :: Processor a a+prefetchProcessor = Processor prefetchStream++-- | Convert the specified signal transform to a processor.+--+-- The processor may return data with delay as the values are requested by demand.+-- Consider using the 'arrivalSignal' function to provide with the information+-- about the time points at which the signal was actually triggered.+--+-- The point is that the 'Stream' used in the 'Processor' is requested outside, +-- while the 'Signal' is triggered inside. They are different by nature. +-- The former is passive, while the latter is active.+--+-- Cancel the processor's process to unsubscribe from the signals provided.+signalProcessor :: (Signal a -> Signal b) -> Processor a b+signalProcessor f =+ Processor $ \xs ->+ Cons $+ do sa <- streamSignal xs+ sb <- signalStream (f sa)+ runStream sb++-- | Convert the specified processor to a signal transform. +--+-- The processor may return data with delay as the values are requested by demand.+-- Consider using the 'arrivalSignal' function to provide with the information+-- about the time points at which the signal was actually triggered.+--+-- The point is that the 'Stream' used in the 'Processor' is requested outside, +-- while the 'Signal' is triggered inside. They are different by nature.+-- The former is passive, while the latter is active.+--+-- Cancel the returned process to unsubscribe from the signal specified.+processorSignaling :: Processor a b -> Signal a -> Process (Signal b)+processorSignaling (Processor f) sa =+ do xs <- signalStream sa+ let ys = f xs+ streamSignal ys++-- | A processor that adds the information about the time points at which +-- the original stream items were received by demand.+arrivalProcessor :: Processor a (Arrival a)+arrivalProcessor = Processor arrivalStream++-- | A processor that delays the input stream by one step using the specified initial value.+delayProcessor :: a -> Processor a a+delayProcessor a0 = Processor $ delayStream a0
Simulation/Aivika/Processor/RoundRobbin.hs view
@@ -1,58 +1,58 @@- --- | --- Module : Simulation.Aivika.Processor.RoundRobbin --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.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 <- liftEvent 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 ++-- |+-- Module : Simulation.Aivika.Processor.RoundRobbin+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.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 <- liftEvent 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
@@ -1,1097 +1,1097 @@- --- | --- Module : Simulation.Aivika.Queue --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- 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 4 x 4 = 64 different types of the queue, each of them will have its own --- behaviour. --- -module Simulation.Aivika.Queue - (-- * Queue Types - FCFSQueue, - LCFSQueue, - SIROQueue, - PriorityQueue, - Queue, - -- * Creating Queue - newFCFSQueue, - newLCFSQueue, - newSIROQueue, - newPriorityQueue, - newQueue, - -- * Queue Properties and Activities - enqueueStrategy, - enqueueStoringStrategy, - dequeueStrategy, - queueNull, - queueFull, - queueMaxCount, - queueCount, - queueCountStats, - enqueueCount, - enqueueLostCount, - enqueueStoreCount, - dequeueCount, - dequeueExtractCount, - queueLoadFactor, - enqueueRate, - enqueueStoreRate, - dequeueRate, - dequeueExtractRate, - queueWaitTime, - queueTotalWaitTime, - enqueueWaitTime, - dequeueWaitTime, - queueRate, - -- * Dequeuing and Enqueuing - dequeue, - dequeueWithOutputPriority, - tryDequeue, - enqueue, - enqueueWithInputPriority, - enqueueWithStoringPriority, - enqueueWithInputStoringPriorities, - tryEnqueue, - tryEnqueueWithStoringPriority, - enqueueOrLost, - enqueueOrLost_, - enqueueWithStoringPriorityOrLost, - enqueueWithStoringPriorityOrLost_, - -- * Awaiting - waitWhileFullQueue, - -- * Summary - queueSummary, - -- * Derived Signals for Properties - queueNullChanged, - queueNullChanged_, - queueFullChanged, - queueFullChanged_, - queueCountChanged, - queueCountChanged_, - enqueueCountChanged, - enqueueCountChanged_, - enqueueLostCountChanged, - enqueueLostCountChanged_, - enqueueStoreCountChanged, - enqueueStoreCountChanged_, - dequeueCountChanged, - dequeueCountChanged_, - dequeueExtractCountChanged, - dequeueExtractCountChanged_, - queueLoadFactorChanged, - queueLoadFactorChanged_, - queueWaitTimeChanged, - queueWaitTimeChanged_, - queueTotalWaitTimeChanged, - queueTotalWaitTimeChanged_, - enqueueWaitTimeChanged, - enqueueWaitTimeChanged_, - dequeueWaitTimeChanged, - dequeueWaitTimeChanged_, - queueRateChanged, - queueRateChanged_, - -- * 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 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 FCFS FCFS a - --- | A type synonym for the ordinary LIFO queue also known as the LCFS --- (Last Come - First Serviced) queue. -type LCFSQueue a = Queue FCFS LCFS FCFS a - --- | A type synonym for the SIRO (Serviced in Random Order) queue. -type SIROQueue a = Queue FCFS SIRO FCFS a - --- | A type synonym for the queue with static priorities applied when --- storing the elements in the queue. -type PriorityQueue a = Queue FCFS StaticPriorities FCFS a - --- | Represents a queue using the specified strategies for enqueueing (input), @si@, --- internal storing (in memory), @sm@, and dequeueing (output), @so@, where @a@ denotes --- the type of items stored in the queue. -data Queue si sm so a = - Queue { queueMaxCount :: Int, - -- ^ The queue capacity. - enqueueStrategy :: si, - -- ^ The strategy applied to the enqueueing (input) processes when the queue is full. - enqueueStoringStrategy :: sm, - -- ^ The strategy applied when storing (in memory) items in the queue. - dequeueStrategy :: so, - -- ^ The strategy applied to the dequeueing (output) processes when the queue is empty. - enqueueRes :: Resource si, - queueStore :: StrategyQueue sm (QueueItem a), - dequeueRes :: Resource so, - queueCountRef :: IORef Int, - queueCountStatsRef :: IORef (TimingStats Int), - enqueueCountRef :: IORef Int, - enqueueLostCountRef :: IORef Int, - enqueueStoreCountRef :: IORef Int, - dequeueCountRef :: IORef Int, - dequeueExtractCountRef :: IORef Int, - queueWaitTimeRef :: IORef (SamplingStats Double), - queueTotalWaitTimeRef :: IORef (SamplingStats Double), - enqueueWaitTimeRef :: IORef (SamplingStats Double), - dequeueWaitTimeRef :: 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 FCFS queue with the specified capacity. -newFCFSQueue :: Int -> Event (FCFSQueue a) -newFCFSQueue = newQueue FCFS FCFS FCFS - --- | Create a new LCFS queue with the specified capacity. -newLCFSQueue :: Int -> Event (LCFSQueue a) -newLCFSQueue = newQueue FCFS LCFS FCFS - --- | Create a new SIRO queue with the specified capacity. -newSIROQueue :: Int -> Event (SIROQueue a) -newSIROQueue = newQueue FCFS SIRO FCFS - --- | Create a new priority queue with the specified capacity. -newPriorityQueue :: Int -> Event (PriorityQueue a) -newPriorityQueue = newQueue FCFS StaticPriorities FCFS - --- | Create a new queue with the specified strategies and capacity. -newQueue :: (QueueStrategy si, - QueueStrategy sm, - QueueStrategy so) => - si - -- ^ the strategy applied to the enqueueing (input) processes when the queue is full - -> sm - -- ^ the strategy applied when storing items in the queue - -> so - -- ^ the strategy applied to the dequeueing (output) processes when the queue is empty - -> Int - -- ^ the queue capacity - -> Event (Queue si sm so a) -newQueue si sm so count = - do t <- liftDynamics time - i <- liftIO $ newIORef 0 - is <- liftIO $ newIORef $ returnTimingStats t 0 - ci <- liftIO $ newIORef 0 - cl <- liftIO $ newIORef 0 - cm <- liftIO $ newIORef 0 - cr <- liftIO $ newIORef 0 - co <- liftIO $ newIORef 0 - ri <- liftSimulation $ newResourceWithMaxCount si count (Just count) - qm <- liftSimulation $ newStrategyQueue sm - ro <- liftSimulation $ newResourceWithMaxCount so 0 (Just count) - w <- liftIO $ newIORef mempty - wt <- liftIO $ newIORef mempty - wi <- liftIO $ newIORef mempty - wo <- liftIO $ newIORef mempty - s1 <- liftSimulation $ newSignalSource - s2 <- liftSimulation $ newSignalSource - s3 <- liftSimulation $ newSignalSource - s4 <- liftSimulation $ newSignalSource - s5 <- liftSimulation $ newSignalSource - return Queue { queueMaxCount = count, - enqueueStrategy = si, - enqueueStoringStrategy = sm, - dequeueStrategy = so, - enqueueRes = ri, - queueStore = qm, - dequeueRes = ro, - queueCountRef = i, - queueCountStatsRef = is, - enqueueCountRef = ci, - enqueueLostCountRef = cl, - enqueueStoreCountRef = cm, - dequeueCountRef = cr, - dequeueExtractCountRef = co, - queueWaitTimeRef = w, - queueTotalWaitTimeRef = wt, - enqueueWaitTimeRef = wi, - dequeueWaitTimeRef = 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 sm so 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 sm so a -> Signal Bool -queueNullChanged q = - mapSignalM (const $ queueNull q) (queueNullChanged_ q) - --- | Signal when the 'queueNull' property value has changed. -queueNullChanged_ :: Queue si sm so a -> Signal () -queueNullChanged_ = queueCountChanged_ - --- | Test whether the queue is full. --- --- See also 'queueFullChanged' and 'queueFullChanged_'. -queueFull :: Queue si sm so 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 sm so a -> Signal Bool -queueFullChanged q = - mapSignalM (const $ queueFull q) (queueFullChanged_ q) - --- | Signal when the 'queueFull' property value has changed. -queueFullChanged_ :: Queue si sm so a -> Signal () -queueFullChanged_ = queueCountChanged_ - --- | Return the current queue size. --- --- See also 'queueCountStats', 'queueCountChanged' and 'queueCountChanged_'. -queueCount :: Queue si sm so a -> Event Int -queueCount q = - Event $ \p -> readIORef (queueCountRef q) - --- | Return the queue size statistics. -queueCountStats :: Queue si sm so a -> Event (TimingStats Int) -queueCountStats q = - Event $ \p -> readIORef (queueCountStatsRef q) - --- | Signal when the 'queueCount' property value has changed. -queueCountChanged :: Queue si sm so a -> Signal Int -queueCountChanged q = - mapSignalM (const $ queueCount q) (queueCountChanged_ q) - --- | Signal when the 'queueCount' property value has changed. -queueCountChanged_ :: Queue si sm so a -> Signal () -queueCountChanged_ q = - mapSignal (const ()) (enqueueStored q) <> - mapSignal (const ()) (dequeueExtracted q) - --- | Return the total number of input items that were enqueued. --- --- See also 'enqueueCountChanged' and 'enqueueCountChanged_'. -enqueueCount :: Queue si sm so a -> Event Int -enqueueCount q = - Event $ \p -> readIORef (enqueueCountRef q) - --- | Signal when the 'enqueueCount' property value has changed. -enqueueCountChanged :: Queue si sm so a -> Signal Int -enqueueCountChanged q = - mapSignalM (const $ enqueueCount q) (enqueueCountChanged_ q) - --- | Signal when the 'enqueueCount' property value has changed. -enqueueCountChanged_ :: Queue si sm so a -> Signal () -enqueueCountChanged_ q = - mapSignal (const ()) (enqueueInitiated q) - --- | Return the number of lost items. --- --- See also 'enqueueLostCountChanged' and 'enqueueLostCountChanged_'. -enqueueLostCount :: Queue si sm so a -> Event Int -enqueueLostCount q = - Event $ \p -> readIORef (enqueueLostCountRef q) - --- | Signal when the 'enqueueLostCount' property value has changed. -enqueueLostCountChanged :: Queue si sm so a -> Signal Int -enqueueLostCountChanged q = - mapSignalM (const $ enqueueLostCount q) (enqueueLostCountChanged_ q) - --- | Signal when the 'enqueueLostCount' property value has changed. -enqueueLostCountChanged_ :: Queue si sm so a -> Signal () -enqueueLostCountChanged_ q = - mapSignal (const ()) (enqueueLost q) - --- | Return the total number of input items that were stored. --- --- See also 'enqueueStoreCountChanged' and 'enqueueStoreCountChanged_'. -enqueueStoreCount :: Queue si sm so a -> Event Int -enqueueStoreCount q = - Event $ \p -> readIORef (enqueueStoreCountRef q) - --- | Signal when the 'enqueueStoreCount' property value has changed. -enqueueStoreCountChanged :: Queue si sm so a -> Signal Int -enqueueStoreCountChanged q = - mapSignalM (const $ enqueueStoreCount q) (enqueueStoreCountChanged_ q) - --- | Signal when the 'enqueueStoreCount' property value has changed. -enqueueStoreCountChanged_ :: Queue si sm so a -> Signal () -enqueueStoreCountChanged_ q = - mapSignal (const ()) (enqueueStored q) - --- | Return the total number of requests for dequeueing the items, --- not taking into account the failed attempts to dequeue immediately --- without suspension. --- --- See also 'dequeueCountChanged' and 'dequeueCountChanged_'. -dequeueCount :: Queue si sm so a -> Event Int -dequeueCount q = - Event $ \p -> readIORef (dequeueCountRef q) - --- | Signal when the 'dequeueCount' property value has changed. -dequeueCountChanged :: Queue si sm so a -> Signal Int -dequeueCountChanged q = - mapSignalM (const $ dequeueCount q) (dequeueCountChanged_ q) - --- | Signal when the 'dequeueCount' property value has changed. -dequeueCountChanged_ :: Queue si sm so a -> Signal () -dequeueCountChanged_ q = - mapSignal (const ()) (dequeueRequested q) - --- | Return the total number of output items that were actually dequeued. --- --- See also 'dequeueExtractCountChanged' and 'dequeueExtractCountChanged_'. -dequeueExtractCount :: Queue si sm so a -> Event Int -dequeueExtractCount q = - Event $ \p -> readIORef (dequeueExtractCountRef q) - --- | Signal when the 'dequeueExtractCount' property value has changed. -dequeueExtractCountChanged :: Queue si sm so a -> Signal Int -dequeueExtractCountChanged q = - mapSignalM (const $ dequeueExtractCount q) (dequeueExtractCountChanged_ q) - --- | Signal when the 'dequeueExtractCount' property value has changed. -dequeueExtractCountChanged_ :: Queue si sm so a -> Signal () -dequeueExtractCountChanged_ 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 sm so 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 sm so a -> Signal Double -queueLoadFactorChanged q = - mapSignalM (const $ queueLoadFactor q) (queueLoadFactorChanged_ q) - --- | Signal when the 'queueLoadFactor' property value has changed. -queueLoadFactorChanged_ :: Queue si sm so 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. -enqueueRate :: Queue si sm so a -> Event Double -enqueueRate q = - Event $ \p -> - do x <- readIORef (enqueueCountRef 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. -enqueueStoreRate :: Queue si sm so a -> Event Double -enqueueStoreRate q = - Event $ \p -> - do x <- readIORef (enqueueStoreCountRef 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 failed attempts to dequeue immediately --- without suspension. -dequeueRate :: Queue si sm so a -> Event Double -dequeueRate q = - Event $ \p -> - do x <- readIORef (dequeueCountRef q) - let t0 = spcStartTime $ pointSpecs p - t = pointTime p - return (fromIntegral x / (t - t0)) - --- | Return the rate of the output items that were actually dequeued: how many items --- per time. -dequeueExtractRate :: Queue si sm so a -> Event Double -dequeueExtractRate q = - Event $ \p -> - do x <- readIORef (dequeueExtractCountRef 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 sm so a -> Event (SamplingStats Double) -queueWaitTime q = - Event $ \p -> readIORef (queueWaitTimeRef q) - --- | Signal when the 'queueWaitTime' property value has changed. -queueWaitTimeChanged :: Queue si sm so a -> Signal (SamplingStats Double) -queueWaitTimeChanged q = - mapSignalM (const $ queueWaitTime q) (queueWaitTimeChanged_ q) - --- | Signal when the 'queueWaitTime' property value has changed. -queueWaitTimeChanged_ :: Queue si sm so 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 sm so a -> Event (SamplingStats Double) -queueTotalWaitTime q = - Event $ \p -> readIORef (queueTotalWaitTimeRef q) - --- | Signal when the 'queueTotalWaitTime' property value has changed. -queueTotalWaitTimeChanged :: Queue si sm so a -> Signal (SamplingStats Double) -queueTotalWaitTimeChanged q = - mapSignalM (const $ queueTotalWaitTime q) (queueTotalWaitTimeChanged_ q) - --- | Signal when the 'queueTotalWaitTime' property value has changed. -queueTotalWaitTimeChanged_ :: Queue si sm so a -> Signal () -queueTotalWaitTimeChanged_ q = - mapSignal (const ()) (dequeueExtracted q) - --- | Return the enqueue 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 'enqueueWaitTimeChanged' and 'enqueueWaitTimeChanged_'. -enqueueWaitTime :: Queue si sm so a -> Event (SamplingStats Double) -enqueueWaitTime q = - Event $ \p -> readIORef (enqueueWaitTimeRef q) - --- | Signal when the 'enqueueWaitTime' property value has changed. -enqueueWaitTimeChanged :: Queue si sm so a -> Signal (SamplingStats Double) -enqueueWaitTimeChanged q = - mapSignalM (const $ enqueueWaitTime q) (enqueueWaitTimeChanged_ q) - --- | Signal when the 'enqueueWaitTime' property value has changed. -enqueueWaitTimeChanged_ :: Queue si sm so a -> Signal () -enqueueWaitTimeChanged_ q = - mapSignal (const ()) (enqueueStored q) - --- | Return the dequeue wait time from the time at which the item was requested --- for dequeueing to the time at which it was actually dequeued. --- --- See also 'dequeueWaitTimeChanged' and 'dequeueWaitTimeChanged_'. -dequeueWaitTime :: Queue si sm so a -> Event (SamplingStats Double) -dequeueWaitTime q = - Event $ \p -> readIORef (dequeueWaitTimeRef q) - --- | Signal when the 'dequeueWaitTime' property value has changed. -dequeueWaitTimeChanged :: Queue si sm so a -> Signal (SamplingStats Double) -dequeueWaitTimeChanged q = - mapSignalM (const $ dequeueWaitTime q) (dequeueWaitTimeChanged_ q) - --- | Signal when the 'dequeueWaitTime' property value has changed. -dequeueWaitTimeChanged_ :: Queue si sm so a -> Signal () -dequeueWaitTimeChanged_ q = - mapSignal (const ()) (dequeueExtracted q) - --- | Return a long-term average queue rate calculated as --- the average queue size divided by the average wait time. --- --- This value may be less than the actual arrival rate as the queue is --- finite and new arrivals may be locked while the queue remains full. --- --- See also 'queueRateChanged' and 'queueRateChanged_'. -queueRate :: Queue si sm so a -> Event Double -queueRate q = - Event $ \p -> - do x <- readIORef (queueCountStatsRef q) - y <- readIORef (queueWaitTimeRef q) - return (timingStatsMean x / samplingStatsMean y) - --- | Signal when the 'queueRate' property value has changed. -queueRateChanged :: Queue si sm so a -> Signal Double -queueRateChanged q = - mapSignalM (const $ queueRate q) (queueRateChanged_ q) - --- | Signal when the 'queueRate' property value has changed. -queueRateChanged_ :: Queue si sm so a -> Signal () -queueRateChanged_ q = - mapSignal (const ()) (enqueueStored q) <> - mapSignal (const ()) (dequeueExtracted q) - --- | Dequeue suspending the process if the queue is empty. -dequeue :: (DequeueStrategy si, - DequeueStrategy sm, - EnqueueStrategy so) - => Queue si sm so a - -- ^ the queue - -> Process a - -- ^ the dequeued value -dequeue q = - do t <- liftEvent $ dequeueRequest q - requestResource (dequeueRes q) - liftEvent $ dequeueExtract q t - --- | Dequeue with the output priority suspending the process if the queue is empty. -dequeueWithOutputPriority :: (DequeueStrategy si, - DequeueStrategy sm, - PriorityQueueStrategy so po) - => Queue si sm so a - -- ^ the queue - -> po - -- ^ the priority for output - -> Process a - -- ^ the dequeued value -dequeueWithOutputPriority q po = - do t <- liftEvent $ dequeueRequest q - requestResourceWithPriority (dequeueRes q) po - liftEvent $ dequeueExtract q t - --- | Try to dequeue immediately. -tryDequeue :: (DequeueStrategy si, - DequeueStrategy sm) - => Queue si sm so a - -- ^ the queue - -> Event (Maybe a) - -- ^ the dequeued value of 'Nothing' -tryDequeue q = - do x <- tryRequestResourceWithinEvent (dequeueRes q) - if x - then do t <- dequeueRequest q - fmap Just $ dequeueExtract q t - else return Nothing - --- | Enqueue the item suspending the process if the queue is full. -enqueue :: (EnqueueStrategy si, - EnqueueStrategy sm, - DequeueStrategy so) - => Queue si sm so a - -- ^ the queue - -> a - -- ^ the item to enqueue - -> Process () -enqueue q a = - do i <- liftEvent $ enqueueInitiate q a - requestResource (enqueueRes q) - liftEvent $ enqueueStore q i - --- | Enqueue with the input priority the item suspending the process if the queue is full. -enqueueWithInputPriority :: (PriorityQueueStrategy si pi, - EnqueueStrategy sm, - DequeueStrategy so) - => Queue si sm so 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 (enqueueRes q) pi - liftEvent $ enqueueStore q i - --- | Enqueue with the storing priority the item suspending the process if the queue is full. -enqueueWithStoringPriority :: (EnqueueStrategy si, - PriorityQueueStrategy sm pm, - DequeueStrategy so) - => Queue si sm so a - -- ^ the queue - -> pm - -- ^ the priority for storing - -> a - -- ^ the item to enqueue - -> Process () -enqueueWithStoringPriority q pm a = - do i <- liftEvent $ enqueueInitiate q a - requestResource (enqueueRes 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 pi, - PriorityQueueStrategy sm pm, - DequeueStrategy so) - => Queue si sm so 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 (enqueueRes 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, - DequeueStrategy so) - => Queue si sm so a - -- ^ the queue - -> a - -- ^ the item which we try to enqueue - -> Event Bool -tryEnqueue q a = - do x <- tryRequestResourceWithinEvent (enqueueRes q) - if x - 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 pm, - DequeueStrategy so) - => Queue si sm so 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 (enqueueRes 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, - DequeueStrategy so) - => Queue si sm so a - -- ^ the queue - -> a - -- ^ the item which we try to enqueue - -> Event Bool -enqueueOrLost q a = - do x <- tryRequestResourceWithinEvent (enqueueRes q) - if x - then do enqueueInitiate q a >>= enqueueStore q - return True - 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 pm, - DequeueStrategy so) - => Queue si sm so 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 (enqueueRes 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, - DequeueStrategy so) - => Queue si sm so a - -- ^ the queue - -> a - -- ^ the item which we try to enqueue - -> Event () -enqueueOrLost_ q a = - do x <- enqueueOrLost q a - return () - --- | Try to enqueue with the storing priority the item. If the queue is full --- then the item will be lost. -enqueueWithStoringPriorityOrLost_ :: (PriorityQueueStrategy sm pm, - DequeueStrategy so) - => Queue si sm so 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 sm so 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 sm so 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', '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 sm so a -> Signal a -enqueueLost q = publishSignal (enqueueLostSource q) - --- | Return a signal that notifies when the dequeuing operation was requested. -dequeueRequested :: Queue si sm so 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 sm so a -> Signal a -dequeueExtracted q = publishSignal (dequeueExtractedSource q) - --- | Initiate the process of enqueuing the item. -enqueueInitiate :: Queue si sm so a - -- ^ the queue - -> a - -- ^ the item to be enqueued - -> Event (QueueItem a) -enqueueInitiate q a = - Event $ \p -> - do let t = pointTime p - modifyIORef' (enqueueCountRef 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, - DequeueStrategy so) - => Queue si sm so 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 (queueStore q) i' - c <- readIORef (queueCountRef q) - let c' = c + 1 - t = pointTime p - c' `seq` writeIORef (queueCountRef q) c' - modifyIORef' (queueCountStatsRef q) (addTimingStats t c') - modifyIORef' (enqueueStoreCountRef q) (+ 1) - invokeEvent p $ - enqueueStat q i' - invokeEvent p $ - releaseResourceWithinEvent (dequeueRes q) - invokeEvent p $ - triggerSignal (enqueueStoredSource q) (itemValue i') - --- | Store with the priority the item. -enqueueStoreWithPriority :: (PriorityQueueStrategy sm pm, - DequeueStrategy so) - => Queue si sm so 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 (queueStore q) pm i' - c <- readIORef (queueCountRef q) - let c' = c + 1 - t = pointTime p - c' `seq` writeIORef (queueCountRef q) c' - modifyIORef' (queueCountStatsRef q) (addTimingStats t c') - modifyIORef' (enqueueStoreCountRef q) (+ 1) - invokeEvent p $ - enqueueStat q i' - invokeEvent p $ - releaseResourceWithinEvent (dequeueRes q) - invokeEvent p $ - triggerSignal (enqueueStoredSource q) (itemValue i') - --- | Deny the enqueuing. -enqueueDeny :: Queue si sm so a - -- ^ the queue - -> a - -- ^ the item to be denied - -> Event () -enqueueDeny q a = - Event $ \p -> - do modifyIORef' (enqueueLostCountRef q) $ (+) 1 - invokeEvent p $ - triggerSignal (enqueueLostSource q) a - --- | Update the statistics for the input wait time of the enqueuing operation. -enqueueStat :: Queue si sm so 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' (enqueueWaitTimeRef q) $ - addSamplingStats (t1 - t0) - --- | Accept the dequeuing request and return the current simulation time. -dequeueRequest :: Queue si sm so a - -- ^ the queue - -> Event Double - -- ^ the current time -dequeueRequest q = - Event $ \p -> - do modifyIORef' (dequeueCountRef q) (+ 1) - invokeEvent p $ - triggerSignal (dequeueRequestedSource q) () - return $ pointTime p - --- | Extract an item for the dequeuing request. -dequeueExtract :: (DequeueStrategy si, - DequeueStrategy sm) - => Queue si sm so 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 (queueStore q) - c <- readIORef (queueCountRef q) - let c' = c - 1 - t = pointTime p - c' `seq` writeIORef (queueCountRef q) c' - modifyIORef' (queueCountStatsRef q) (addTimingStats t c') - modifyIORef' (dequeueExtractCountRef q) (+ 1) - invokeEvent p $ - dequeueStat q t' i - invokeEvent p $ - releaseResourceWithinEvent (enqueueRes 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 sm so 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' (dequeueWaitTimeRef 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 sm so 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 sm so 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 sm so a -> Int -> Event ShowS -queueSummary q indent = - do let si = enqueueStrategy q - sm = enqueueStoringStrategy q - so = dequeueStrategy q - null <- queueNull q - full <- queueFull q - let maxCount = queueMaxCount q - count <- queueCount q - countStats <- queueCountStats q - enqueueCount <- enqueueCount q - enqueueLostCount <- enqueueLostCount q - enqueueStoreCount <- enqueueStoreCount q - dequeueCount <- dequeueCount q - dequeueExtractCount <- dequeueExtractCount q - loadFactor <- queueLoadFactor q - enqueueRate <- enqueueRate q - enqueueStoreRate <- enqueueStoreRate q - dequeueRate <- dequeueRate q - dequeueExtractRate <- dequeueExtractRate q - waitTime <- queueWaitTime q - totalWaitTime <- queueTotalWaitTime q - enqueueWaitTime <- enqueueWaitTime q - dequeueWaitTime <- dequeueWaitTime q - let tab = replicate indent ' ' - return $ - showString tab . - showString "the enqueueing (input) strategy = " . - shows si . - showString "\n" . - showString tab . - showString "the storing (memory) strategy = " . - shows sm . - showString "\n" . - showString tab . - showString "the dequeueing (output) 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 size statistics = \n\n" . - timingStatsSummary countStats (2 + indent) . - showString "\n\n" . - showString tab . - showString "the enqueue count (number of the input items that were enqueued) = " . - shows enqueueCount . - showString "\n" . - showString tab . - showString "the enqueue lost count (number of the lost items) = " . - shows enqueueLostCount . - showString "\n" . - showString tab . - showString "the enqueue store count (number of the input items that were stored) = " . - shows enqueueStoreCount . - showString "\n" . - showString tab . - showString "the dequeue count (number of requests for dequeueing an item) = " . - shows dequeueCount . - showString "\n" . - showString tab . - showString "the dequeue extract count (number of the output items that were dequeued) = " . - shows dequeueExtractCount . - showString "\n" . - showString tab . - showString "the load factor (size / max. capacity) = " . - shows loadFactor . - showString "\n" . - showString tab . - showString "the enqueue rate (how many input items were enqueued per time) = " . - shows enqueueRate . - showString "\n" . - showString tab . - showString "the enqueue store rate (how many input items were stored per time) = " . - shows enqueueStoreRate . - showString "\n" . - showString tab . - showString "the dequeue rate (how many requests for dequeueing per time) = " . - shows dequeueRate . - showString "\n" . - showString tab . - showString "the dequeue extract rate (how many output items were dequeued per time) = " . - shows dequeueExtractRate . - 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 enqueue wait time (when the enqueueing was initiated -> when was stored) = \n\n" . - samplingStatsSummary enqueueWaitTime (2 + indent) . - showString "\n\n" . - showString tab . - showString "the dequeue wait time (when was requested for dequeueing -> when was dequeued) = \n\n" . - samplingStatsSummary dequeueWaitTime (2 + indent) ++-- |+-- Module : Simulation.Aivika.Queue+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- 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 4 x 4 = 64 different types of the queue, each of them will have its own+-- behaviour.+--+module Simulation.Aivika.Queue+ (-- * Queue Types+ FCFSQueue,+ LCFSQueue,+ SIROQueue,+ PriorityQueue,+ Queue,+ -- * Creating Queue+ newFCFSQueue,+ newLCFSQueue,+ newSIROQueue,+ newPriorityQueue,+ newQueue,+ -- * Queue Properties and Activities+ enqueueStrategy,+ enqueueStoringStrategy,+ dequeueStrategy,+ queueNull,+ queueFull,+ queueMaxCount,+ queueCount,+ queueCountStats,+ enqueueCount,+ enqueueLostCount,+ enqueueStoreCount,+ dequeueCount,+ dequeueExtractCount,+ queueLoadFactor,+ enqueueRate,+ enqueueStoreRate,+ dequeueRate,+ dequeueExtractRate,+ queueWaitTime,+ queueTotalWaitTime,+ enqueueWaitTime,+ dequeueWaitTime,+ queueRate,+ -- * Dequeuing and Enqueuing+ dequeue,+ dequeueWithOutputPriority,+ tryDequeue,+ enqueue,+ enqueueWithInputPriority,+ enqueueWithStoringPriority,+ enqueueWithInputStoringPriorities,+ tryEnqueue,+ tryEnqueueWithStoringPriority,+ enqueueOrLost,+ enqueueOrLost_,+ enqueueWithStoringPriorityOrLost,+ enqueueWithStoringPriorityOrLost_,+ -- * Awaiting+ waitWhileFullQueue,+ -- * Summary+ queueSummary,+ -- * Derived Signals for Properties+ queueNullChanged,+ queueNullChanged_,+ queueFullChanged,+ queueFullChanged_,+ queueCountChanged,+ queueCountChanged_,+ enqueueCountChanged,+ enqueueCountChanged_,+ enqueueLostCountChanged,+ enqueueLostCountChanged_,+ enqueueStoreCountChanged,+ enqueueStoreCountChanged_,+ dequeueCountChanged,+ dequeueCountChanged_,+ dequeueExtractCountChanged,+ dequeueExtractCountChanged_,+ queueLoadFactorChanged,+ queueLoadFactorChanged_,+ queueWaitTimeChanged,+ queueWaitTimeChanged_,+ queueTotalWaitTimeChanged,+ queueTotalWaitTimeChanged_,+ enqueueWaitTimeChanged,+ enqueueWaitTimeChanged_,+ dequeueWaitTimeChanged,+ dequeueWaitTimeChanged_,+ queueRateChanged,+ queueRateChanged_,+ -- * 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 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 FCFS FCFS a++-- | A type synonym for the ordinary LIFO queue also known as the LCFS+-- (Last Come - First Serviced) queue.+type LCFSQueue a = Queue FCFS LCFS FCFS a++-- | A type synonym for the SIRO (Serviced in Random Order) queue.+type SIROQueue a = Queue FCFS SIRO FCFS a++-- | A type synonym for the queue with static priorities applied when+-- storing the elements in the queue.+type PriorityQueue a = Queue FCFS StaticPriorities FCFS a++-- | Represents a queue using the specified strategies for enqueueing (input), @si@,+-- internal storing (in memory), @sm@, and dequeueing (output), @so@, where @a@ denotes+-- the type of items stored in the queue.+data Queue si sm so a =+ Queue { queueMaxCount :: Int,+ -- ^ The queue capacity.+ enqueueStrategy :: si,+ -- ^ The strategy applied to the enqueueing (input) processes when the queue is full.+ enqueueStoringStrategy :: sm,+ -- ^ The strategy applied when storing (in memory) items in the queue.+ dequeueStrategy :: so,+ -- ^ The strategy applied to the dequeueing (output) processes when the queue is empty.+ enqueueRes :: Resource si,+ queueStore :: StrategyQueue sm (QueueItem a),+ dequeueRes :: Resource so,+ queueCountRef :: IORef Int,+ queueCountStatsRef :: IORef (TimingStats Int),+ enqueueCountRef :: IORef Int,+ enqueueLostCountRef :: IORef Int,+ enqueueStoreCountRef :: IORef Int,+ dequeueCountRef :: IORef Int,+ dequeueExtractCountRef :: IORef Int,+ queueWaitTimeRef :: IORef (SamplingStats Double),+ queueTotalWaitTimeRef :: IORef (SamplingStats Double),+ enqueueWaitTimeRef :: IORef (SamplingStats Double),+ dequeueWaitTimeRef :: 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 FCFS queue with the specified capacity. +newFCFSQueue :: Int -> Event (FCFSQueue a) +newFCFSQueue = newQueue FCFS FCFS FCFS+ +-- | Create a new LCFS queue with the specified capacity. +newLCFSQueue :: Int -> Event (LCFSQueue a) +newLCFSQueue = newQueue FCFS LCFS FCFS+ +-- | Create a new SIRO queue with the specified capacity. +newSIROQueue :: Int -> Event (SIROQueue a) +newSIROQueue = newQueue FCFS SIRO FCFS+ +-- | Create a new priority queue with the specified capacity. +newPriorityQueue :: Int -> Event (PriorityQueue a) +newPriorityQueue = newQueue FCFS StaticPriorities FCFS+ +-- | Create a new queue with the specified strategies and capacity. +newQueue :: (QueueStrategy si,+ QueueStrategy sm,+ QueueStrategy so) =>+ si+ -- ^ the strategy applied to the enqueueing (input) processes when the queue is full+ -> sm+ -- ^ the strategy applied when storing items in the queue+ -> so+ -- ^ the strategy applied to the dequeueing (output) processes when the queue is empty+ -> Int+ -- ^ the queue capacity+ -> Event (Queue si sm so a) +newQueue si sm so count =+ do t <- liftDynamics time+ i <- liftIO $ newIORef 0+ is <- liftIO $ newIORef $ returnTimingStats t 0+ ci <- liftIO $ newIORef 0+ cl <- liftIO $ newIORef 0+ cm <- liftIO $ newIORef 0+ cr <- liftIO $ newIORef 0+ co <- liftIO $ newIORef 0+ ri <- liftSimulation $ newResourceWithMaxCount si count (Just count)+ qm <- liftSimulation $ newStrategyQueue sm+ ro <- liftSimulation $ newResourceWithMaxCount so 0 (Just count)+ w <- liftIO $ newIORef mempty+ wt <- liftIO $ newIORef mempty+ wi <- liftIO $ newIORef mempty+ wo <- liftIO $ newIORef mempty + s1 <- liftSimulation $ newSignalSource+ s2 <- liftSimulation $ newSignalSource+ s3 <- liftSimulation $ newSignalSource+ s4 <- liftSimulation $ newSignalSource+ s5 <- liftSimulation $ newSignalSource+ return Queue { queueMaxCount = count,+ enqueueStrategy = si,+ enqueueStoringStrategy = sm,+ dequeueStrategy = so,+ enqueueRes = ri,+ queueStore = qm,+ dequeueRes = ro,+ queueCountRef = i,+ queueCountStatsRef = is,+ enqueueCountRef = ci,+ enqueueLostCountRef = cl,+ enqueueStoreCountRef = cm,+ dequeueCountRef = cr,+ dequeueExtractCountRef = co,+ queueWaitTimeRef = w,+ queueTotalWaitTimeRef = wt,+ enqueueWaitTimeRef = wi,+ dequeueWaitTimeRef = 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 sm so 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 sm so a -> Signal Bool+queueNullChanged q =+ mapSignalM (const $ queueNull q) (queueNullChanged_ q)+ +-- | Signal when the 'queueNull' property value has changed.+queueNullChanged_ :: Queue si sm so a -> Signal ()+queueNullChanged_ = queueCountChanged_++-- | Test whether the queue is full.+--+-- See also 'queueFullChanged' and 'queueFullChanged_'.+queueFull :: Queue si sm so 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 sm so a -> Signal Bool+queueFullChanged q =+ mapSignalM (const $ queueFull q) (queueFullChanged_ q)+ +-- | Signal when the 'queueFull' property value has changed.+queueFullChanged_ :: Queue si sm so a -> Signal ()+queueFullChanged_ = queueCountChanged_++-- | Return the current queue size.+--+-- See also 'queueCountStats', 'queueCountChanged' and 'queueCountChanged_'.+queueCount :: Queue si sm so a -> Event Int+queueCount q =+ Event $ \p -> readIORef (queueCountRef q)++-- | Return the queue size statistics.+queueCountStats :: Queue si sm so a -> Event (TimingStats Int)+queueCountStats q =+ Event $ \p -> readIORef (queueCountStatsRef q)+ +-- | Signal when the 'queueCount' property value has changed.+queueCountChanged :: Queue si sm so a -> Signal Int+queueCountChanged q =+ mapSignalM (const $ queueCount q) (queueCountChanged_ q)+ +-- | Signal when the 'queueCount' property value has changed.+queueCountChanged_ :: Queue si sm so a -> Signal ()+queueCountChanged_ q =+ mapSignal (const ()) (enqueueStored q) <>+ mapSignal (const ()) (dequeueExtracted q)++-- | Return the total number of input items that were enqueued.+--+-- See also 'enqueueCountChanged' and 'enqueueCountChanged_'.+enqueueCount :: Queue si sm so a -> Event Int+enqueueCount q =+ Event $ \p -> readIORef (enqueueCountRef q)+ +-- | Signal when the 'enqueueCount' property value has changed.+enqueueCountChanged :: Queue si sm so a -> Signal Int+enqueueCountChanged q =+ mapSignalM (const $ enqueueCount q) (enqueueCountChanged_ q)+ +-- | Signal when the 'enqueueCount' property value has changed.+enqueueCountChanged_ :: Queue si sm so a -> Signal ()+enqueueCountChanged_ q =+ mapSignal (const ()) (enqueueInitiated q)+ +-- | Return the number of lost items.+--+-- See also 'enqueueLostCountChanged' and 'enqueueLostCountChanged_'.+enqueueLostCount :: Queue si sm so a -> Event Int+enqueueLostCount q =+ Event $ \p -> readIORef (enqueueLostCountRef q)+ +-- | Signal when the 'enqueueLostCount' property value has changed.+enqueueLostCountChanged :: Queue si sm so a -> Signal Int+enqueueLostCountChanged q =+ mapSignalM (const $ enqueueLostCount q) (enqueueLostCountChanged_ q)+ +-- | Signal when the 'enqueueLostCount' property value has changed.+enqueueLostCountChanged_ :: Queue si sm so a -> Signal ()+enqueueLostCountChanged_ q =+ mapSignal (const ()) (enqueueLost q)+ +-- | Return the total number of input items that were stored.+--+-- See also 'enqueueStoreCountChanged' and 'enqueueStoreCountChanged_'.+enqueueStoreCount :: Queue si sm so a -> Event Int+enqueueStoreCount q =+ Event $ \p -> readIORef (enqueueStoreCountRef q)+ +-- | Signal when the 'enqueueStoreCount' property value has changed.+enqueueStoreCountChanged :: Queue si sm so a -> Signal Int+enqueueStoreCountChanged q =+ mapSignalM (const $ enqueueStoreCount q) (enqueueStoreCountChanged_ q)+ +-- | Signal when the 'enqueueStoreCount' property value has changed.+enqueueStoreCountChanged_ :: Queue si sm so a -> Signal ()+enqueueStoreCountChanged_ q =+ mapSignal (const ()) (enqueueStored q)+ +-- | Return the total number of requests for dequeueing the items,+-- not taking into account the failed attempts to dequeue immediately+-- without suspension.+--+-- See also 'dequeueCountChanged' and 'dequeueCountChanged_'.+dequeueCount :: Queue si sm so a -> Event Int+dequeueCount q =+ Event $ \p -> readIORef (dequeueCountRef q)+ +-- | Signal when the 'dequeueCount' property value has changed.+dequeueCountChanged :: Queue si sm so a -> Signal Int+dequeueCountChanged q =+ mapSignalM (const $ dequeueCount q) (dequeueCountChanged_ q)+ +-- | Signal when the 'dequeueCount' property value has changed.+dequeueCountChanged_ :: Queue si sm so a -> Signal ()+dequeueCountChanged_ q =+ mapSignal (const ()) (dequeueRequested q)+ +-- | Return the total number of output items that were actually dequeued.+--+-- See also 'dequeueExtractCountChanged' and 'dequeueExtractCountChanged_'.+dequeueExtractCount :: Queue si sm so a -> Event Int+dequeueExtractCount q =+ Event $ \p -> readIORef (dequeueExtractCountRef q)+ +-- | Signal when the 'dequeueExtractCount' property value has changed.+dequeueExtractCountChanged :: Queue si sm so a -> Signal Int+dequeueExtractCountChanged q =+ mapSignalM (const $ dequeueExtractCount q) (dequeueExtractCountChanged_ q)+ +-- | Signal when the 'dequeueExtractCount' property value has changed.+dequeueExtractCountChanged_ :: Queue si sm so a -> Signal ()+dequeueExtractCountChanged_ 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 sm so 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 sm so a -> Signal Double+queueLoadFactorChanged q =+ mapSignalM (const $ queueLoadFactor q) (queueLoadFactorChanged_ q)+ +-- | Signal when the 'queueLoadFactor' property value has changed.+queueLoadFactorChanged_ :: Queue si sm so 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.+enqueueRate :: Queue si sm so a -> Event Double+enqueueRate q =+ Event $ \p ->+ do x <- readIORef (enqueueCountRef 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.+enqueueStoreRate :: Queue si sm so a -> Event Double+enqueueStoreRate q =+ Event $ \p ->+ do x <- readIORef (enqueueStoreCountRef 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 failed attempts to dequeue immediately+-- without suspension.+dequeueRate :: Queue si sm so a -> Event Double+dequeueRate q =+ Event $ \p ->+ do x <- readIORef (dequeueCountRef q)+ let t0 = spcStartTime $ pointSpecs p+ t = pointTime p+ return (fromIntegral x / (t - t0))+ +-- | Return the rate of the output items that were actually dequeued: how many items+-- per time.+dequeueExtractRate :: Queue si sm so a -> Event Double+dequeueExtractRate q =+ Event $ \p ->+ do x <- readIORef (dequeueExtractCountRef 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 sm so a -> Event (SamplingStats Double)+queueWaitTime q =+ Event $ \p -> readIORef (queueWaitTimeRef q)+ +-- | Signal when the 'queueWaitTime' property value has changed.+queueWaitTimeChanged :: Queue si sm so a -> Signal (SamplingStats Double)+queueWaitTimeChanged q =+ mapSignalM (const $ queueWaitTime q) (queueWaitTimeChanged_ q)+ +-- | Signal when the 'queueWaitTime' property value has changed.+queueWaitTimeChanged_ :: Queue si sm so 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 sm so a -> Event (SamplingStats Double)+queueTotalWaitTime q =+ Event $ \p -> readIORef (queueTotalWaitTimeRef q)+ +-- | Signal when the 'queueTotalWaitTime' property value has changed.+queueTotalWaitTimeChanged :: Queue si sm so a -> Signal (SamplingStats Double)+queueTotalWaitTimeChanged q =+ mapSignalM (const $ queueTotalWaitTime q) (queueTotalWaitTimeChanged_ q)+ +-- | Signal when the 'queueTotalWaitTime' property value has changed.+queueTotalWaitTimeChanged_ :: Queue si sm so a -> Signal ()+queueTotalWaitTimeChanged_ q =+ mapSignal (const ()) (dequeueExtracted q)+ +-- | Return the enqueue 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 'enqueueWaitTimeChanged' and 'enqueueWaitTimeChanged_'.+enqueueWaitTime :: Queue si sm so a -> Event (SamplingStats Double)+enqueueWaitTime q =+ Event $ \p -> readIORef (enqueueWaitTimeRef q)+ +-- | Signal when the 'enqueueWaitTime' property value has changed.+enqueueWaitTimeChanged :: Queue si sm so a -> Signal (SamplingStats Double)+enqueueWaitTimeChanged q =+ mapSignalM (const $ enqueueWaitTime q) (enqueueWaitTimeChanged_ q)+ +-- | Signal when the 'enqueueWaitTime' property value has changed.+enqueueWaitTimeChanged_ :: Queue si sm so a -> Signal ()+enqueueWaitTimeChanged_ q =+ mapSignal (const ()) (enqueueStored q)+ +-- | Return the dequeue wait time from the time at which the item was requested+-- for dequeueing to the time at which it was actually dequeued.+--+-- See also 'dequeueWaitTimeChanged' and 'dequeueWaitTimeChanged_'.+dequeueWaitTime :: Queue si sm so a -> Event (SamplingStats Double)+dequeueWaitTime q =+ Event $ \p -> readIORef (dequeueWaitTimeRef q)+ +-- | Signal when the 'dequeueWaitTime' property value has changed.+dequeueWaitTimeChanged :: Queue si sm so a -> Signal (SamplingStats Double)+dequeueWaitTimeChanged q =+ mapSignalM (const $ dequeueWaitTime q) (dequeueWaitTimeChanged_ q)+ +-- | Signal when the 'dequeueWaitTime' property value has changed.+dequeueWaitTimeChanged_ :: Queue si sm so a -> Signal ()+dequeueWaitTimeChanged_ q =+ mapSignal (const ()) (dequeueExtracted q)++-- | Return a long-term average queue rate calculated as+-- the average queue size divided by the average wait time.+--+-- This value may be less than the actual arrival rate as the queue is+-- finite and new arrivals may be locked while the queue remains full.+--+-- See also 'queueRateChanged' and 'queueRateChanged_'.+queueRate :: Queue si sm so a -> Event Double+queueRate q =+ Event $ \p ->+ do x <- readIORef (queueCountStatsRef q)+ y <- readIORef (queueWaitTimeRef q)+ return (timingStatsMean x / samplingStatsMean y) + +-- | Signal when the 'queueRate' property value has changed.+queueRateChanged :: Queue si sm so a -> Signal Double+queueRateChanged q =+ mapSignalM (const $ queueRate q) (queueRateChanged_ q)+ +-- | Signal when the 'queueRate' property value has changed.+queueRateChanged_ :: Queue si sm so a -> Signal ()+queueRateChanged_ q =+ mapSignal (const ()) (enqueueStored q) <>+ mapSignal (const ()) (dequeueExtracted q)++-- | Dequeue suspending the process if the queue is empty.+dequeue :: (DequeueStrategy si,+ DequeueStrategy sm,+ EnqueueStrategy so)+ => Queue si sm so a+ -- ^ the queue+ -> Process a+ -- ^ the dequeued value+dequeue q =+ do t <- liftEvent $ dequeueRequest q+ requestResource (dequeueRes q)+ liftEvent $ dequeueExtract q t+ +-- | Dequeue with the output priority suspending the process if the queue is empty.+dequeueWithOutputPriority :: (DequeueStrategy si,+ DequeueStrategy sm,+ PriorityQueueStrategy so po)+ => Queue si sm so a+ -- ^ the queue+ -> po+ -- ^ the priority for output+ -> Process a+ -- ^ the dequeued value+dequeueWithOutputPriority q po =+ do t <- liftEvent $ dequeueRequest q+ requestResourceWithPriority (dequeueRes q) po+ liftEvent $ dequeueExtract q t+ +-- | Try to dequeue immediately.+tryDequeue :: (DequeueStrategy si,+ DequeueStrategy sm)+ => Queue si sm so a+ -- ^ the queue+ -> Event (Maybe a)+ -- ^ the dequeued value of 'Nothing'+tryDequeue q =+ do x <- tryRequestResourceWithinEvent (dequeueRes q)+ if x + then do t <- dequeueRequest q+ fmap Just $ dequeueExtract q t+ else return Nothing++-- | Enqueue the item suspending the process if the queue is full. +enqueue :: (EnqueueStrategy si,+ EnqueueStrategy sm,+ DequeueStrategy so)+ => Queue si sm so a+ -- ^ the queue+ -> a+ -- ^ the item to enqueue+ -> Process ()+enqueue q a =+ do i <- liftEvent $ enqueueInitiate q a+ requestResource (enqueueRes q)+ liftEvent $ enqueueStore q i+ +-- | Enqueue with the input priority the item suspending the process if the queue is full. +enqueueWithInputPriority :: (PriorityQueueStrategy si pi,+ EnqueueStrategy sm,+ DequeueStrategy so)+ => Queue si sm so 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 (enqueueRes q) pi+ liftEvent $ enqueueStore q i+ +-- | Enqueue with the storing priority the item suspending the process if the queue is full. +enqueueWithStoringPriority :: (EnqueueStrategy si,+ PriorityQueueStrategy sm pm,+ DequeueStrategy so)+ => Queue si sm so a+ -- ^ the queue+ -> pm+ -- ^ the priority for storing+ -> a+ -- ^ the item to enqueue+ -> Process ()+enqueueWithStoringPriority q pm a =+ do i <- liftEvent $ enqueueInitiate q a+ requestResource (enqueueRes 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 pi,+ PriorityQueueStrategy sm pm,+ DequeueStrategy so)+ => Queue si sm so 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 (enqueueRes 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,+ DequeueStrategy so)+ => Queue si sm so a+ -- ^ the queue+ -> a+ -- ^ the item which we try to enqueue+ -> Event Bool+tryEnqueue q a =+ do x <- tryRequestResourceWithinEvent (enqueueRes q)+ if x + 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 pm,+ DequeueStrategy so)+ => Queue si sm so 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 (enqueueRes 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,+ DequeueStrategy so)+ => Queue si sm so a+ -- ^ the queue+ -> a+ -- ^ the item which we try to enqueue+ -> Event Bool+enqueueOrLost q a =+ do x <- tryRequestResourceWithinEvent (enqueueRes q)+ if x+ then do enqueueInitiate q a >>= enqueueStore q+ return True+ 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 pm,+ DequeueStrategy so)+ => Queue si sm so 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 (enqueueRes 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,+ DequeueStrategy so)+ => Queue si sm so a+ -- ^ the queue+ -> a+ -- ^ the item which we try to enqueue+ -> Event ()+enqueueOrLost_ q a =+ do x <- enqueueOrLost q a+ return ()++-- | Try to enqueue with the storing priority the item. If the queue is full+-- then the item will be lost.+enqueueWithStoringPriorityOrLost_ :: (PriorityQueueStrategy sm pm,+ DequeueStrategy so)+ => Queue si sm so 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 sm so 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 sm so 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', '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 sm so a -> Signal a+enqueueLost q = publishSignal (enqueueLostSource q)++-- | Return a signal that notifies when the dequeuing operation was requested.+dequeueRequested :: Queue si sm so 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 sm so a -> Signal a+dequeueExtracted q = publishSignal (dequeueExtractedSource q)++-- | Initiate the process of enqueuing the item.+enqueueInitiate :: Queue si sm so a+ -- ^ the queue+ -> a+ -- ^ the item to be enqueued+ -> Event (QueueItem a)+enqueueInitiate q a =+ Event $ \p ->+ do let t = pointTime p+ modifyIORef' (enqueueCountRef 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,+ DequeueStrategy so)+ => Queue si sm so 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 (queueStore q) i'+ c <- readIORef (queueCountRef q)+ let c' = c + 1+ t = pointTime p + c' `seq` writeIORef (queueCountRef q) c'+ modifyIORef' (queueCountStatsRef q) (addTimingStats t c')+ modifyIORef' (enqueueStoreCountRef q) (+ 1)+ invokeEvent p $+ enqueueStat q i'+ invokeEvent p $+ releaseResourceWithinEvent (dequeueRes q)+ invokeEvent p $+ triggerSignal (enqueueStoredSource q) (itemValue i')++-- | Store with the priority the item.+enqueueStoreWithPriority :: (PriorityQueueStrategy sm pm,+ DequeueStrategy so)+ => Queue si sm so 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 (queueStore q) pm i'+ c <- readIORef (queueCountRef q)+ let c' = c + 1+ t = pointTime p+ c' `seq` writeIORef (queueCountRef q) c'+ modifyIORef' (queueCountStatsRef q) (addTimingStats t c')+ modifyIORef' (enqueueStoreCountRef q) (+ 1)+ invokeEvent p $+ enqueueStat q i'+ invokeEvent p $+ releaseResourceWithinEvent (dequeueRes q)+ invokeEvent p $+ triggerSignal (enqueueStoredSource q) (itemValue i')++-- | Deny the enqueuing.+enqueueDeny :: Queue si sm so a+ -- ^ the queue+ -> a+ -- ^ the item to be denied+ -> Event ()+enqueueDeny q a =+ Event $ \p ->+ do modifyIORef' (enqueueLostCountRef q) $ (+) 1+ invokeEvent p $+ triggerSignal (enqueueLostSource q) a++-- | Update the statistics for the input wait time of the enqueuing operation.+enqueueStat :: Queue si sm so 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' (enqueueWaitTimeRef q) $+ addSamplingStats (t1 - t0)++-- | Accept the dequeuing request and return the current simulation time.+dequeueRequest :: Queue si sm so a+ -- ^ the queue+ -> Event Double+ -- ^ the current time+dequeueRequest q =+ Event $ \p ->+ do modifyIORef' (dequeueCountRef q) (+ 1)+ invokeEvent p $+ triggerSignal (dequeueRequestedSource q) ()+ return $ pointTime p ++-- | Extract an item for the dequeuing request. +dequeueExtract :: (DequeueStrategy si,+ DequeueStrategy sm)+ => Queue si sm so 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 (queueStore q)+ c <- readIORef (queueCountRef q)+ let c' = c - 1+ t = pointTime p+ c' `seq` writeIORef (queueCountRef q) c'+ modifyIORef' (queueCountStatsRef q) (addTimingStats t c')+ modifyIORef' (dequeueExtractCountRef q) (+ 1)+ invokeEvent p $+ dequeueStat q t' i+ invokeEvent p $+ releaseResourceWithinEvent (enqueueRes 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 sm so 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' (dequeueWaitTimeRef 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 sm so 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 sm so 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 sm so a -> Int -> Event ShowS+queueSummary q indent =+ do let si = enqueueStrategy q+ sm = enqueueStoringStrategy q+ so = dequeueStrategy q+ null <- queueNull q+ full <- queueFull q+ let maxCount = queueMaxCount q+ count <- queueCount q+ countStats <- queueCountStats q+ enqueueCount <- enqueueCount q+ enqueueLostCount <- enqueueLostCount q+ enqueueStoreCount <- enqueueStoreCount q+ dequeueCount <- dequeueCount q+ dequeueExtractCount <- dequeueExtractCount q+ loadFactor <- queueLoadFactor q+ enqueueRate <- enqueueRate q+ enqueueStoreRate <- enqueueStoreRate q+ dequeueRate <- dequeueRate q+ dequeueExtractRate <- dequeueExtractRate q+ waitTime <- queueWaitTime q+ totalWaitTime <- queueTotalWaitTime q+ enqueueWaitTime <- enqueueWaitTime q+ dequeueWaitTime <- dequeueWaitTime q+ let tab = replicate indent ' '+ return $+ showString tab .+ showString "the enqueueing (input) strategy = " .+ shows si .+ showString "\n" .+ showString tab .+ showString "the storing (memory) strategy = " .+ shows sm .+ showString "\n" .+ showString tab .+ showString "the dequeueing (output) 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 size statistics = \n\n" .+ timingStatsSummary countStats (2 + indent) .+ showString "\n\n" .+ showString tab .+ showString "the enqueue count (number of the input items that were enqueued) = " .+ shows enqueueCount .+ showString "\n" .+ showString tab .+ showString "the enqueue lost count (number of the lost items) = " .+ shows enqueueLostCount .+ showString "\n" .+ showString tab .+ showString "the enqueue store count (number of the input items that were stored) = " .+ shows enqueueStoreCount .+ showString "\n" .+ showString tab .+ showString "the dequeue count (number of requests for dequeueing an item) = " .+ shows dequeueCount .+ showString "\n" .+ showString tab .+ showString "the dequeue extract count (number of the output items that were dequeued) = " .+ shows dequeueExtractCount .+ showString "\n" .+ showString tab .+ showString "the load factor (size / max. capacity) = " .+ shows loadFactor .+ showString "\n" .+ showString tab .+ showString "the enqueue rate (how many input items were enqueued per time) = " .+ shows enqueueRate .+ showString "\n" .+ showString tab .+ showString "the enqueue store rate (how many input items were stored per time) = " .+ shows enqueueStoreRate .+ showString "\n" .+ showString tab .+ showString "the dequeue rate (how many requests for dequeueing per time) = " .+ shows dequeueRate .+ showString "\n" .+ showString tab .+ showString "the dequeue extract rate (how many output items were dequeued per time) = " .+ shows dequeueExtractRate .+ 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 enqueue wait time (when the enqueueing was initiated -> when was stored) = \n\n" .+ samplingStatsSummary enqueueWaitTime (2 + indent) .+ showString "\n\n" .+ showString tab .+ showString "the dequeue wait time (when was requested for dequeueing -> when was dequeued) = \n\n" .+ samplingStatsSummary dequeueWaitTime (2 + indent)
Simulation/Aivika/Queue/Infinite.hs view
@@ -1,636 +1,636 @@- --- | --- Module : Simulation.Aivika.Queue.Infinite --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.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 - enqueueStoringStrategy, - dequeueStrategy, - queueNull, - queueCount, - queueCountStats, - enqueueStoreCount, - dequeueCount, - dequeueExtractCount, - enqueueStoreRate, - dequeueRate, - dequeueExtractRate, - queueWaitTime, - dequeueWaitTime, - queueRate, - -- * Dequeuing and Enqueuing - dequeue, - dequeueWithOutputPriority, - tryDequeue, - enqueue, - enqueueWithStoringPriority, - -- * Summary - queueSummary, - -- * Derived Signals for Properties - queueNullChanged, - queueNullChanged_, - queueCountChanged, - queueCountChanged_, - enqueueStoreCountChanged, - enqueueStoreCountChanged_, - dequeueCountChanged, - dequeueCountChanged_, - dequeueExtractCountChanged, - dequeueExtractCountChanged_, - queueWaitTimeChanged, - queueWaitTimeChanged_, - dequeueWaitTimeChanged, - dequeueWaitTimeChanged_, - queueRateChanged, - queueRateChanged_, - -- * 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 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 FCFS a - --- | A type synonym for the ordinary LIFO queue also known as the LCFS --- (Last Come - First Serviced) queue. -type LCFSQueue a = Queue LCFS FCFS a - --- | A type synonym for the SIRO (Serviced in Random Order) queue. -type SIROQueue a = Queue SIRO FCFS a - --- | A type synonym for the queue with static priorities applied when --- storing the elements in the queue. -type PriorityQueue a = Queue StaticPriorities FCFS a - --- | Represents an infinite queue using the specified strategies for --- internal storing (in memory), @sm@, and dequeueing (output), @so@, where @a@ denotes --- the type of items stored in the queue. -data Queue sm so a = - Queue { enqueueStoringStrategy :: sm, - -- ^ The strategy applied when storing (in memory) items in the queue. - dequeueStrategy :: so, - -- ^ The strategy applied to the dequeueing (output) processes. - queueStore :: StrategyQueue sm (QueueItem a), - dequeueRes :: Resource so, - queueCountRef :: IORef Int, - queueCountStatsRef :: IORef (TimingStats Int), - enqueueStoreCountRef :: IORef Int, - dequeueCountRef :: IORef Int, - dequeueExtractCountRef :: IORef Int, - queueWaitTimeRef :: IORef (SamplingStats Double), - dequeueWaitTimeRef :: 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 :: Event (FCFSQueue a) -newFCFSQueue = newQueue FCFS FCFS - --- | Create a new infinite LCFS queue. -newLCFSQueue :: Event (LCFSQueue a) -newLCFSQueue = newQueue LCFS FCFS - --- | Create a new infinite SIRO queue. -newSIROQueue :: Event (SIROQueue a) -newSIROQueue = newQueue SIRO FCFS - --- | Create a new infinite priority queue. -newPriorityQueue :: Event (PriorityQueue a) -newPriorityQueue = newQueue StaticPriorities FCFS - --- | Create a new infinite queue with the specified strategies. -newQueue :: (QueueStrategy sm, - QueueStrategy so) => - sm - -- ^ the strategy applied when storing items in the queue - -> so - -- ^ the strategy applied to the dequeueing (output) processes when the queue is empty - -> Event (Queue sm so a) -newQueue sm so = - do t <- liftDynamics time - i <- liftIO $ newIORef 0 - is <- liftIO $ newIORef $ returnTimingStats t 0 - cm <- liftIO $ newIORef 0 - cr <- liftIO $ newIORef 0 - co <- liftIO $ newIORef 0 - qm <- liftSimulation $ newStrategyQueue sm - ro <- liftSimulation $ newResourceWithMaxCount so 0 Nothing - w <- liftIO $ newIORef mempty - wo <- liftIO $ newIORef mempty - s3 <- liftSimulation newSignalSource - s4 <- liftSimulation newSignalSource - s5 <- liftSimulation newSignalSource - return Queue { enqueueStoringStrategy = sm, - dequeueStrategy = so, - queueStore = qm, - dequeueRes = ro, - queueCountRef = i, - queueCountStatsRef = is, - enqueueStoreCountRef = cm, - dequeueCountRef = cr, - dequeueExtractCountRef = co, - queueWaitTimeRef = w, - dequeueWaitTimeRef = wo, - enqueueStoredSource = s3, - dequeueRequestedSource = s4, - dequeueExtractedSource = s5 } - --- | Test whether the queue is empty. --- --- See also 'queueNullChanged' and 'queueNullChanged_'. -queueNull :: Queue sm so 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 so a -> Signal Bool -queueNullChanged q = - mapSignalM (const $ queueNull q) (queueNullChanged_ q) - --- | Signal when the 'queueNull' property value has changed. -queueNullChanged_ :: Queue sm so a -> Signal () -queueNullChanged_ = queueCountChanged_ - --- | Return the current queue size. --- --- See also 'queueCountStats', 'queueCountChanged' and 'queueCountChanged_'. -queueCount :: Queue sm so a -> Event Int -queueCount q = - Event $ \p -> readIORef (queueCountRef q) - --- | Return the queue size statistics. -queueCountStats :: Queue sm so a -> Event (TimingStats Int) -queueCountStats q = - Event $ \p -> readIORef (queueCountStatsRef q) - --- | Signal when the 'queueCount' property value has changed. -queueCountChanged :: Queue sm so a -> Signal Int -queueCountChanged q = - mapSignalM (const $ queueCount q) (queueCountChanged_ q) - --- | Signal when the 'queueCount' property value has changed. -queueCountChanged_ :: Queue sm so a -> Signal () -queueCountChanged_ q = - mapSignal (const ()) (enqueueStored q) <> - mapSignal (const ()) (dequeueExtracted q) - --- | Return the total number of input items that were stored. --- --- See also 'enqueueStoreCountChanged' and 'enqueueStoreCountChanged_'. -enqueueStoreCount :: Queue sm so a -> Event Int -enqueueStoreCount q = - Event $ \p -> readIORef (enqueueStoreCountRef q) - --- | Signal when the 'enqueueStoreCount' property value has changed. -enqueueStoreCountChanged :: Queue sm so a -> Signal Int -enqueueStoreCountChanged q = - mapSignalM (const $ enqueueStoreCount q) (enqueueStoreCountChanged_ q) - --- | Signal when the 'enqueueStoreCount' property value has changed. -enqueueStoreCountChanged_ :: Queue sm so a -> Signal () -enqueueStoreCountChanged_ q = - mapSignal (const ()) (enqueueStored q) - --- | Return the total number of requests for dequeueing the items, --- not taking into account the failed attempts to dequeue immediately --- without suspension. --- --- See also 'dequeueCountChanged' and 'dequeueCountChanged_'. -dequeueCount :: Queue sm so a -> Event Int -dequeueCount q = - Event $ \p -> readIORef (dequeueCountRef q) - --- | Signal when the 'dequeueCount' property value has changed. -dequeueCountChanged :: Queue sm so a -> Signal Int -dequeueCountChanged q = - mapSignalM (const $ dequeueCount q) (dequeueCountChanged_ q) - --- | Signal when the 'dequeueCount' property value has changed. -dequeueCountChanged_ :: Queue sm so a -> Signal () -dequeueCountChanged_ q = - mapSignal (const ()) (dequeueRequested q) - --- | Return the total number of output items that were actually dequeued. --- --- See also 'dequeueExtractCountChanged' and 'dequeueExtractCountChanged_'. -dequeueExtractCount :: Queue sm so a -> Event Int -dequeueExtractCount q = - Event $ \p -> readIORef (dequeueExtractCountRef q) - --- | Signal when the 'dequeueExtractCount' property value has changed. -dequeueExtractCountChanged :: Queue sm so a -> Signal Int -dequeueExtractCountChanged q = - mapSignalM (const $ dequeueExtractCount q) (dequeueExtractCountChanged_ q) - --- | Signal when the 'dequeueExtractCount' property value has changed. -dequeueExtractCountChanged_ :: Queue sm so a -> Signal () -dequeueExtractCountChanged_ q = - mapSignal (const ()) (dequeueExtracted q) - --- | Return the rate of the items that were stored: how many items --- per time. -enqueueStoreRate :: Queue sm so a -> Event Double -enqueueStoreRate q = - Event $ \p -> - do x <- readIORef (enqueueStoreCountRef 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 failed attempts to dequeue immediately --- without suspension. -dequeueRate :: Queue sm so a -> Event Double -dequeueRate q = - Event $ \p -> - do x <- readIORef (dequeueCountRef 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. -dequeueExtractRate :: Queue sm so a -> Event Double -dequeueExtractRate q = - Event $ \p -> - do x <- readIORef (dequeueExtractCountRef 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 so a -> Event (SamplingStats Double) -queueWaitTime q = - Event $ \p -> readIORef (queueWaitTimeRef q) - --- | Signal when the 'queueWaitTime' property value has changed. -queueWaitTimeChanged :: Queue sm so a -> Signal (SamplingStats Double) -queueWaitTimeChanged q = - mapSignalM (const $ queueWaitTime q) (queueWaitTimeChanged_ q) - --- | Signal when the 'queueWaitTime' property value has changed. -queueWaitTimeChanged_ :: Queue sm so a -> Signal () -queueWaitTimeChanged_ q = - mapSignal (const ()) (dequeueExtracted q) - --- | Return the dequeue wait time from the time at which the item was requested --- for dequeueing to the time at which it was actually dequeued. --- --- See also 'dequeueWaitTimeChanged' and 'dequeueWaitTimeChanged_'. -dequeueWaitTime :: Queue sm so a -> Event (SamplingStats Double) -dequeueWaitTime q = - Event $ \p -> readIORef (dequeueWaitTimeRef q) - --- | Signal when the 'dequeueWaitTime' property value has changed. -dequeueWaitTimeChanged :: Queue sm so a -> Signal (SamplingStats Double) -dequeueWaitTimeChanged q = - mapSignalM (const $ dequeueWaitTime q) (dequeueWaitTimeChanged_ q) - --- | Signal when the 'dequeueWaitTime' property value has changed. -dequeueWaitTimeChanged_ :: Queue sm so a -> Signal () -dequeueWaitTimeChanged_ q = - mapSignal (const ()) (dequeueExtracted q) - --- | Return a long-term average queue rate calculated as --- the average queue size divided by the average wait time. --- --- See also 'queueRateChanged' and 'queueRateChanged_'. -queueRate :: Queue sm so a -> Event Double -queueRate q = - Event $ \p -> - do x <- readIORef (queueCountStatsRef q) - y <- readIORef (queueWaitTimeRef q) - return (timingStatsMean x / samplingStatsMean y) - --- | Signal when the 'queueRate' property value has changed. -queueRateChanged :: Queue sm so a -> Signal Double -queueRateChanged q = - mapSignalM (const $ queueRate q) (queueRateChanged_ q) - --- | Signal when the 'queueRate' property value has changed. -queueRateChanged_ :: Queue sm so a -> Signal () -queueRateChanged_ q = - mapSignal (const ()) (enqueueStored q) <> - mapSignal (const ()) (dequeueExtracted q) - --- | Dequeue suspending the process if the queue is empty. -dequeue :: (DequeueStrategy sm, - EnqueueStrategy so) - => Queue sm so a - -- ^ the queue - -> Process a - -- ^ the dequeued value -dequeue q = - do t <- liftEvent $ dequeueRequest q - requestResource (dequeueRes q) - liftEvent $ dequeueExtract q t - --- | Dequeue with the output priority suspending the process if the queue is empty. -dequeueWithOutputPriority :: (DequeueStrategy sm, - PriorityQueueStrategy so po) - => Queue sm so a - -- ^ the queue - -> po - -- ^ the priority for output - -> Process a - -- ^ the dequeued value -dequeueWithOutputPriority q po = - do t <- liftEvent $ dequeueRequest q - requestResourceWithPriority (dequeueRes q) po - liftEvent $ dequeueExtract q t - --- | Try to dequeue immediately. -tryDequeue :: DequeueStrategy sm - => Queue sm so a - -- ^ the queue - -> Event (Maybe a) - -- ^ the dequeued value of 'Nothing' -tryDequeue q = - do x <- tryRequestResourceWithinEvent (dequeueRes q) - if x - then do t <- dequeueRequest q - fmap Just $ dequeueExtract q t - else return Nothing - --- | Enqueue the item. -enqueue :: (EnqueueStrategy sm, - DequeueStrategy so) - => Queue sm so a - -- ^ the queue - -> a - -- ^ the item to enqueue - -> Event () -enqueue = enqueueStore - --- | Enqueue with the storing priority the item. -enqueueWithStoringPriority :: (PriorityQueueStrategy sm pm, - DequeueStrategy so) - => Queue sm so 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 so a -> Signal a -enqueueStored q = publishSignal (enqueueStoredSource q) - --- | Return a signal that notifies when the dequeuing operation was requested. -dequeueRequested :: Queue sm so 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 so a -> Signal a -dequeueExtracted q = publishSignal (dequeueExtractedSource q) - --- | Store the item. -enqueueStore :: (EnqueueStrategy sm, - DequeueStrategy so) - => Queue sm so 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 (queueStore q) i - c <- readIORef (queueCountRef q) - let c' = c + 1 - t = pointTime p - c' `seq` writeIORef (queueCountRef q) c' - modifyIORef' (queueCountStatsRef q) (addTimingStats t c') - modifyIORef' (enqueueStoreCountRef q) (+ 1) - invokeEvent p $ - releaseResourceWithinEvent (dequeueRes q) - invokeEvent p $ - triggerSignal (enqueueStoredSource q) (itemValue i) - --- | Store with the priority the item. -enqueueStoreWithPriority :: (PriorityQueueStrategy sm pm, - DequeueStrategy so) - => Queue sm so 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 (queueStore q) pm i - c <- readIORef (queueCountRef q) - let c' = c + 1 - t = pointTime p - c' `seq` writeIORef (queueCountRef q) c' - modifyIORef' (queueCountStatsRef q) (addTimingStats t c') - modifyIORef' (enqueueStoreCountRef q) (+ 1) - invokeEvent p $ - releaseResourceWithinEvent (dequeueRes q) - invokeEvent p $ - triggerSignal (enqueueStoredSource q) (itemValue i) - --- | Accept the dequeuing request and return the current simulation time. -dequeueRequest :: Queue sm so a - -- ^ the queue - -> Event Double - -- ^ the current time -dequeueRequest q = - Event $ \p -> - do modifyIORef' (dequeueCountRef q) (+ 1) - invokeEvent p $ - triggerSignal (dequeueRequestedSource q) () - return $ pointTime p - --- | Extract an item for the dequeuing request. -dequeueExtract :: DequeueStrategy sm - => Queue sm so 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 (queueStore q) - c <- readIORef (queueCountRef q) - let c' = c - 1 - t = pointTime p - c' `seq` writeIORef (queueCountRef q) c' - modifyIORef' (queueCountStatsRef q) (addTimingStats t c') - modifyIORef' (dequeueExtractCountRef 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 so 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' (dequeueWaitTimeRef 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 so 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 so a -> Int -> Event ShowS -queueSummary q indent = - do let sm = enqueueStoringStrategy q - so = dequeueStrategy q - null <- queueNull q - count <- queueCount q - countStats <- queueCountStats q - enqueueStoreCount <- enqueueStoreCount q - dequeueCount <- dequeueCount q - dequeueExtractCount <- dequeueExtractCount q - enqueueStoreRate <- enqueueStoreRate q - dequeueRate <- dequeueRate q - dequeueExtractRate <- dequeueExtractRate q - waitTime <- queueWaitTime q - dequeueWaitTime <- dequeueWaitTime q - let tab = replicate indent ' ' - return $ - showString tab . - showString "the storing (memory) strategy = " . - shows sm . - showString "\n" . - showString tab . - showString "the dequeueing (output) strategy = " . - shows so . - showString "\n" . - showString tab . - showString "empty? = " . - shows null . - showString "\n" . - showString tab . - showString "the current size = " . - shows count . - showString "\n" . - showString tab . - showString "the size statistics = \n\n" . - timingStatsSummary countStats (2 + indent) . - showString "\n\n" . - showString tab . - showString "the enqueue store count (number of the input items that were stored) = " . - shows enqueueStoreCount . - showString "\n" . - showString tab . - showString "the dequeue count (number of requests for dequeueing an item) = " . - shows dequeueCount . - showString "\n" . - showString tab . - showString "the dequeue extract count (number of the output items that were dequeued) = " . - shows dequeueExtractCount . - showString "\n" . - showString tab . - showString "the enqueue store rate (how many input items were stored per time) = " . - shows enqueueStoreRate . - showString "\n" . - showString tab . - showString "the dequeue rate (how many requests for dequeueing per time) = " . - shows dequeueRate . - showString "\n" . - showString tab . - showString "the dequeue extract rate (how many output items were dequeued per time) = " . - shows dequeueExtractRate . - 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 dequeue wait time (when was requested for dequeueing -> when was dequeued) = \n\n" . - samplingStatsSummary dequeueWaitTime (2 + indent) ++-- |+-- Module : Simulation.Aivika.Queue.Infinite+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.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+ enqueueStoringStrategy,+ dequeueStrategy,+ queueNull,+ queueCount,+ queueCountStats,+ enqueueStoreCount,+ dequeueCount,+ dequeueExtractCount,+ enqueueStoreRate,+ dequeueRate,+ dequeueExtractRate,+ queueWaitTime,+ dequeueWaitTime,+ queueRate,+ -- * Dequeuing and Enqueuing+ dequeue,+ dequeueWithOutputPriority,+ tryDequeue,+ enqueue,+ enqueueWithStoringPriority,+ -- * Summary+ queueSummary,+ -- * Derived Signals for Properties+ queueNullChanged,+ queueNullChanged_,+ queueCountChanged,+ queueCountChanged_,+ enqueueStoreCountChanged,+ enqueueStoreCountChanged_,+ dequeueCountChanged,+ dequeueCountChanged_,+ dequeueExtractCountChanged,+ dequeueExtractCountChanged_,+ queueWaitTimeChanged,+ queueWaitTimeChanged_,+ dequeueWaitTimeChanged,+ dequeueWaitTimeChanged_,+ queueRateChanged,+ queueRateChanged_,+ -- * 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 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 FCFS a++-- | A type synonym for the ordinary LIFO queue also known as the LCFS+-- (Last Come - First Serviced) queue.+type LCFSQueue a = Queue LCFS FCFS a++-- | A type synonym for the SIRO (Serviced in Random Order) queue.+type SIROQueue a = Queue SIRO FCFS a++-- | A type synonym for the queue with static priorities applied when+-- storing the elements in the queue.+type PriorityQueue a = Queue StaticPriorities FCFS a++-- | Represents an infinite queue using the specified strategies for+-- internal storing (in memory), @sm@, and dequeueing (output), @so@, where @a@ denotes+-- the type of items stored in the queue.+data Queue sm so a =+ Queue { enqueueStoringStrategy :: sm,+ -- ^ The strategy applied when storing (in memory) items in the queue.+ dequeueStrategy :: so,+ -- ^ The strategy applied to the dequeueing (output) processes.+ queueStore :: StrategyQueue sm (QueueItem a),+ dequeueRes :: Resource so,+ queueCountRef :: IORef Int,+ queueCountStatsRef :: IORef (TimingStats Int),+ enqueueStoreCountRef :: IORef Int,+ dequeueCountRef :: IORef Int,+ dequeueExtractCountRef :: IORef Int,+ queueWaitTimeRef :: IORef (SamplingStats Double),+ dequeueWaitTimeRef :: 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 :: Event (FCFSQueue a) +newFCFSQueue = newQueue FCFS FCFS+ +-- | Create a new infinite LCFS queue. +newLCFSQueue :: Event (LCFSQueue a) +newLCFSQueue = newQueue LCFS FCFS+ +-- | Create a new infinite SIRO queue. +newSIROQueue :: Event (SIROQueue a) +newSIROQueue = newQueue SIRO FCFS+ +-- | Create a new infinite priority queue. +newPriorityQueue :: Event (PriorityQueue a) +newPriorityQueue = newQueue StaticPriorities FCFS+ +-- | Create a new infinite queue with the specified strategies. +newQueue :: (QueueStrategy sm,+ QueueStrategy so) =>+ sm+ -- ^ the strategy applied when storing items in the queue+ -> so+ -- ^ the strategy applied to the dequeueing (output) processes when the queue is empty+ -> Event (Queue sm so a) +newQueue sm so =+ do t <- liftDynamics time+ i <- liftIO $ newIORef 0+ is <- liftIO $ newIORef $ returnTimingStats t 0+ cm <- liftIO $ newIORef 0+ cr <- liftIO $ newIORef 0+ co <- liftIO $ newIORef 0+ qm <- liftSimulation $ newStrategyQueue sm+ ro <- liftSimulation $ newResourceWithMaxCount so 0 Nothing+ w <- liftIO $ newIORef mempty+ wo <- liftIO $ newIORef mempty + s3 <- liftSimulation newSignalSource+ s4 <- liftSimulation newSignalSource+ s5 <- liftSimulation newSignalSource+ return Queue { enqueueStoringStrategy = sm,+ dequeueStrategy = so,+ queueStore = qm,+ dequeueRes = ro,+ queueCountRef = i,+ queueCountStatsRef = is,+ enqueueStoreCountRef = cm,+ dequeueCountRef = cr,+ dequeueExtractCountRef = co,+ queueWaitTimeRef = w,+ dequeueWaitTimeRef = wo,+ enqueueStoredSource = s3,+ dequeueRequestedSource = s4,+ dequeueExtractedSource = s5 }++-- | Test whether the queue is empty.+--+-- See also 'queueNullChanged' and 'queueNullChanged_'.+queueNull :: Queue sm so 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 so a -> Signal Bool+queueNullChanged q =+ mapSignalM (const $ queueNull q) (queueNullChanged_ q)+ +-- | Signal when the 'queueNull' property value has changed.+queueNullChanged_ :: Queue sm so a -> Signal ()+queueNullChanged_ = queueCountChanged_++-- | Return the current queue size.+--+-- See also 'queueCountStats', 'queueCountChanged' and 'queueCountChanged_'.+queueCount :: Queue sm so a -> Event Int+queueCount q =+ Event $ \p -> readIORef (queueCountRef q)++-- | Return the queue size statistics.+queueCountStats :: Queue sm so a -> Event (TimingStats Int)+queueCountStats q =+ Event $ \p -> readIORef (queueCountStatsRef q)+ +-- | Signal when the 'queueCount' property value has changed.+queueCountChanged :: Queue sm so a -> Signal Int+queueCountChanged q =+ mapSignalM (const $ queueCount q) (queueCountChanged_ q)+ +-- | Signal when the 'queueCount' property value has changed.+queueCountChanged_ :: Queue sm so a -> Signal ()+queueCountChanged_ q =+ mapSignal (const ()) (enqueueStored q) <>+ mapSignal (const ()) (dequeueExtracted q)+ +-- | Return the total number of input items that were stored.+--+-- See also 'enqueueStoreCountChanged' and 'enqueueStoreCountChanged_'.+enqueueStoreCount :: Queue sm so a -> Event Int+enqueueStoreCount q =+ Event $ \p -> readIORef (enqueueStoreCountRef q)+ +-- | Signal when the 'enqueueStoreCount' property value has changed.+enqueueStoreCountChanged :: Queue sm so a -> Signal Int+enqueueStoreCountChanged q =+ mapSignalM (const $ enqueueStoreCount q) (enqueueStoreCountChanged_ q)+ +-- | Signal when the 'enqueueStoreCount' property value has changed.+enqueueStoreCountChanged_ :: Queue sm so a -> Signal ()+enqueueStoreCountChanged_ q =+ mapSignal (const ()) (enqueueStored q)+ +-- | Return the total number of requests for dequeueing the items,+-- not taking into account the failed attempts to dequeue immediately+-- without suspension.+--+-- See also 'dequeueCountChanged' and 'dequeueCountChanged_'.+dequeueCount :: Queue sm so a -> Event Int+dequeueCount q =+ Event $ \p -> readIORef (dequeueCountRef q)+ +-- | Signal when the 'dequeueCount' property value has changed.+dequeueCountChanged :: Queue sm so a -> Signal Int+dequeueCountChanged q =+ mapSignalM (const $ dequeueCount q) (dequeueCountChanged_ q)+ +-- | Signal when the 'dequeueCount' property value has changed.+dequeueCountChanged_ :: Queue sm so a -> Signal ()+dequeueCountChanged_ q =+ mapSignal (const ()) (dequeueRequested q)+ +-- | Return the total number of output items that were actually dequeued.+--+-- See also 'dequeueExtractCountChanged' and 'dequeueExtractCountChanged_'.+dequeueExtractCount :: Queue sm so a -> Event Int+dequeueExtractCount q =+ Event $ \p -> readIORef (dequeueExtractCountRef q)+ +-- | Signal when the 'dequeueExtractCount' property value has changed.+dequeueExtractCountChanged :: Queue sm so a -> Signal Int+dequeueExtractCountChanged q =+ mapSignalM (const $ dequeueExtractCount q) (dequeueExtractCountChanged_ q)+ +-- | Signal when the 'dequeueExtractCount' property value has changed.+dequeueExtractCountChanged_ :: Queue sm so a -> Signal ()+dequeueExtractCountChanged_ q =+ mapSignal (const ()) (dequeueExtracted q)++-- | Return the rate of the items that were stored: how many items+-- per time.+enqueueStoreRate :: Queue sm so a -> Event Double+enqueueStoreRate q =+ Event $ \p ->+ do x <- readIORef (enqueueStoreCountRef 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 failed attempts to dequeue immediately+-- without suspension.+dequeueRate :: Queue sm so a -> Event Double+dequeueRate q =+ Event $ \p ->+ do x <- readIORef (dequeueCountRef 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.+dequeueExtractRate :: Queue sm so a -> Event Double+dequeueExtractRate q =+ Event $ \p ->+ do x <- readIORef (dequeueExtractCountRef 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 so a -> Event (SamplingStats Double)+queueWaitTime q =+ Event $ \p -> readIORef (queueWaitTimeRef q)+ +-- | Signal when the 'queueWaitTime' property value has changed.+queueWaitTimeChanged :: Queue sm so a -> Signal (SamplingStats Double)+queueWaitTimeChanged q =+ mapSignalM (const $ queueWaitTime q) (queueWaitTimeChanged_ q)+ +-- | Signal when the 'queueWaitTime' property value has changed.+queueWaitTimeChanged_ :: Queue sm so a -> Signal ()+queueWaitTimeChanged_ q =+ mapSignal (const ()) (dequeueExtracted q)+ +-- | Return the dequeue wait time from the time at which the item was requested+-- for dequeueing to the time at which it was actually dequeued.+--+-- See also 'dequeueWaitTimeChanged' and 'dequeueWaitTimeChanged_'.+dequeueWaitTime :: Queue sm so a -> Event (SamplingStats Double)+dequeueWaitTime q =+ Event $ \p -> readIORef (dequeueWaitTimeRef q)+ +-- | Signal when the 'dequeueWaitTime' property value has changed.+dequeueWaitTimeChanged :: Queue sm so a -> Signal (SamplingStats Double)+dequeueWaitTimeChanged q =+ mapSignalM (const $ dequeueWaitTime q) (dequeueWaitTimeChanged_ q)+ +-- | Signal when the 'dequeueWaitTime' property value has changed.+dequeueWaitTimeChanged_ :: Queue sm so a -> Signal ()+dequeueWaitTimeChanged_ q =+ mapSignal (const ()) (dequeueExtracted q)++-- | Return a long-term average queue rate calculated as+-- the average queue size divided by the average wait time.+--+-- See also 'queueRateChanged' and 'queueRateChanged_'.+queueRate :: Queue sm so a -> Event Double+queueRate q =+ Event $ \p ->+ do x <- readIORef (queueCountStatsRef q)+ y <- readIORef (queueWaitTimeRef q)+ return (timingStatsMean x / samplingStatsMean y) ++-- | Signal when the 'queueRate' property value has changed.+queueRateChanged :: Queue sm so a -> Signal Double+queueRateChanged q =+ mapSignalM (const $ queueRate q) (queueRateChanged_ q)++-- | Signal when the 'queueRate' property value has changed.+queueRateChanged_ :: Queue sm so a -> Signal ()+queueRateChanged_ q =+ mapSignal (const ()) (enqueueStored q) <>+ mapSignal (const ()) (dequeueExtracted q)+ +-- | Dequeue suspending the process if the queue is empty.+dequeue :: (DequeueStrategy sm,+ EnqueueStrategy so)+ => Queue sm so a+ -- ^ the queue+ -> Process a+ -- ^ the dequeued value+dequeue q =+ do t <- liftEvent $ dequeueRequest q+ requestResource (dequeueRes q)+ liftEvent $ dequeueExtract q t+ +-- | Dequeue with the output priority suspending the process if the queue is empty.+dequeueWithOutputPriority :: (DequeueStrategy sm,+ PriorityQueueStrategy so po)+ => Queue sm so a+ -- ^ the queue+ -> po+ -- ^ the priority for output+ -> Process a+ -- ^ the dequeued value+dequeueWithOutputPriority q po =+ do t <- liftEvent $ dequeueRequest q+ requestResourceWithPriority (dequeueRes q) po+ liftEvent $ dequeueExtract q t+ +-- | Try to dequeue immediately.+tryDequeue :: DequeueStrategy sm+ => Queue sm so a+ -- ^ the queue+ -> Event (Maybe a)+ -- ^ the dequeued value of 'Nothing'+tryDequeue q =+ do x <- tryRequestResourceWithinEvent (dequeueRes q)+ if x + then do t <- dequeueRequest q+ fmap Just $ dequeueExtract q t+ else return Nothing++-- | Enqueue the item. +enqueue :: (EnqueueStrategy sm,+ DequeueStrategy so)+ => Queue sm so a+ -- ^ the queue+ -> a+ -- ^ the item to enqueue+ -> Event ()+enqueue = enqueueStore+ +-- | Enqueue with the storing priority the item. +enqueueWithStoringPriority :: (PriorityQueueStrategy sm pm,+ DequeueStrategy so)+ => Queue sm so 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 so a -> Signal a+enqueueStored q = publishSignal (enqueueStoredSource q)++-- | Return a signal that notifies when the dequeuing operation was requested.+dequeueRequested :: Queue sm so 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 so a -> Signal a+dequeueExtracted q = publishSignal (dequeueExtractedSource q)++-- | Store the item.+enqueueStore :: (EnqueueStrategy sm,+ DequeueStrategy so)+ => Queue sm so 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 (queueStore q) i+ c <- readIORef (queueCountRef q)+ let c' = c + 1+ t = pointTime p+ c' `seq` writeIORef (queueCountRef q) c'+ modifyIORef' (queueCountStatsRef q) (addTimingStats t c')+ modifyIORef' (enqueueStoreCountRef q) (+ 1)+ invokeEvent p $+ releaseResourceWithinEvent (dequeueRes q)+ invokeEvent p $+ triggerSignal (enqueueStoredSource q) (itemValue i)++-- | Store with the priority the item.+enqueueStoreWithPriority :: (PriorityQueueStrategy sm pm,+ DequeueStrategy so)+ => Queue sm so 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 (queueStore q) pm i+ c <- readIORef (queueCountRef q)+ let c' = c + 1+ t = pointTime p+ c' `seq` writeIORef (queueCountRef q) c'+ modifyIORef' (queueCountStatsRef q) (addTimingStats t c')+ modifyIORef' (enqueueStoreCountRef q) (+ 1)+ invokeEvent p $+ releaseResourceWithinEvent (dequeueRes q)+ invokeEvent p $+ triggerSignal (enqueueStoredSource q) (itemValue i)++-- | Accept the dequeuing request and return the current simulation time.+dequeueRequest :: Queue sm so a+ -- ^ the queue+ -> Event Double+ -- ^ the current time+dequeueRequest q =+ Event $ \p ->+ do modifyIORef' (dequeueCountRef q) (+ 1)+ invokeEvent p $+ triggerSignal (dequeueRequestedSource q) ()+ return $ pointTime p ++-- | Extract an item for the dequeuing request. +dequeueExtract :: DequeueStrategy sm+ => Queue sm so 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 (queueStore q)+ c <- readIORef (queueCountRef q)+ let c' = c - 1+ t = pointTime p+ c' `seq` writeIORef (queueCountRef q) c'+ modifyIORef' (queueCountStatsRef q) (addTimingStats t c')+ modifyIORef' (dequeueExtractCountRef 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 so 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' (dequeueWaitTimeRef 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 so 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 so a -> Int -> Event ShowS+queueSummary q indent =+ do let sm = enqueueStoringStrategy q+ so = dequeueStrategy q+ null <- queueNull q+ count <- queueCount q+ countStats <- queueCountStats q+ enqueueStoreCount <- enqueueStoreCount q+ dequeueCount <- dequeueCount q+ dequeueExtractCount <- dequeueExtractCount q+ enqueueStoreRate <- enqueueStoreRate q+ dequeueRate <- dequeueRate q+ dequeueExtractRate <- dequeueExtractRate q+ waitTime <- queueWaitTime q+ dequeueWaitTime <- dequeueWaitTime q+ let tab = replicate indent ' '+ return $+ showString tab .+ showString "the storing (memory) strategy = " .+ shows sm .+ showString "\n" .+ showString tab .+ showString "the dequeueing (output) strategy = " .+ shows so .+ showString "\n" .+ showString tab .+ showString "empty? = " .+ shows null .+ showString "\n" .+ showString tab .+ showString "the current size = " .+ shows count .+ showString "\n" .+ showString tab .+ showString "the size statistics = \n\n" .+ timingStatsSummary countStats (2 + indent) .+ showString "\n\n" .+ showString tab .+ showString "the enqueue store count (number of the input items that were stored) = " .+ shows enqueueStoreCount .+ showString "\n" .+ showString tab .+ showString "the dequeue count (number of requests for dequeueing an item) = " .+ shows dequeueCount .+ showString "\n" .+ showString tab .+ showString "the dequeue extract count (number of the output items that were dequeued) = " .+ shows dequeueExtractCount .+ showString "\n" .+ showString tab .+ showString "the enqueue store rate (how many input items were stored per time) = " .+ shows enqueueStoreRate .+ showString "\n" .+ showString tab .+ showString "the dequeue rate (how many requests for dequeueing per time) = " .+ shows dequeueRate .+ showString "\n" .+ showString tab .+ showString "the dequeue extract rate (how many output items were dequeued per time) = " .+ shows dequeueExtractRate .+ 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 dequeue wait time (when was requested for dequeueing -> when was dequeued) = \n\n" .+ samplingStatsSummary dequeueWaitTime (2 + indent)
Simulation/Aivika/QueueStrategy.hs view
@@ -1,187 +1,187 @@- -{-# LANGUAGE TypeFamilies, MultiParamTypeClasses, FunctionalDependencies #-} - --- | --- Module : Simulation.Aivika.QueueStrategy --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- This module defines the queue strategies. --- -module Simulation.Aivika.QueueStrategy where - -import System.Random -import Control.Monad.Trans - -import Simulation.Aivika.Simulation -import Simulation.Aivika.Event -import Simulation.Aivika.DoubleLinkedList -import qualified Simulation.Aivika.PriorityQueue as PQ -import qualified Simulation.Aivika.Vector as V - --- | Defines the basic queue strategy. -class QueueStrategy s where - - -- | A queue used by the strategy. - data StrategyQueue s :: * -> * - - -- | Create a new queue by the specified strategy. - newStrategyQueue :: s - -- ^ the strategy - -> Simulation (StrategyQueue s i) - -- ^ a new queue - - -- | Test whether the queue is empty. - strategyQueueNull :: StrategyQueue s i - -- ^ the queue - -> Event Bool - -- ^ the result of the test - --- | Defines a strategy with support of the dequeuing operation. -class QueueStrategy s => DequeueStrategy s where - - -- | Dequeue the front element and return it. - strategyDequeue :: StrategyQueue s i - -- ^ the queue - -> Event i - -- ^ the dequeued element - --- | It defines a strategy when we can enqueue a single element. -class DequeueStrategy s => EnqueueStrategy s where - - -- | Enqueue an element. - strategyEnqueue :: StrategyQueue s 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 => PriorityQueueStrategy s p | s -> p where - - -- | Enqueue an element with the specified priority. - strategyEnqueueWithPriority :: StrategyQueue s 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 deriving (Eq, Ord, Show) - --- | Strategy: Last Come - First Served (LCFS) -data LCFS = LCFS deriving (Eq, Ord, Show) - --- | Strategy: Service in Random Order (SIRO). -data SIRO = SIRO deriving (Eq, Ord, Show) - --- | Strategy: Static Priorities. It uses the priority queue. -data StaticPriorities = StaticPriorities deriving (Eq, Ord, Show) - --- | An implementation of the 'FCFS' queue strategy. -instance QueueStrategy FCFS where - - -- | A queue used by the 'FCFS' strategy. - newtype StrategyQueue FCFS i = FCFSQueue (DoubleLinkedList i) - - newStrategyQueue s = fmap FCFSQueue $ liftIO newList - - strategyQueueNull (FCFSQueue q) = liftIO $ listNull q - --- | An implementation of the 'FCFS' queue strategy. -instance DequeueStrategy FCFS where - - strategyDequeue (FCFSQueue q) = - liftIO $ - do i <- listFirst q - listRemoveFirst q - return i - --- | An implementation of the 'FCFS' queue strategy. -instance EnqueueStrategy FCFS where - - strategyEnqueue (FCFSQueue q) i = liftIO $ listAddLast q i - --- | An implementation of the 'LCFS' queue strategy. -instance QueueStrategy LCFS where - - -- | A queue used by the 'LCFS' strategy. - newtype StrategyQueue LCFS i = LCFSQueue (DoubleLinkedList i) - - newStrategyQueue s = fmap LCFSQueue $ liftIO newList - - strategyQueueNull (LCFSQueue q) = liftIO $ listNull q - --- | An implementation of the 'LCFS' queue strategy. -instance DequeueStrategy LCFS where - - strategyDequeue (LCFSQueue q) = - liftIO $ - do i <- listFirst q - listRemoveFirst q - return i - --- | An implementation of the 'LCFS' queue strategy. -instance EnqueueStrategy LCFS where - - strategyEnqueue (LCFSQueue q) i = liftIO $ listInsertFirst q i - --- | An implementation of the 'StaticPriorities' queue strategy. -instance QueueStrategy StaticPriorities where - - -- | A queue used by the 'StaticPriorities' strategy. - newtype StrategyQueue StaticPriorities i = StaticPriorityQueue (PQ.PriorityQueue i) - - newStrategyQueue s = fmap StaticPriorityQueue $ liftIO PQ.newQueue - - strategyQueueNull (StaticPriorityQueue q) = liftIO $ PQ.queueNull q - --- | An implementation of the 'StaticPriorities' queue strategy. -instance DequeueStrategy StaticPriorities where - - strategyDequeue (StaticPriorityQueue q) = - liftIO $ - do (_, i) <- PQ.queueFront q - PQ.dequeue q - return i - --- | An implementation of the 'StaticPriorities' queue strategy. -instance PriorityQueueStrategy StaticPriorities Double where - - strategyEnqueueWithPriority (StaticPriorityQueue q) p i = liftIO $ PQ.enqueue q p i - --- | An implementation of the 'SIRO' queue strategy. -instance QueueStrategy SIRO where - - -- | A queue used by the 'SIRO' strategy. - newtype StrategyQueue SIRO i = SIROQueue (V.Vector i) - - newStrategyQueue s = fmap SIROQueue $ liftIO V.newVector - - strategyQueueNull (SIROQueue q) = - liftIO $ - do n <- V.vectorCount q - return (n == 0) - --- | An implementation of the 'SIRO' queue strategy. -instance DequeueStrategy SIRO where - - strategyDequeue (SIROQueue q) = - liftIO $ - do n <- V.vectorCount q - i <- getStdRandom (randomR (0, n - 1)) - x <- V.readVector q i - V.vectorDeleteAt q i - return x - --- | An implementation of the 'SIRO' queue strategy. -instance EnqueueStrategy SIRO where - - strategyEnqueue (SIROQueue q) i = liftIO $ V.appendVector q i ++{-# LANGUAGE TypeFamilies, MultiParamTypeClasses, FunctionalDependencies #-}++-- |+-- Module : Simulation.Aivika.QueueStrategy+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- This module defines the queue strategies.+--+module Simulation.Aivika.QueueStrategy where++import System.Random+import Control.Monad.Trans++import Simulation.Aivika.Simulation+import Simulation.Aivika.Event+import Simulation.Aivika.DoubleLinkedList+import qualified Simulation.Aivika.PriorityQueue as PQ+import qualified Simulation.Aivika.Vector as V++-- | Defines the basic queue strategy.+class QueueStrategy s where++ -- | A queue used by the strategy.+ data StrategyQueue s :: * -> *++ -- | Create a new queue by the specified strategy.+ newStrategyQueue :: s+ -- ^ the strategy+ -> Simulation (StrategyQueue s i)+ -- ^ a new queue++ -- | Test whether the queue is empty.+ strategyQueueNull :: StrategyQueue s i+ -- ^ the queue+ -> Event Bool+ -- ^ the result of the test++-- | Defines a strategy with support of the dequeuing operation.+class QueueStrategy s => DequeueStrategy s where++ -- | Dequeue the front element and return it.+ strategyDequeue :: StrategyQueue s i+ -- ^ the queue+ -> Event i+ -- ^ the dequeued element++-- | It defines a strategy when we can enqueue a single element.+class DequeueStrategy s => EnqueueStrategy s where++ -- | Enqueue an element.+ strategyEnqueue :: StrategyQueue s 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 => PriorityQueueStrategy s p | s -> p where++ -- | Enqueue an element with the specified priority.+ strategyEnqueueWithPriority :: StrategyQueue s 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 deriving (Eq, Ord, Show)++-- | Strategy: Last Come - First Served (LCFS)+data LCFS = LCFS deriving (Eq, Ord, Show)++-- | Strategy: Service in Random Order (SIRO).+data SIRO = SIRO deriving (Eq, Ord, Show)++-- | Strategy: Static Priorities. It uses the priority queue.+data StaticPriorities = StaticPriorities deriving (Eq, Ord, Show)++-- | An implementation of the 'FCFS' queue strategy.+instance QueueStrategy FCFS where++ -- | A queue used by the 'FCFS' strategy.+ newtype StrategyQueue FCFS i = FCFSQueue (DoubleLinkedList i)++ newStrategyQueue s = fmap FCFSQueue $ liftIO newList++ strategyQueueNull (FCFSQueue q) = liftIO $ listNull q++-- | An implementation of the 'FCFS' queue strategy.+instance DequeueStrategy FCFS where++ strategyDequeue (FCFSQueue q) =+ liftIO $+ do i <- listFirst q+ listRemoveFirst q+ return i++-- | An implementation of the 'FCFS' queue strategy.+instance EnqueueStrategy FCFS where++ strategyEnqueue (FCFSQueue q) i = liftIO $ listAddLast q i++-- | An implementation of the 'LCFS' queue strategy.+instance QueueStrategy LCFS where++ -- | A queue used by the 'LCFS' strategy.+ newtype StrategyQueue LCFS i = LCFSQueue (DoubleLinkedList i)+ + newStrategyQueue s = fmap LCFSQueue $ liftIO newList+ + strategyQueueNull (LCFSQueue q) = liftIO $ listNull q++-- | An implementation of the 'LCFS' queue strategy.+instance DequeueStrategy LCFS where++ strategyDequeue (LCFSQueue q) =+ liftIO $+ do i <- listFirst q+ listRemoveFirst q+ return i++-- | An implementation of the 'LCFS' queue strategy.+instance EnqueueStrategy LCFS where++ strategyEnqueue (LCFSQueue q) i = liftIO $ listInsertFirst q i++-- | An implementation of the 'StaticPriorities' queue strategy.+instance QueueStrategy StaticPriorities where++ -- | A queue used by the 'StaticPriorities' strategy.+ newtype StrategyQueue StaticPriorities i = StaticPriorityQueue (PQ.PriorityQueue i)+ + newStrategyQueue s = fmap StaticPriorityQueue $ liftIO PQ.newQueue++ strategyQueueNull (StaticPriorityQueue q) = liftIO $ PQ.queueNull q++-- | An implementation of the 'StaticPriorities' queue strategy.+instance DequeueStrategy StaticPriorities where++ strategyDequeue (StaticPriorityQueue q) =+ liftIO $+ do (_, i) <- PQ.queueFront q+ PQ.dequeue q+ return i++-- | An implementation of the 'StaticPriorities' queue strategy.+instance PriorityQueueStrategy StaticPriorities Double where++ strategyEnqueueWithPriority (StaticPriorityQueue q) p i = liftIO $ PQ.enqueue q p i++-- | An implementation of the 'SIRO' queue strategy.+instance QueueStrategy SIRO where++ -- | A queue used by the 'SIRO' strategy.+ newtype StrategyQueue SIRO i = SIROQueue (V.Vector i)++ newStrategyQueue s = fmap SIROQueue $ liftIO V.newVector++ strategyQueueNull (SIROQueue q) =+ liftIO $+ do n <- V.vectorCount q+ return (n == 0)++-- | An implementation of the 'SIRO' queue strategy.+instance DequeueStrategy SIRO where++ strategyDequeue (SIROQueue q) =+ liftIO $+ do n <- V.vectorCount q+ i <- getStdRandom (randomR (0, n - 1))+ x <- V.readVector q i+ V.vectorDeleteAt q i+ return x++-- | An implementation of the 'SIRO' queue strategy.+instance EnqueueStrategy SIRO where++ strategyEnqueue (SIROQueue q) i = liftIO $ V.appendVector q i
Simulation/Aivika/Ref.hs view
@@ -1,69 +1,69 @@- --- | --- Module : Simulation.Aivika.Ref --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- This module defines an updatable reference that depends on the event queue. --- -module Simulation.Aivika.Ref - (Ref, - refChanged, - refChanged_, - newRef, - readRef, - writeRef, - modifyRef) where - -import Data.IORef -import Control.Monad -import Control.Monad.Trans - -import Simulation.Aivika.Internal.Simulation -import Simulation.Aivika.Internal.Event -import Simulation.Aivika.Internal.Signal -import Simulation.Aivika.Signal - --- | The 'Ref' type represents a mutable variable similar to the 'IORef' variable --- but only dependent on the event queue, which allows synchronizing the reference --- with the model explicitly through the 'Event' monad. -data Ref a = - Ref { refValue :: IORef a, - refChangedSource :: SignalSource a } - --- | Create a new reference. -newRef :: a -> Simulation (Ref a) -newRef a = - do x <- liftIO $ newIORef a - s <- newSignalSource - return Ref { refValue = x, - refChangedSource = s } - --- | Read the value of a reference. -readRef :: Ref a -> Event a -readRef r = Event $ \p -> readIORef (refValue r) - --- | Write a new value into the reference. -writeRef :: Ref a -> a -> Event () -writeRef r a = Event $ \p -> - do a `seq` writeIORef (refValue r) a - invokeEvent p $ triggerSignal (refChangedSource r) a - --- | Mutate the contents of the reference. -modifyRef :: Ref a -> (a -> a) -> Event () -modifyRef r f = Event $ \p -> - do a <- readIORef (refValue r) - let b = f a - b `seq` writeIORef (refValue r) b - invokeEvent p $ triggerSignal (refChangedSource r) b - --- | Return a signal that notifies about every change of the reference state. -refChanged :: Ref a -> Signal a -refChanged v = publishSignal (refChangedSource v) - --- | Return a signal that notifies about every change of the reference state. -refChanged_ :: Ref a -> Signal () -refChanged_ r = mapSignal (const ()) $ refChanged r ++-- |+-- Module : Simulation.Aivika.Ref+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- This module defines an updatable reference that depends on the event queue.+--+module Simulation.Aivika.Ref+ (Ref,+ refChanged,+ refChanged_,+ newRef,+ readRef,+ writeRef,+ modifyRef) where++import Data.IORef+import Control.Monad+import Control.Monad.Trans++import Simulation.Aivika.Internal.Simulation+import Simulation.Aivika.Internal.Event+import Simulation.Aivika.Internal.Signal+import Simulation.Aivika.Signal++-- | The 'Ref' type represents a mutable variable similar to the 'IORef' variable +-- but only dependent on the event queue, which allows synchronizing the reference+-- with the model explicitly through the 'Event' monad.+data Ref a = + Ref { refValue :: IORef a, + refChangedSource :: SignalSource a }++-- | Create a new reference.+newRef :: a -> Simulation (Ref a)+newRef a =+ do x <- liftIO $ newIORef a+ s <- newSignalSource+ return Ref { refValue = x, + refChangedSource = s }+ +-- | Read the value of a reference.+readRef :: Ref a -> Event a+readRef r = Event $ \p -> readIORef (refValue r)++-- | Write a new value into the reference.+writeRef :: Ref a -> a -> Event ()+writeRef r a = Event $ \p -> + do a `seq` writeIORef (refValue r) a+ invokeEvent p $ triggerSignal (refChangedSource r) a++-- | Mutate the contents of the reference.+modifyRef :: Ref a -> (a -> a) -> Event ()+modifyRef r f = Event $ \p -> + do a <- readIORef (refValue r)+ let b = f a+ b `seq` writeIORef (refValue r) b+ invokeEvent p $ triggerSignal (refChangedSource r) b++-- | Return a signal that notifies about every change of the reference state.+refChanged :: Ref a -> Signal a+refChanged v = publishSignal (refChangedSource v)++-- | Return a signal that notifies about every change of the reference state.+refChanged_ :: Ref a -> Signal ()+refChanged_ r = mapSignal (const ()) $ refChanged r
Simulation/Aivika/Ref/Plain.hs view
@@ -1,53 +1,53 @@- --- | --- Module : Simulation.Aivika.Ref.Plain --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- This module defines a light-weight and more fast version of an updatable reference --- that depends on the event queue but that doesn't supply with the signal notification. --- -module Simulation.Aivika.Ref.Plain - (Ref, - newRef, - readRef, - writeRef, - modifyRef) where - -import Data.IORef -import Control.Monad -import Control.Monad.Trans - -import Simulation.Aivika.Internal.Simulation -import Simulation.Aivika.Internal.Event - --- | The 'Ref' type represents a mutable variable similar to the 'IORef' variable --- but only dependent on the event queue, which allows synchronizing the reference --- with the model explicitly through the 'Event' monad. -newtype Ref a = - Ref { refValue :: IORef a } - --- | Create a new reference. -newRef :: a -> Simulation (Ref a) -newRef a = - do x <- liftIO $ newIORef a - return Ref { refValue = x } - --- | Read the value of a reference. -readRef :: Ref a -> Event a -readRef r = Event $ \p -> readIORef (refValue r) - --- | Write a new value into the reference. -writeRef :: Ref a -> a -> Event () -writeRef r a = Event $ \p -> - a `seq` writeIORef (refValue r) a - --- | Mutate the contents of the reference. -modifyRef :: Ref a -> (a -> a) -> Event () -modifyRef r f = Event $ \p -> - do a <- readIORef (refValue r) - let b = f a - b `seq` writeIORef (refValue r) b ++-- |+-- Module : Simulation.Aivika.Ref.Plain+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- This module defines a light-weight and more fast version of an updatable reference+-- that depends on the event queue but that doesn't supply with the signal notification.+--+module Simulation.Aivika.Ref.Plain+ (Ref,+ newRef,+ readRef,+ writeRef,+ modifyRef) where++import Data.IORef+import Control.Monad+import Control.Monad.Trans++import Simulation.Aivika.Internal.Simulation+import Simulation.Aivika.Internal.Event++-- | The 'Ref' type represents a mutable variable similar to the 'IORef' variable +-- but only dependent on the event queue, which allows synchronizing the reference+-- with the model explicitly through the 'Event' monad.+newtype Ref a = + Ref { refValue :: IORef a }++-- | Create a new reference.+newRef :: a -> Simulation (Ref a)+newRef a =+ do x <- liftIO $ newIORef a+ return Ref { refValue = x }+ +-- | Read the value of a reference.+readRef :: Ref a -> Event a+readRef r = Event $ \p -> readIORef (refValue r)++-- | Write a new value into the reference.+writeRef :: Ref a -> a -> Event ()+writeRef r a = Event $ \p -> + a `seq` writeIORef (refValue r) a++-- | Mutate the contents of the reference.+modifyRef :: Ref a -> (a -> a) -> Event ()+modifyRef r f = Event $ \p -> + do a <- readIORef (refValue r)+ let b = f a+ b `seq` writeIORef (refValue r) b
Simulation/Aivika/Resource.hs view
@@ -1,335 +1,335 @@- --- | --- Module : Simulation.Aivika.Resource --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- 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 Types - FCFSResource, - LCFSResource, - SIROResource, - PriorityResource, - Resource, - -- * Creating Resource - newFCFSResource, - newFCFSResourceWithMaxCount, - newLCFSResource, - newLCFSResourceWithMaxCount, - newSIROResource, - newSIROResourceWithMaxCount, - newPriorityResource, - newPriorityResourceWithMaxCount, - newResource, - newResourceWithMaxCount, - -- * Resource Properties - resourceStrategy, - resourceMaxCount, - resourceCount, - -- * Requesting for and Releasing Resource - requestResource, - requestResourceWithPriority, - tryRequestResourceWithinEvent, - releaseResource, - releaseResourceWithinEvent, - usingResource, - 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 - -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 - --- | The ordinary LCFS (Last Come - First Serviced) resource. -type LCFSResource = Resource LCFS - --- | The SIRO (Serviced in Random Order) resource. -type SIROResource = Resource SIRO - --- | The resource with static priorities. -type PriorityResource = Resource StaticPriorities - --- | Represents the resource with strategy @s@ applied for queuing the requests. -data Resource s = - Resource { resourceStrategy :: s, - -- ^ 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 :: StrategyQueue s (Event (Maybe (ContParams ()))) } - -instance Eq (Resource s) where - x == y = resourceCountRef x == resourceCountRef y -- unique references - --- | 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 - => s - -- ^ the strategy for managing the queuing requests - -> Int - -- ^ the initial count (and maximal count too) of the resource - -> Simulation (Resource s) -newResource s count = - Simulation $ \r -> - 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 = Just count, - resourceCountRef = countRef, - resourceWaitList = waitList } - --- | 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 - => 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) -newResourceWithMaxCount s count maxCount = - Simulation $ \r -> - 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 -> Event Int -resourceCount r = - Event $ \p -> readIORef (resourceCountRef r) - --- | Request for the resource decreasing its count in case of success, --- otherwise suspending the discontinuous process until some other --- process releases the resource. -requestResource :: EnqueueStrategy s - => Resource s - -- ^ the requested resource - -> Process () -requestResource r = - Process $ \pid -> - Cont $ \c -> - Event $ \p -> - do a <- readIORef (resourceCountRef r) - if a == 0 - then do c <- invokeEvent p $ contFreeze c - invokeEvent p $ - strategyEnqueue (resourceWaitList r) c - else do let a' = a - 1 - a' `seq` writeIORef (resourceCountRef r) a' - invokeEvent p $ resumeCont c () - --- | 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 p - => Resource s - -- ^ the requested resource - -> p - -- ^ the priority - -> Process () -requestResourceWithPriority r priority = - Process $ \pid -> - Cont $ \c -> - Event $ \p -> - do a <- readIORef (resourceCountRef r) - if a == 0 - then do c <- invokeEvent p $ contFreeze c - invokeEvent p $ - strategyEnqueueWithPriority (resourceWaitList r) priority c - else do let a' = a - 1 - a' `seq` writeIORef (resourceCountRef r) a' - invokeEvent p $ resumeCont c () - --- | Release the resource increasing its count and resuming one of the --- previously suspended processes as possible. -releaseResource :: DequeueStrategy s - => Resource s - -- ^ the resource to release - -> Process () -releaseResource r = - Process $ \_ -> - Cont $ \c -> - Event $ \p -> - do invokeEvent p $ releaseResourceWithinEvent r - invokeEvent p $ resumeCont c () - --- | Release the resource increasing its count and resuming one of the --- previously suspended processes as possible. -releaseResourceWithinEvent :: DequeueStrategy s - => Resource s - -- ^ the resource to release - -> Event () -releaseResourceWithinEvent r = - Event $ \p -> - do a <- readIORef (resourceCountRef r) - let a' = a + 1 - 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 (resourceWaitList r) - if f - then a' `seq` writeIORef (resourceCountRef r) a' - else do c <- invokeEvent p $ - strategyDequeue (resourceWaitList r) - 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'. -tryRequestResourceWithinEvent :: Resource s - -- ^ the resource which we try to request for - -> Event Bool -tryRequestResourceWithinEvent r = - Event $ \p -> - do a <- readIORef (resourceCountRef r) - if a == 0 - then return False - else do let a' = a - 1 - a' `seq` writeIORef (resourceCountRef r) a' - return True - --- | Acquire the resource, perform some action and safely release the resource --- in the end, even if the 'IOException' was raised within the action. -usingResource :: EnqueueStrategy s - => Resource s - -- ^ the resource we are going to request for and then release in the end - -> Process a - -- ^ the action we are going to apply having the resource - -> Process a - -- ^ the result of the action -usingResource r m = - do requestResource r - finallyProcess m $ releaseResource r - --- | 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. -usingResourceWithPriority :: PriorityQueueStrategy s p - => Resource s - -- ^ the resource we are going to request for and then - -- release in the end - -> p - -- ^ the priority - -> Process a - -- ^ the action we are going to apply having the resource - -> Process a - -- ^ the result of the action -usingResourceWithPriority r priority m = - do requestResourceWithPriority r priority - finallyProcess m $ releaseResource r ++-- |+-- Module : Simulation.Aivika.Resource+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- 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 Types+ FCFSResource,+ LCFSResource,+ SIROResource,+ PriorityResource,+ Resource,+ -- * Creating Resource+ newFCFSResource,+ newFCFSResourceWithMaxCount,+ newLCFSResource,+ newLCFSResourceWithMaxCount,+ newSIROResource,+ newSIROResourceWithMaxCount,+ newPriorityResource,+ newPriorityResourceWithMaxCount,+ newResource,+ newResourceWithMaxCount,+ -- * Resource Properties+ resourceStrategy,+ resourceMaxCount,+ resourceCount,+ -- * Requesting for and Releasing Resource+ requestResource,+ requestResourceWithPriority,+ tryRequestResourceWithinEvent,+ releaseResource,+ releaseResourceWithinEvent,+ usingResource,+ 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++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++-- | The ordinary LCFS (Last Come - First Serviced) resource.+type LCFSResource = Resource LCFS++-- | The SIRO (Serviced in Random Order) resource.+type SIROResource = Resource SIRO++-- | The resource with static priorities.+type PriorityResource = Resource StaticPriorities++-- | Represents the resource with strategy @s@ applied for queuing the requests.+data Resource s = + Resource { resourceStrategy :: s,+ -- ^ 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 :: StrategyQueue s (Event (Maybe (ContParams ()))) }++instance Eq (Resource s) where+ x == y = resourceCountRef x == resourceCountRef y -- unique references++-- | 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+ => s+ -- ^ the strategy for managing the queuing requests+ -> Int+ -- ^ the initial count (and maximal count too) of the resource+ -> Simulation (Resource s)+newResource s count =+ Simulation $ \r ->+ 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 = Just count,+ resourceCountRef = countRef,+ resourceWaitList = waitList }++-- | 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+ => 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)+newResourceWithMaxCount s count maxCount =+ Simulation $ \r ->+ 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 -> Event Int+resourceCount r =+ Event $ \p -> readIORef (resourceCountRef r)++-- | Request for the resource decreasing its count in case of success,+-- otherwise suspending the discontinuous process until some other +-- process releases the resource.+requestResource :: EnqueueStrategy s+ => Resource s+ -- ^ the requested resource+ -> Process ()+requestResource r =+ Process $ \pid ->+ Cont $ \c ->+ Event $ \p ->+ do a <- readIORef (resourceCountRef r)+ if a == 0 + then do c <- invokeEvent p $ contFreeze c+ invokeEvent p $+ strategyEnqueue (resourceWaitList r) c+ else do let a' = a - 1+ a' `seq` writeIORef (resourceCountRef r) a'+ invokeEvent p $ resumeCont c ()++-- | 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 p+ => Resource s+ -- ^ the requested resource+ -> p+ -- ^ the priority+ -> Process ()+requestResourceWithPriority r priority =+ Process $ \pid ->+ Cont $ \c ->+ Event $ \p ->+ do a <- readIORef (resourceCountRef r)+ if a == 0 + then do c <- invokeEvent p $ contFreeze c+ invokeEvent p $+ strategyEnqueueWithPriority (resourceWaitList r) priority c+ else do let a' = a - 1+ a' `seq` writeIORef (resourceCountRef r) a'+ invokeEvent p $ resumeCont c ()++-- | Release the resource increasing its count and resuming one of the+-- previously suspended processes as possible.+releaseResource :: DequeueStrategy s+ => Resource s+ -- ^ the resource to release+ -> Process ()+releaseResource r = + Process $ \_ ->+ Cont $ \c ->+ Event $ \p ->+ do invokeEvent p $ releaseResourceWithinEvent r+ invokeEvent p $ resumeCont c ()++-- | Release the resource increasing its count and resuming one of the+-- previously suspended processes as possible.+releaseResourceWithinEvent :: DequeueStrategy s+ => Resource s+ -- ^ the resource to release+ -> Event ()+releaseResourceWithinEvent r =+ Event $ \p ->+ do a <- readIORef (resourceCountRef r)+ let a' = a + 1+ 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 (resourceWaitList r)+ if f + then a' `seq` writeIORef (resourceCountRef r) a'+ else do c <- invokeEvent p $+ strategyDequeue (resourceWaitList r)+ 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'.+tryRequestResourceWithinEvent :: Resource s+ -- ^ the resource which we try to request for+ -> Event Bool+tryRequestResourceWithinEvent r =+ Event $ \p ->+ do a <- readIORef (resourceCountRef r)+ if a == 0 + then return False+ else do let a' = a - 1+ a' `seq` writeIORef (resourceCountRef r) a'+ return True+ +-- | Acquire the resource, perform some action and safely release the resource +-- in the end, even if the 'IOException' was raised within the action. +usingResource :: EnqueueStrategy s+ => Resource s+ -- ^ the resource we are going to request for and then release in the end+ -> Process a+ -- ^ the action we are going to apply having the resource+ -> Process a+ -- ^ the result of the action+usingResource r m =+ do requestResource r+ finallyProcess m $ releaseResource r++-- | 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.+usingResourceWithPriority :: PriorityQueueStrategy s p+ => Resource s+ -- ^ the resource we are going to request for and then+ -- release in the end+ -> p+ -- ^ the priority+ -> Process a+ -- ^ the action we are going to apply having the resource+ -> Process a+ -- ^ the result of the action+usingResourceWithPriority r priority m =+ do requestResourceWithPriority r priority+ finallyProcess m $ releaseResource r
Simulation/Aivika/Results.hs view
@@ -1,1884 +1,1931 @@- -{-# LANGUAGE CPP, FlexibleContexts, FlexibleInstances, UndecidableInstances, ExistentialQuantification #-} - --- | --- Module : Simulation.Aivika.Results --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- The module allows exporting the simulation results from the model. --- -module Simulation.Aivika.Results - (-- * Definitions Focused on Modeling - Results, - ResultTransform, - ResultName, - ResultProvider(..), - results, - expandResults, - resultSummary, - resultByName, - resultByProperty, - resultById, - resultByIndex, - resultBySubscript, - ResultComputing(..), - ResultComputation(..), - ResultListWithSubscript(..), - ResultArrayWithSubscript(..), -#ifndef __HASTE__ - ResultVectorWithSubscript(..), -#endif - -- * Definitions Focused on Using the Library - ResultExtract(..), - extractIntResults, - extractIntListResults, - extractIntStatsResults, - extractIntStatsEitherResults, - extractIntTimingStatsResults, - extractDoubleResults, - extractDoubleListResults, - extractDoubleStatsResults, - extractDoubleStatsEitherResults, - extractDoubleTimingStatsResults, - extractStringResults, - ResultPredefinedSignals(..), - newResultPredefinedSignals, - resultSignal, - pureResultSignal, - -- * Definitions Focused on Extending the Library - ResultSourceMap, - ResultSource(..), - ResultItem(..), - ResultItemable(..), - resultItemToIntStatsEitherValue, - resultItemToDoubleStatsEitherValue, - ResultObject(..), - ResultProperty(..), - ResultVector(..), - memoResultVectorSignal, - memoResultVectorSummary, - ResultSeparator(..), - ResultValue(..), - voidResultValue, - ResultContainer(..), - resultContainerPropertySource, - resultContainerConstProperty, - resultContainerIntegProperty, - resultContainerProperty, - resultContainerMapProperty, - resultValueToContainer, - resultContainerToValue, - ResultData, - ResultSignal(..), - maybeResultSignal, - textResultSource, - timeResultSource, - resultSourceToIntValues, - resultSourceToIntListValues, - resultSourceToIntStatsValues, - resultSourceToIntStatsEitherValues, - resultSourceToIntTimingStatsValues, - resultSourceToDoubleValues, - resultSourceToDoubleListValues, - resultSourceToDoubleStatsValues, - resultSourceToDoubleStatsEitherValues, - resultSourceToDoubleTimingStatsValues, - resultSourceToStringValues, - resultSourceMap, - resultSourceList, - resultsToIntValues, - resultsToIntListValues, - resultsToIntStatsValues, - resultsToIntStatsEitherValues, - resultsToIntTimingStatsValues, - resultsToDoubleValues, - resultsToDoubleListValues, - resultsToDoubleStatsValues, - resultsToDoubleStatsEitherValues, - resultsToDoubleTimingStatsValues, - resultsToStringValues, - composeResults, - computeResultValue) where - -import Control.Monad -import Control.Monad.Trans - -import qualified Data.Map as M -import qualified Data.Array as A - -#ifndef __HASTE__ -import qualified Data.Vector as V -#endif - -import Data.Ix -import Data.Maybe -import Data.Monoid - -import Simulation.Aivika.Parameter -import Simulation.Aivika.Simulation -import Simulation.Aivika.Dynamics -import Simulation.Aivika.Event -import Simulation.Aivika.Signal -import Simulation.Aivika.Statistics -import Simulation.Aivika.Statistics.Accumulator -import Simulation.Aivika.Ref -import qualified Simulation.Aivika.Ref.Plain as LR -import Simulation.Aivika.Var -import Simulation.Aivika.QueueStrategy -import qualified Simulation.Aivika.Queue as Q -import qualified Simulation.Aivika.Queue.Infinite as IQ -import Simulation.Aivika.Arrival -import Simulation.Aivika.Server -import Simulation.Aivika.Results.Locale - --- | A name used for indentifying the results when generating output. -type ResultName = String - --- | Represents a provider of the simulation results. It is usually something, or --- an array of something, or a list of such values which can be simulated to get data. -class ResultProvider p where - - -- | Return the source of simulation results by the specified name, description and provider. - resultSource :: ResultName -> ResultDescription -> p -> ResultSource - resultSource name descr = resultSource' name (UserDefinedResultId descr) - - -- | Return the source of simulation results by the specified name, identifier and provider. - resultSource' :: ResultName -> ResultId -> p -> ResultSource - --- | It associates the result sources with their names. -type ResultSourceMap = M.Map ResultName ResultSource - --- | Encapsulates the result source. -data ResultSource = ResultItemSource ResultItem - -- ^ The source consisting of a single item. - | ResultObjectSource ResultObject - -- ^ An object-like source. - | ResultVectorSource ResultVector - -- ^ A vector-like structure. - | ResultSeparatorSource ResultSeparator - -- ^ This is a separator text. - --- | The simulation results represented by a single item. -data ResultItem = forall a. ResultItemable a => ResultItem a - --- | Represents a type class for actual representing the items. -class ResultItemable a where - - -- | The item name. - resultItemName :: a -> ResultName - - -- | The item identifier. - resultItemId :: a -> ResultId - - -- | Whether the item emits a signal. - resultItemSignal :: a -> ResultSignal - - -- | Return an expanded version of the item, for example, - -- when the statistics item is exanded to an object - -- having the corresponded properties for count, average, - -- deviation, minimum, maximum and so on. - resultItemExpansion :: a -> ResultSource - - -- | Return usually a short version of the item, i.e. its summary, - -- but values of some data types such as statistics can be - -- implicitly expanded to an object with the corresponded - -- properties. - resultItemSummary :: a -> ResultSource - - -- | Return integer numbers in time points. - resultItemToIntValue :: a -> ResultValue Int - - -- | Return lists of integer numbers in time points. - resultItemToIntListValue :: a -> ResultValue [Int] - - -- | Return statistics based on integer numbers. - resultItemToIntStatsValue :: a -> ResultValue (SamplingStats Int) - - -- | Return timing statistics based on integer numbers. - resultItemToIntTimingStatsValue :: a -> ResultValue (TimingStats Int) - - -- | Return double numbers in time points. - resultItemToDoubleValue :: a -> ResultValue Double - - -- | Return lists of double numbers in time points. - resultItemToDoubleListValue :: a -> ResultValue [Double] - - -- | Return statistics based on double numbers. - resultItemToDoubleStatsValue :: a -> ResultValue (SamplingStats Double) - - -- | Return timing statistics based on integer numbers. - resultItemToDoubleTimingStatsValue :: a -> ResultValue (TimingStats Double) - - -- | Return string representations in time points. - resultItemToStringValue :: a -> ResultValue String - --- | Return a version optimised for fast aggregation of the statistics based on integer numbers. -resultItemToIntStatsEitherValue :: ResultItemable a => a -> ResultValue (Either Int (SamplingStats Int)) -resultItemToIntStatsEitherValue x = - case resultValueData x1 of - Just a1 -> fmap Left x1 - Nothing -> - case resultValueData x2 of - Just a2 -> fmap Right x2 - Nothing -> voidResultValue x2 - where - x1 = resultItemToIntValue x - x2 = resultItemToIntStatsValue x - --- | Return a version optimised for fast aggregation of the statistics based on double floating point numbers. -resultItemToDoubleStatsEitherValue :: ResultItemable a => a -> ResultValue (Either Double (SamplingStats Double)) -resultItemToDoubleStatsEitherValue x = - case resultValueData x1 of - Just a1 -> fmap Left x1 - Nothing -> - case resultValueData x2 of - Just a2 -> fmap Right x2 - Nothing -> voidResultValue x2 - where - x1 = resultItemToDoubleValue x - x2 = resultItemToDoubleStatsValue x - --- | The simulation results represented by an object having properties. -data ResultObject = - ResultObject { resultObjectName :: ResultName, - -- ^ The object name. - resultObjectId :: ResultId, - -- ^ The object identifier. - resultObjectTypeId :: ResultId, - -- ^ The object type identifier. - resultObjectProperties :: [ResultProperty], - -- ^ The object properties. - resultObjectSignal :: ResultSignal, - -- ^ A combined signal if present. - resultObjectSummary :: ResultSource - -- ^ A short version of the object, i.e. its summary. - } - --- | The object property containing the simulation results. -data ResultProperty = - ResultProperty { resultPropertyLabel :: ResultName, - -- ^ The property short label. - resultPropertyId :: ResultId, - -- ^ The property identifier. - resultPropertySource :: ResultSource - -- ^ The simulation results supplied by the property. - } - --- | The simulation results represented by a vector. -data ResultVector = - ResultVector { resultVectorName :: ResultName, - -- ^ The vector name. - resultVectorId :: ResultId, - -- ^ The vector identifier. - resultVectorItems :: A.Array Int ResultSource, - -- ^ The results supplied by the vector items. - resultVectorSubscript :: A.Array Int ResultName, - -- ^ The subscript used as a suffix to create item names. - resultVectorSignal :: ResultSignal, - -- ^ A combined signal if present. - resultVectorSummary :: ResultSource - -- ^ A short version of the vector, i.e. summary. - } - --- | Calculate the result vector signal and memoize it in a new vector. -memoResultVectorSignal :: ResultVector -> ResultVector -memoResultVectorSignal x = - x { resultVectorSignal = - foldr (<>) mempty $ map resultSourceSignal $ A.elems $ resultVectorItems x } - --- | Calculate the result vector summary and memoize it in a new vector. -memoResultVectorSummary :: ResultVector -> ResultVector -memoResultVectorSummary x = - x { resultVectorSummary = - ResultVectorSource $ - x { resultVectorItems = - A.array bnds [(i, resultSourceSummary e) | (i, e) <- ies] } } - where - arr = resultVectorItems x - bnds = A.bounds arr - ies = A.assocs arr - --- | It separates the simulation results when printing. -data ResultSeparator = - ResultSeparator { resultSeparatorText :: String - -- ^ The separator text. - } - --- | A parameterised value that actually represents a generalised result item that have no parametric type. -data ResultValue e = - ResultValue { resultValueName :: ResultName, - -- ^ The value name. - resultValueId :: ResultId, - -- ^ The value identifier. - resultValueData :: ResultData e, - -- ^ Simulation data supplied by the value. - resultValueSignal :: ResultSignal - -- ^ Whether the value emits a signal when changing simulation data. - } - -instance Functor ResultValue where - fmap f x = x { resultValueData = fmap (fmap f) (resultValueData x) } - --- | Return a new value with the discarded simulation results. -voidResultValue :: ResultValue a -> ResultValue b -voidResultValue x = x { resultValueData = Nothing } - --- | A container of the simulation results such as queue, server or array. -data ResultContainer e = - ResultContainer { resultContainerName :: ResultName, - -- ^ The container name. - resultContainerId :: ResultId, - -- ^ The container identifier. - resultContainerData :: e, - -- ^ The container data. - resultContainerSignal :: ResultSignal - -- ^ Whether the container emits a signal when changing simulation data. - } - -instance Functor ResultContainer where - fmap f x = x { resultContainerData = f (resultContainerData x) } - --- | Create a new property source by the specified container. -resultContainerPropertySource :: ResultItemable (ResultValue b) - => ResultContainer a - -- ^ the container - -> ResultName - -- ^ the property label - -> ResultId - -- ^ the property identifier - -> (a -> ResultData b) - -- ^ get the specified data from the container - -> (a -> ResultSignal) - -- ^ get the data signal from the container - -> ResultSource -resultContainerPropertySource cont name i f g = - ResultItemSource $ - ResultItem $ - ResultValue { - resultValueName = (resultContainerName cont) ++ "." ++ name, - resultValueId = i, - resultValueData = f (resultContainerData cont), - resultValueSignal = g (resultContainerData cont) } - --- | Create a constant property by the specified container. -resultContainerConstProperty :: ResultItemable (ResultValue b) - => ResultContainer a - -- ^ the container - -> ResultName - -- ^ the property label - -> ResultId - -- ^ the property identifier - -> (a -> b) - -- ^ get the specified data from the container - -> ResultProperty -resultContainerConstProperty cont name i f = - ResultProperty { - resultPropertyLabel = name, - resultPropertyId = i, - resultPropertySource = - resultContainerPropertySource cont name i (Just . return . f) (const EmptyResultSignal) } - --- | Create by the specified container a property that changes in the integration time points, or it is supposed to be such one. -resultContainerIntegProperty :: ResultItemable (ResultValue b) - => ResultContainer a - -- ^ the container - -> ResultName - -- ^ the property label - -> ResultId - -- ^ the property identifier - -> (a -> Event b) - -- ^ get the specified data from the container - -> ResultProperty -resultContainerIntegProperty cont name i f = - ResultProperty { - resultPropertyLabel = name, - resultPropertyId = i, - resultPropertySource = - resultContainerPropertySource cont name i (Just . f) (const UnknownResultSignal) } - --- | Create a property by the specified container. -resultContainerProperty :: ResultItemable (ResultValue b) - => ResultContainer a - -- ^ the container - -> ResultName - -- ^ the property label - -> ResultId - -- ^ the property identifier - -> (a -> Event b) - -- ^ get the specified data from the container - -> (a -> Signal ()) - -- ^ get a signal triggered when changing data. - -> ResultProperty -resultContainerProperty cont name i f g = - ResultProperty { - resultPropertyLabel = name, - resultPropertyId = i, - resultPropertySource = - resultContainerPropertySource cont name i (Just . f) (ResultSignal . g) } - --- | Create by the specified container a mapped property which is recomputed each time again and again. -resultContainerMapProperty :: ResultItemable (ResultValue b) - => ResultContainer (ResultData a) - -- ^ the container - -> ResultName - -- ^ the property label - -> ResultId - -- ^ the property identifier - -> (a -> b) - -- ^ recompute the specified data - -> ResultProperty -resultContainerMapProperty cont name i f = - ResultProperty { - resultPropertyLabel = name, - resultPropertyId = i, - resultPropertySource = - resultContainerPropertySource cont name i (fmap $ fmap f) (const $ resultContainerSignal cont) } - --- | Convert the result value to a container with the specified object identifier. -resultValueToContainer :: ResultValue a -> ResultContainer (ResultData a) -resultValueToContainer x = - ResultContainer { - resultContainerName = resultValueName x, - resultContainerId = resultValueId x, - resultContainerData = resultValueData x, - resultContainerSignal = resultValueSignal x } - --- | Convert the result container to a value. -resultContainerToValue :: ResultContainer (ResultData a) -> ResultValue a -resultContainerToValue x = - ResultValue { - resultValueName = resultContainerName x, - resultValueId = resultContainerId x, - resultValueData = resultContainerData x, - resultValueSignal = resultContainerSignal x } - --- | Represents the very simulation results. -type ResultData e = Maybe (Event e) - --- | Whether an object containing the results emits a signal notifying about change of data. -data ResultSignal = EmptyResultSignal - -- ^ There is no signal at all. - | UnknownResultSignal - -- ^ The signal is unknown, but the entity probably changes. - | ResultSignal (Signal ()) - -- ^ When the signal is precisely specified. - | ResultSignalMix (Signal ()) - -- ^ When the specified signal was combined with unknown signal. - -instance Monoid ResultSignal where - - mempty = EmptyResultSignal - - mappend EmptyResultSignal z = z - - mappend UnknownResultSignal EmptyResultSignal = UnknownResultSignal - mappend UnknownResultSignal UnknownResultSignal = UnknownResultSignal - mappend UnknownResultSignal (ResultSignal x) = ResultSignalMix x - mappend UnknownResultSignal z@(ResultSignalMix x) = z - - mappend z@(ResultSignal x) EmptyResultSignal = z - mappend (ResultSignal x) UnknownResultSignal = ResultSignalMix x - mappend (ResultSignal x) (ResultSignal y) = ResultSignal (x <> y) - mappend (ResultSignal x) (ResultSignalMix y) = ResultSignalMix (x <> y) - - mappend z@(ResultSignalMix x) EmptyResultSignal = z - mappend z@(ResultSignalMix x) UnknownResultSignal = z - mappend (ResultSignalMix x) (ResultSignal y) = ResultSignalMix (x <> y) - mappend (ResultSignalMix x) (ResultSignalMix y) = ResultSignalMix (x <> y) - --- | Construct a new result signal by the specified optional pure signal. -maybeResultSignal :: Maybe (Signal ()) -> ResultSignal -maybeResultSignal (Just x) = ResultSignal x -maybeResultSignal Nothing = EmptyResultSignal - -instance ResultItemable (ResultValue Int) where - - resultItemName = resultValueName - resultItemId = resultValueId - resultItemSignal = resultValueSignal - - resultItemToIntValue = id - resultItemToIntListValue = fmap return - resultItemToIntStatsValue = fmap returnSamplingStats - resultItemToIntTimingStatsValue = voidResultValue - - resultItemToDoubleValue = fmap fromIntegral - resultItemToDoubleListValue = fmap (return . fromIntegral) - resultItemToDoubleStatsValue = fmap (returnSamplingStats . fromIntegral) - resultItemToDoubleTimingStatsValue = voidResultValue - - resultItemToStringValue = fmap show - - resultItemExpansion = ResultItemSource . ResultItem - resultItemSummary = ResultItemSource . ResultItem - -instance ResultItemable (ResultValue Double) where - - resultItemName = resultValueName - resultItemId = resultValueId - resultItemSignal = resultValueSignal - - resultItemToIntValue = voidResultValue - resultItemToIntListValue = voidResultValue - resultItemToIntStatsValue = voidResultValue - resultItemToIntTimingStatsValue = voidResultValue - - resultItemToDoubleValue = id - resultItemToDoubleListValue = fmap return - resultItemToDoubleStatsValue = fmap returnSamplingStats - resultItemToDoubleTimingStatsValue = voidResultValue - - resultItemToStringValue = fmap show - - resultItemExpansion = ResultItemSource . ResultItem - resultItemSummary = ResultItemSource . ResultItem - -instance ResultItemable (ResultValue [Int]) where - - resultItemName = resultValueName - resultItemId = resultValueId - resultItemSignal = resultValueSignal - - resultItemToIntValue = voidResultValue - resultItemToIntListValue = id - resultItemToIntStatsValue = fmap listSamplingStats - resultItemToIntTimingStatsValue = voidResultValue - - resultItemToDoubleValue = voidResultValue - resultItemToDoubleListValue = fmap (map fromIntegral) - resultItemToDoubleStatsValue = fmap (fromIntSamplingStats . listSamplingStats) - resultItemToDoubleTimingStatsValue = voidResultValue - - resultItemToStringValue = fmap show - - resultItemExpansion = ResultItemSource . ResultItem - resultItemSummary = ResultItemSource . ResultItem - -instance ResultItemable (ResultValue [Double]) where - - resultItemName = resultValueName - resultItemId = resultValueId - resultItemSignal = resultValueSignal - - resultItemToIntValue = voidResultValue - resultItemToIntListValue = voidResultValue - resultItemToIntStatsValue = voidResultValue - resultItemToIntTimingStatsValue = voidResultValue - - resultItemToDoubleValue = voidResultValue - resultItemToDoubleListValue = id - resultItemToDoubleStatsValue = fmap listSamplingStats - resultItemToDoubleTimingStatsValue = voidResultValue - - resultItemToStringValue = fmap show - - resultItemExpansion = ResultItemSource . ResultItem - resultItemSummary = ResultItemSource . ResultItem - -instance ResultItemable (ResultValue (SamplingStats Int)) where - - resultItemName = resultValueName - resultItemId = resultValueId - resultItemSignal = resultValueSignal - - resultItemToIntValue = voidResultValue - resultItemToIntListValue = voidResultValue - resultItemToIntStatsValue = id - resultItemToIntTimingStatsValue = voidResultValue - - resultItemToDoubleValue = voidResultValue - resultItemToDoubleListValue = voidResultValue - resultItemToDoubleStatsValue = fmap fromIntSamplingStats - resultItemToDoubleTimingStatsValue = voidResultValue - - resultItemToStringValue = fmap show - - resultItemExpansion = samplingStatsResultSource - resultItemSummary = samplingStatsResultSummary - -instance ResultItemable (ResultValue (SamplingStats Double)) where - - resultItemName = resultValueName - resultItemId = resultValueId - resultItemSignal = resultValueSignal - - resultItemToIntValue = voidResultValue - resultItemToIntListValue = voidResultValue - resultItemToIntStatsValue = voidResultValue - resultItemToIntTimingStatsValue = voidResultValue - - resultItemToDoubleValue = voidResultValue - resultItemToDoubleListValue = voidResultValue - resultItemToDoubleStatsValue = id - resultItemToDoubleTimingStatsValue = voidResultValue - - resultItemToStringValue = fmap show - - resultItemExpansion = samplingStatsResultSource - resultItemSummary = samplingStatsResultSummary - -instance ResultItemable (ResultValue (TimingStats Int)) where - - resultItemName = resultValueName - resultItemId = resultValueId - resultItemSignal = resultValueSignal - - resultItemToIntValue = voidResultValue - resultItemToIntListValue = voidResultValue - resultItemToIntStatsValue = voidResultValue - resultItemToIntTimingStatsValue = id - - resultItemToDoubleValue = voidResultValue - resultItemToDoubleListValue = voidResultValue - resultItemToDoubleStatsValue = voidResultValue - resultItemToDoubleTimingStatsValue = fmap fromIntTimingStats - - resultItemToStringValue = fmap show - - resultItemExpansion = timingStatsResultSource - resultItemSummary = timingStatsResultSummary - -instance ResultItemable (ResultValue (TimingStats Double)) where - - resultItemName = resultValueName - resultItemId = resultValueId - resultItemSignal = resultValueSignal - - resultItemToIntValue = voidResultValue - resultItemToIntListValue = voidResultValue - resultItemToIntStatsValue = voidResultValue - resultItemToIntTimingStatsValue = voidResultValue - - resultItemToDoubleValue = voidResultValue - resultItemToDoubleListValue = voidResultValue - resultItemToDoubleStatsValue = voidResultValue - resultItemToDoubleTimingStatsValue = id - - resultItemToStringValue = fmap show - - resultItemExpansion = timingStatsResultSource - resultItemSummary = timingStatsResultSummary - -instance ResultItemable (ResultValue Bool) where - - resultItemName = resultValueName - resultItemId = resultValueId - resultItemSignal = resultValueSignal - - resultItemToIntValue = voidResultValue - resultItemToIntListValue = voidResultValue - resultItemToIntStatsValue = voidResultValue - resultItemToIntTimingStatsValue = voidResultValue - - resultItemToDoubleValue = voidResultValue - resultItemToDoubleListValue = voidResultValue - resultItemToDoubleStatsValue = voidResultValue - resultItemToDoubleTimingStatsValue = voidResultValue - - resultItemToStringValue = fmap show - - resultItemExpansion = ResultItemSource . ResultItem - resultItemSummary = ResultItemSource . ResultItem - -instance ResultItemable (ResultValue String) where - - resultItemName = resultValueName - resultItemId = resultValueId - resultItemSignal = resultValueSignal - - resultItemToIntValue = voidResultValue - resultItemToIntListValue = voidResultValue - resultItemToIntStatsValue = voidResultValue - resultItemToIntTimingStatsValue = voidResultValue - - resultItemToDoubleValue = voidResultValue - resultItemToDoubleListValue = voidResultValue - resultItemToDoubleStatsValue = voidResultValue - resultItemToDoubleTimingStatsValue = voidResultValue - - resultItemToStringValue = fmap show - - resultItemExpansion = ResultItemSource . ResultItem - resultItemSummary = ResultItemSource . ResultItem - -instance ResultItemable (ResultValue ()) where - - resultItemName = resultValueName - resultItemId = resultValueId - resultItemSignal = resultValueSignal - - resultItemToIntValue = voidResultValue - resultItemToIntListValue = voidResultValue - resultItemToIntStatsValue = voidResultValue - resultItemToIntTimingStatsValue = voidResultValue - - resultItemToDoubleValue = voidResultValue - resultItemToDoubleListValue = voidResultValue - resultItemToDoubleStatsValue = voidResultValue - resultItemToDoubleTimingStatsValue = voidResultValue - - resultItemToStringValue = fmap show - - resultItemExpansion = ResultItemSource . ResultItem - resultItemSummary = ResultItemSource . ResultItem - -instance ResultItemable (ResultValue FCFS) where - - resultItemName = resultValueName - resultItemId = resultValueId - resultItemSignal = resultValueSignal - - resultItemToIntValue = voidResultValue - resultItemToIntListValue = voidResultValue - resultItemToIntStatsValue = voidResultValue - resultItemToIntTimingStatsValue = voidResultValue - - resultItemToDoubleValue = voidResultValue - resultItemToDoubleListValue = voidResultValue - resultItemToDoubleStatsValue = voidResultValue - resultItemToDoubleTimingStatsValue = voidResultValue - - resultItemToStringValue = fmap show - - resultItemExpansion = ResultItemSource . ResultItem - resultItemSummary = ResultItemSource . ResultItem - -instance ResultItemable (ResultValue LCFS) where - - resultItemName = resultValueName - resultItemId = resultValueId - resultItemSignal = resultValueSignal - - resultItemToIntValue = voidResultValue - resultItemToIntListValue = voidResultValue - resultItemToIntStatsValue = voidResultValue - resultItemToIntTimingStatsValue = voidResultValue - - resultItemToDoubleValue = voidResultValue - resultItemToDoubleListValue = voidResultValue - resultItemToDoubleStatsValue = voidResultValue - resultItemToDoubleTimingStatsValue = voidResultValue - - resultItemToStringValue = fmap show - - resultItemExpansion = ResultItemSource . ResultItem - resultItemSummary = ResultItemSource . ResultItem - -instance ResultItemable (ResultValue SIRO) where - - resultItemName = resultValueName - resultItemId = resultValueId - resultItemSignal = resultValueSignal - - resultItemToIntValue = voidResultValue - resultItemToIntListValue = voidResultValue - resultItemToIntStatsValue = voidResultValue - resultItemToIntTimingStatsValue = voidResultValue - - resultItemToDoubleValue = voidResultValue - resultItemToDoubleListValue = voidResultValue - resultItemToDoubleStatsValue = voidResultValue - resultItemToDoubleTimingStatsValue = voidResultValue - - resultItemToStringValue = fmap show - - resultItemExpansion = ResultItemSource . ResultItem - resultItemSummary = ResultItemSource . ResultItem - -instance ResultItemable (ResultValue StaticPriorities) where - - resultItemName = resultValueName - resultItemId = resultValueId - resultItemSignal = resultValueSignal - - resultItemToIntValue = voidResultValue - resultItemToIntListValue = voidResultValue - resultItemToIntStatsValue = voidResultValue - resultItemToIntTimingStatsValue = voidResultValue - - resultItemToDoubleValue = voidResultValue - resultItemToDoubleListValue = voidResultValue - resultItemToDoubleStatsValue = voidResultValue - resultItemToDoubleTimingStatsValue = voidResultValue - - resultItemToStringValue = fmap show - - resultItemExpansion = ResultItemSource . ResultItem - resultItemSummary = ResultItemSource . ResultItem - --- | Flatten the result source. -flattenResultSource :: ResultSource -> [ResultItem] -flattenResultSource (ResultItemSource x) = [x] -flattenResultSource (ResultObjectSource x) = - concat $ map (flattenResultSource . resultPropertySource) $ resultObjectProperties x -flattenResultSource (ResultVectorSource x) = - concat $ map flattenResultSource $ A.elems $ resultVectorItems x -flattenResultSource (ResultSeparatorSource x) = [] - --- | Return the result source name. -resultSourceName :: ResultSource -> ResultName -resultSourceName (ResultItemSource (ResultItem x)) = resultItemName x -resultSourceName (ResultObjectSource x) = resultObjectName x -resultSourceName (ResultVectorSource x) = resultVectorName x -resultSourceName (ResultSeparatorSource x) = [] - --- | Expand the result source returning a more detailed version expanding the properties as possible. -expandResultSource :: ResultSource -> ResultSource -expandResultSource (ResultItemSource (ResultItem x)) = resultItemExpansion x -expandResultSource (ResultObjectSource x) = - ResultObjectSource $ - x { resultObjectProperties = - flip fmap (resultObjectProperties x) $ \p -> - p { resultPropertySource = expandResultSource (resultPropertySource p) } } -expandResultSource (ResultVectorSource x) = - ResultVectorSource $ - x { resultVectorItems = - A.array bnds [(i, expandResultSource e) | (i, e) <- ies] } - where arr = resultVectorItems x - bnds = A.bounds arr - ies = A.assocs arr -expandResultSource z@(ResultSeparatorSource x) = z - --- | Return a summarised and usually more short version of the result source expanding the main properties or excluding auxiliary properties if required. -resultSourceSummary :: ResultSource -> ResultSource -resultSourceSummary (ResultItemSource (ResultItem x)) = resultItemSummary x -resultSourceSummary (ResultObjectSource x) = resultObjectSummary x -resultSourceSummary (ResultVectorSource x) = resultVectorSummary x -resultSourceSummary z@(ResultSeparatorSource x) = z - --- | Return a signal emitted by the source. -resultSourceSignal :: ResultSource -> ResultSignal -resultSourceSignal (ResultItemSource (ResultItem x)) = resultItemSignal x -resultSourceSignal (ResultObjectSource x) = resultObjectSignal x -resultSourceSignal (ResultVectorSource x) = resultVectorSignal x -resultSourceSignal (ResultSeparatorSource x) = EmptyResultSignal - --- | Represent the result source as integer numbers. -resultSourceToIntValues :: ResultSource -> [ResultValue Int] -resultSourceToIntValues = map (\(ResultItem x) -> resultItemToIntValue x) . flattenResultSource - --- | Represent the result source as lists of integer numbers. -resultSourceToIntListValues :: ResultSource -> [ResultValue [Int]] -resultSourceToIntListValues = map (\(ResultItem x) -> resultItemToIntListValue x) . flattenResultSource - --- | Represent the result source as statistics based on integer numbers. -resultSourceToIntStatsValues :: ResultSource -> [ResultValue (SamplingStats Int)] -resultSourceToIntStatsValues = map (\(ResultItem x) -> resultItemToIntStatsValue x) . flattenResultSource - --- | Represent the result source as statistics based on integer numbers and optimised for fast aggregation. -resultSourceToIntStatsEitherValues :: ResultSource -> [ResultValue (Either Int (SamplingStats Int))] -resultSourceToIntStatsEitherValues = map (\(ResultItem x) -> resultItemToIntStatsEitherValue x) . flattenResultSource - --- | Represent the result source as timing statistics based on integer numbers. -resultSourceToIntTimingStatsValues :: ResultSource -> [ResultValue (TimingStats Int)] -resultSourceToIntTimingStatsValues = map (\(ResultItem x) -> resultItemToIntTimingStatsValue x) . flattenResultSource - --- | Represent the result source as double floating point numbers. -resultSourceToDoubleValues :: ResultSource -> [ResultValue Double] -resultSourceToDoubleValues = map (\(ResultItem x) -> resultItemToDoubleValue x) . flattenResultSource - --- | Represent the result source as lists of double floating point numbers. -resultSourceToDoubleListValues :: ResultSource -> [ResultValue [Double]] -resultSourceToDoubleListValues = map (\(ResultItem x) -> resultItemToDoubleListValue x) . flattenResultSource - --- | Represent the result source as statistics based on double floating point numbers. -resultSourceToDoubleStatsValues :: ResultSource -> [ResultValue (SamplingStats Double)] -resultSourceToDoubleStatsValues = map (\(ResultItem x) -> resultItemToDoubleStatsValue x) . flattenResultSource - --- | Represent the result source as statistics based on double floating point numbers and optimised for fast aggregation. -resultSourceToDoubleStatsEitherValues :: ResultSource -> [ResultValue (Either Double (SamplingStats Double))] -resultSourceToDoubleStatsEitherValues = map (\(ResultItem x) -> resultItemToDoubleStatsEitherValue x) . flattenResultSource - --- | Represent the result source as timing statistics based on double floating point numbers. -resultSourceToDoubleTimingStatsValues :: ResultSource -> [ResultValue (TimingStats Double)] -resultSourceToDoubleTimingStatsValues = map (\(ResultItem x) -> resultItemToDoubleTimingStatsValue x) . flattenResultSource - --- | Represent the result source as string values. -resultSourceToStringValues :: ResultSource -> [ResultValue String] -resultSourceToStringValues = map (\(ResultItem x) -> resultItemToStringValue x) . flattenResultSource - --- | It contains the results of simulation. -data Results = - Results { resultSourceMap :: ResultSourceMap, - -- ^ The sources of simulation results as a map of associated names. - resultSourceList :: [ResultSource] - -- ^ The sources of simulation results as an ordered list. - } - --- | It transforms the results of simulation. -type ResultTransform = Results -> Results - --- | It representes the predefined signals provided by every simulation model. -data ResultPredefinedSignals = - ResultPredefinedSignals { resultSignalInIntegTimes :: Signal Double, - -- ^ The signal triggered in the integration time points. - resultSignalInStartTime :: Signal Double, - -- ^ The signal triggered in the start time. - resultSignalInStopTime :: Signal Double - -- ^ The signal triggered in the stop time. - } - --- | Create the predefined signals provided by every simulation model. -newResultPredefinedSignals :: Simulation ResultPredefinedSignals -newResultPredefinedSignals = runDynamicsInStartTime $ runEventWith EarlierEvents d where - d = do signalInIntegTimes <- newSignalInIntegTimes - signalInStartTime <- newSignalInStartTime - signalInStopTime <- newSignalInStopTime - return ResultPredefinedSignals { resultSignalInIntegTimes = signalInIntegTimes, - resultSignalInStartTime = signalInStartTime, - resultSignalInStopTime = signalInStopTime } - -instance Monoid Results where - - mempty = results mempty - mappend x y = results $ resultSourceList x <> resultSourceList y - --- | Prepare the simulation results. -results :: [ResultSource] -> Results -results ms = - Results { resultSourceMap = M.fromList $ map (\x -> (resultSourceName x, x)) ms, - resultSourceList = ms } - --- | Represent the results as integer numbers. -resultsToIntValues :: Results -> [ResultValue Int] -resultsToIntValues = concat . map resultSourceToIntValues . resultSourceList - --- | Represent the results as lists of integer numbers. -resultsToIntListValues :: Results -> [ResultValue [Int]] -resultsToIntListValues = concat . map resultSourceToIntListValues . resultSourceList - --- | Represent the results as statistics based on integer numbers. -resultsToIntStatsValues :: Results -> [ResultValue (SamplingStats Int)] -resultsToIntStatsValues = concat . map resultSourceToIntStatsValues . resultSourceList - --- | Represent the results as statistics based on integer numbers and optimised for fast aggregation. -resultsToIntStatsEitherValues :: Results -> [ResultValue (Either Int (SamplingStats Int))] -resultsToIntStatsEitherValues = concat . map resultSourceToIntStatsEitherValues . resultSourceList - --- | Represent the results as timing statistics based on integer numbers. -resultsToIntTimingStatsValues :: Results -> [ResultValue (TimingStats Int)] -resultsToIntTimingStatsValues = concat . map resultSourceToIntTimingStatsValues . resultSourceList - --- | Represent the results as double floating point numbers. -resultsToDoubleValues :: Results -> [ResultValue Double] -resultsToDoubleValues = concat . map resultSourceToDoubleValues . resultSourceList - --- | Represent the results as lists of double floating point numbers. -resultsToDoubleListValues :: Results -> [ResultValue [Double]] -resultsToDoubleListValues = concat . map resultSourceToDoubleListValues . resultSourceList - --- | Represent the results as statistics based on double floating point numbers. -resultsToDoubleStatsValues :: Results -> [ResultValue (SamplingStats Double)] -resultsToDoubleStatsValues = concat . map resultSourceToDoubleStatsValues . resultSourceList - --- | Represent the results as statistics based on double floating point numbers and optimised for fast aggregation. -resultsToDoubleStatsEitherValues :: Results -> [ResultValue (Either Double (SamplingStats Double))] -resultsToDoubleStatsEitherValues = concat . map resultSourceToDoubleStatsEitherValues . resultSourceList - --- | Represent the results as timing statistics based on double floating point numbers. -resultsToDoubleTimingStatsValues :: Results -> [ResultValue (TimingStats Double)] -resultsToDoubleTimingStatsValues = concat . map resultSourceToDoubleTimingStatsValues . resultSourceList - --- | Represent the results as string values. -resultsToStringValues :: Results -> [ResultValue String] -resultsToStringValues = concat . map resultSourceToStringValues . resultSourceList - --- | Return a signal emitted by the specified results. -resultSignal :: Results -> ResultSignal -resultSignal = mconcat . map resultSourceSignal . resultSourceList - --- | Return an expanded version of the simulation results expanding the properties as possible, which --- takes place for expanding statistics to show the count, average, deviation, minimum, maximum etc. --- as separate values. -expandResults :: ResultTransform -expandResults = results . map expandResultSource . resultSourceList - --- | Return a short version of the simulation results, i.e. their summary, expanding the main properties --- or excluding auxiliary properties if required. -resultSummary :: ResultTransform -resultSummary = results . map resultSourceSummary . resultSourceList - --- | Take a result by its name. -resultByName :: ResultName -> ResultTransform -resultByName name rs = - case M.lookup name (resultSourceMap rs) of - Just x -> results [x] - Nothing -> - error $ - "Not found result source with name " ++ name ++ - ": resultByName" - --- | Take a result from the object with the specified property label, --- but it is more preferrable to refer to the property by its 'ResultId' --- identifier with help of the 'resultById' function. -resultByProperty :: ResultName -> ResultTransform -resultByProperty label rs = flip composeResults rs loop - where - loop x = - case x of - ResultObjectSource s -> - let ps = - flip filter (resultObjectProperties s) $ \p -> - resultPropertyLabel p == label - in case ps of - [] -> - error $ - "Not found property " ++ label ++ - " for object " ++ resultObjectName s ++ - ": resultByProperty" - ps -> - map resultPropertySource ps - ResultVectorSource s -> - concat $ map loop $ A.elems $ resultVectorItems s - x -> - error $ - "Result source " ++ resultSourceName x ++ - " is neither object, nor vector " ++ - ": resultByProperty" - --- | Take a result from the object with the specified identifier. It can identify --- an item, object property, the object iself, vector or its elements. -resultById :: ResultId -> ResultTransform -resultById i rs = flip composeResults rs loop - where - loop x = - case x of - ResultItemSource (ResultItem s) -> - if resultItemId s == i - then [x] - else error $ - "Expected to find item with Id = " ++ show i ++ - ", while the item " ++ resultItemName s ++ - " has actual Id = " ++ show (resultItemId s) ++ - ": resultById" - ResultObjectSource s -> - if resultObjectId s == i - then [x] - else let ps = - flip filter (resultObjectProperties s) $ \p -> - resultPropertyId p == i - in case ps of - [] -> - error $ - "Not found property with Id = " ++ show i ++ - " for object " ++ resultObjectName s ++ - " that has actual Id = " ++ show (resultObjectId s) ++ - ": resultById" - ps -> - map resultPropertySource ps - ResultVectorSource s -> - if resultVectorId s == i - then [x] - else concat $ map loop $ A.elems $ resultVectorItems s - x -> - error $ - "Result source " ++ resultSourceName x ++ - " is neither item, nor object, nor vector " ++ - ": resultById" - --- | Take a result from the vector by the specified integer index. -resultByIndex :: Int -> ResultTransform -resultByIndex index rs = flip composeResults rs loop - where - loop x = - case x of - ResultVectorSource s -> - [resultVectorItems s A.! index] - x -> - error $ - "Result source " ++ resultSourceName x ++ - " is not vector " ++ - ": resultByIndex" - --- | Take a result from the vector by the specified string subscript. -resultBySubscript :: ResultName -> ResultTransform -resultBySubscript subscript rs = flip composeResults rs loop - where - loop x = - case x of - ResultVectorSource s -> - let ys = A.elems $ resultVectorItems s - zs = A.elems $ resultVectorSubscript s - ps = - flip filter (zip ys zs) $ \(y, z) -> - z == subscript - in case ps of - [] -> - error $ - "Not found subscript " ++ subscript ++ - " for vector " ++ resultVectorName s ++ - ": resultBySubscript" - ps -> - map fst ps - x -> - error $ - "Result source " ++ resultSourceName x ++ - " is not vector " ++ - ": resultBySubscript" - --- | Compose the results using the specified transformation function. -composeResults :: (ResultSource -> [ResultSource]) -> ResultTransform -composeResults f = - results . concat . map f . resultSourceList - --- | Concatenate the results using the specified list of transformation functions. -concatResults :: [ResultTransform] -> ResultTransform -concatResults trs rs = - results $ concat $ map (\tr -> resultSourceList $ tr rs) trs - --- | Append the results using the specified transformation functions. -appendResults :: ResultTransform -> ResultTransform -> ResultTransform -appendResults x y = - concatResults [x, y] - --- | Return a pure signal as a result of combination of the predefined signals --- with the specified result signal usually provided by the sources. --- --- The signal returned is triggered when the source signal is triggered. --- The pure signal is also triggered in the integration time points --- if the source signal is unknown or it was combined with any unknown signal. -pureResultSignal :: ResultPredefinedSignals -> ResultSignal -> Signal () -pureResultSignal rs EmptyResultSignal = - void (resultSignalInStartTime rs) -pureResultSignal rs UnknownResultSignal = - void (resultSignalInIntegTimes rs) -pureResultSignal rs (ResultSignal s) = - void (resultSignalInStartTime rs) <> void (resultSignalInStopTime rs) <> s -pureResultSignal rs (ResultSignalMix s) = - void (resultSignalInIntegTimes rs) <> s - --- | Defines a final result extract: its name, values and other data. -data ResultExtract e = - ResultExtract { resultExtractName :: ResultName, - -- ^ The result name. - resultExtractId :: ResultId, - -- ^ The result identifier. - resultExtractData :: Event e, - -- ^ The result values. - resultExtractSignal :: ResultSignal - -- ^ Whether the result emits a signal. - } - --- | Extract the results as integer values, or raise a conversion error. -extractIntResults :: Results -> [ResultExtract Int] -extractIntResults rs = flip map (resultsToIntValues rs) $ \x -> - let n = resultValueName x - i = resultValueId x - a = resultValueData x - s = resultValueSignal x - in case a of - Nothing -> - error $ - "Cannot represent variable " ++ n ++ - " as a source of integer values: extractIntResults" - Just a -> - ResultExtract n i a s - --- | Extract the results as lists of integer values, or raise a conversion error. -extractIntListResults :: Results -> [ResultExtract [Int]] -extractIntListResults rs = flip map (resultsToIntListValues rs) $ \x -> - let n = resultValueName x - i = resultValueId x - a = resultValueData x - s = resultValueSignal x - in case a of - Nothing -> - error $ - "Cannot represent variable " ++ n ++ - " as a source of lists of integer values: extractIntListResults" - Just a -> - ResultExtract n i a s - --- | Extract the results as statistics based on integer values, --- or raise a conversion error. -extractIntStatsResults :: Results -> [ResultExtract (SamplingStats Int)] -extractIntStatsResults rs = flip map (resultsToIntStatsValues rs) $ \x -> - let n = resultValueName x - i = resultValueId x - a = resultValueData x - s = resultValueSignal x - in case a of - Nothing -> - error $ - "Cannot represent variable " ++ n ++ - " as a source of statistics based on integer values: extractIntStatsResults" - Just a -> - ResultExtract n i a s - --- | Extract the results as statistics based on integer values and optimised --- for fast aggregation, or raise a conversion error. -extractIntStatsEitherResults :: Results -> [ResultExtract (Either Int (SamplingStats Int))] -extractIntStatsEitherResults rs = flip map (resultsToIntStatsEitherValues rs) $ \x -> - let n = resultValueName x - i = resultValueId x - a = resultValueData x - s = resultValueSignal x - in case a of - Nothing -> - error $ - "Cannot represent variable " ++ n ++ - " as a source of statistics based on integer values: extractIntStatsEitherResults" - Just a -> - ResultExtract n i a s - --- | Extract the results as timing statistics based on integer values, --- or raise a conversion error. -extractIntTimingStatsResults :: Results -> [ResultExtract (TimingStats Int)] -extractIntTimingStatsResults rs = flip map (resultsToIntTimingStatsValues rs) $ \x -> - let n = resultValueName x - i = resultValueId x - a = resultValueData x - s = resultValueSignal x - in case a of - Nothing -> - error $ - "Cannot represent variable " ++ n ++ - " as a source of timing statistics based on integer values: extractIntTimingStatsResults" - Just a -> - ResultExtract n i a s - --- | Extract the results as double floating point values, or raise a conversion error. -extractDoubleResults :: Results -> [ResultExtract Double] -extractDoubleResults rs = flip map (resultsToDoubleValues rs) $ \x -> - let n = resultValueName x - i = resultValueId x - a = resultValueData x - s = resultValueSignal x - in case a of - Nothing -> - error $ - "Cannot represent variable " ++ n ++ - " as a source of double floating point values: extractDoubleResults" - Just a -> - ResultExtract n i a s - --- | Extract the results as lists of double floating point values, --- or raise a conversion error. -extractDoubleListResults :: Results -> [ResultExtract [Double]] -extractDoubleListResults rs = flip map (resultsToDoubleListValues rs) $ \x -> - let n = resultValueName x - i = resultValueId x - a = resultValueData x - s = resultValueSignal x - in case a of - Nothing -> - error $ - "Cannot represent variable " ++ n ++ - " as a source of lists of double floating point values: extractDoubleListResults" - Just a -> - ResultExtract n i a s - --- | Extract the results as statistics based on double floating point values, --- or raise a conversion error. -extractDoubleStatsResults :: Results -> [ResultExtract (SamplingStats Double)] -extractDoubleStatsResults rs = flip map (resultsToDoubleStatsValues rs) $ \x -> - let n = resultValueName x - i = resultValueId x - a = resultValueData x - s = resultValueSignal x - in case a of - Nothing -> - error $ - "Cannot represent variable " ++ n ++ - " as a source of statistics based on double floating point values: extractDoubleStatsResults" - Just a -> - ResultExtract n i a s - --- | Extract the results as statistics based on double floating point values --- and optimised for fast aggregation, or raise a conversion error. -extractDoubleStatsEitherResults :: Results -> [ResultExtract (Either Double (SamplingStats Double))] -extractDoubleStatsEitherResults rs = flip map (resultsToDoubleStatsEitherValues rs) $ \x -> - let n = resultValueName x - i = resultValueId x - a = resultValueData x - s = resultValueSignal x - in case a of - Nothing -> - error $ - "Cannot represent variable " ++ n ++ - " as a source of statistics based on double floating point values: extractDoubleStatsEitherResults" - Just a -> - ResultExtract n i a s - --- | Extract the results as timing statistics based on double floating point values, --- or raise a conversion error. -extractDoubleTimingStatsResults :: Results -> [ResultExtract (TimingStats Double)] -extractDoubleTimingStatsResults rs = flip map (resultsToDoubleTimingStatsValues rs) $ \x -> - let n = resultValueName x - i = resultValueId x - a = resultValueData x - s = resultValueSignal x - in case a of - Nothing -> - error $ - "Cannot represent variable " ++ n ++ - " as a source of timing statistics based on double floating point values: extractDoubleTimingStatsResults" - Just a -> - ResultExtract n i a s - --- | Extract the results as string values, or raise a conversion error. -extractStringResults :: Results -> [ResultExtract String] -extractStringResults rs = flip map (resultsToStringValues rs) $ \x -> - let n = resultValueName x - i = resultValueId x - a = resultValueData x - s = resultValueSignal x - in case a of - Nothing -> - error $ - "Cannot represent variable " ++ n ++ - " as a source of string values: extractStringResults" - Just a -> - ResultExtract n i a s - --- | Represents a computation that can return the simulation data. -class ResultComputing m where - - -- | Compute data with the results of simulation. - computeResultData :: m a -> ResultData a - - -- | Return the signal triggered when data change if such a signal exists. - computeResultSignal :: m a -> ResultSignal - --- | Return a new result value by the specified name, identifier and computation. -computeResultValue :: ResultComputing m - => ResultName - -- ^ the result name - -> ResultId - -- ^ the result identifier - -> m a - -- ^ the result computation - -> ResultValue a -computeResultValue name i m = - ResultValue { - resultValueName = name, - resultValueId = i, - resultValueData = computeResultData m, - resultValueSignal = computeResultSignal m } - --- | Represents a computation that can return the simulation data. -data ResultComputation a = - ResultComputation { resultComputationData :: ResultData a, - -- ^ Return data from the computation. - resultComputationSignal :: ResultSignal - -- ^ Return a signal from the computation. - } - -instance ResultComputing ResultComputation where - - computeResultData = resultComputationData - computeResultSignal = resultComputationSignal - -instance ResultComputing Parameter where - - computeResultData = Just . liftParameter - computeResultSignal = const UnknownResultSignal - -instance ResultComputing Simulation where - - computeResultData = Just . liftSimulation - computeResultSignal = const UnknownResultSignal - -instance ResultComputing Dynamics where - - computeResultData = Just . liftDynamics - computeResultSignal = const UnknownResultSignal - -instance ResultComputing Event where - - computeResultData = Just . id - computeResultSignal = const UnknownResultSignal - -instance ResultComputing Ref where - - computeResultData = Just . readRef - computeResultSignal = ResultSignal . refChanged_ - -instance ResultComputing LR.Ref where - - computeResultData = Just . LR.readRef - computeResultSignal = const UnknownResultSignal - -instance ResultComputing Var where - - computeResultData = Just . readVar - computeResultSignal = ResultSignal . varChanged_ - -instance ResultComputing Signalable where - - computeResultData = Just . readSignalable - computeResultSignal = ResultSignal . signalableChanged_ - --- | Return a source by the specified statistics. -samplingStatsResultSource :: (ResultItemable (ResultValue a), - ResultItemable (ResultValue (SamplingStats a))) - => ResultValue (SamplingStats a) - -- ^ the statistics - -> ResultSource -samplingStatsResultSource x = - ResultObjectSource $ - ResultObject { - resultObjectName = resultValueName x, - resultObjectId = resultValueId x, - resultObjectTypeId = SamplingStatsId, - resultObjectSignal = resultValueSignal x, - resultObjectSummary = samplingStatsResultSummary x, - resultObjectProperties = [ - resultContainerMapProperty c "count" SamplingStatsCountId samplingStatsCount, - resultContainerMapProperty c "mean" SamplingStatsMeanId samplingStatsMean, - resultContainerMapProperty c "mean2" SamplingStatsMean2Id samplingStatsMean2, - resultContainerMapProperty c "std" SamplingStatsDeviationId samplingStatsDeviation, - resultContainerMapProperty c "var" SamplingStatsVarianceId samplingStatsVariance, - resultContainerMapProperty c "min" SamplingStatsMinId samplingStatsMin, - resultContainerMapProperty c "max" SamplingStatsMaxId samplingStatsMax ] } - where - c = resultValueToContainer x - --- | Return the summary by the specified statistics. -samplingStatsResultSummary :: ResultItemable (ResultValue (SamplingStats a)) - => ResultValue (SamplingStats a) - -- ^ the statistics - -> ResultSource -samplingStatsResultSummary = ResultItemSource . ResultItem . resultItemToStringValue - --- | Return a source by the specified timing statistics. -timingStatsResultSource :: (TimingData a, - ResultItemable (ResultValue a), - ResultItemable (ResultValue (TimingStats a))) - => ResultValue (TimingStats a) - -- ^ the statistics - -> ResultSource -timingStatsResultSource x = - ResultObjectSource $ - ResultObject { - resultObjectName = resultValueName x, - resultObjectId = resultValueId x, - resultObjectTypeId = TimingStatsId, - resultObjectSignal = resultValueSignal x, - resultObjectSummary = timingStatsResultSummary x, - resultObjectProperties = [ - resultContainerMapProperty c "count" TimingStatsCountId timingStatsCount, - resultContainerMapProperty c "mean" TimingStatsMeanId timingStatsMean, - resultContainerMapProperty c "std" TimingStatsDeviationId timingStatsDeviation, - resultContainerMapProperty c "var" TimingStatsVarianceId timingStatsVariance, - resultContainerMapProperty c "min" TimingStatsMinId timingStatsMin, - resultContainerMapProperty c "max" TimingStatsMaxId timingStatsMax, - resultContainerMapProperty c "minTime" TimingStatsMinTimeId timingStatsMinTime, - resultContainerMapProperty c "maxTime" TimingStatsMaxTimeId timingStatsMaxTime, - resultContainerMapProperty c "startTime" TimingStatsStartTimeId timingStatsStartTime, - resultContainerMapProperty c "lastTime" TimingStatsLastTimeId timingStatsLastTime, - resultContainerMapProperty c "sum" TimingStatsSumId timingStatsSum, - resultContainerMapProperty c "sum2" TimingStatsSum2Id timingStatsSum2 ] } - where - c = resultValueToContainer x - --- | Return the summary by the specified timing statistics. -timingStatsResultSummary :: (TimingData a, ResultItemable (ResultValue (TimingStats a))) - => ResultValue (TimingStats a) - -- ^ the statistics - -> ResultSource -timingStatsResultSummary = ResultItemSource . ResultItem . resultItemToStringValue - --- | Return a source by the specified finite queue. -queueResultSource :: (Show si, Show sm, Show so, - ResultItemable (ResultValue si), - ResultItemable (ResultValue sm), - ResultItemable (ResultValue so)) - => ResultContainer (Q.Queue si sm so a) - -- ^ the queue container - -> ResultSource -queueResultSource c = - ResultObjectSource $ - ResultObject { - resultObjectName = resultContainerName c, - resultObjectId = resultContainerId c, - resultObjectTypeId = FiniteQueueId, - resultObjectSignal = resultContainerSignal c, - resultObjectSummary = queueResultSummary c, - resultObjectProperties = [ - resultContainerConstProperty c "enqueueStrategy" EnqueueStrategyId Q.enqueueStrategy, - resultContainerConstProperty c "enqueueStoringStrategy" EnqueueStoringStrategyId Q.enqueueStoringStrategy, - resultContainerConstProperty c "dequeueStrategy" DequeueStrategyId Q.dequeueStrategy, - resultContainerProperty c "queueNull" QueueNullId Q.queueNull Q.queueNullChanged_, - resultContainerProperty c "queueFull" QueueFullId Q.queueFull Q.queueFullChanged_, - resultContainerConstProperty c "queueMaxCount" QueueMaxCountId Q.queueMaxCount, - resultContainerProperty c "queueCount" QueueCountId Q.queueCount Q.queueCountChanged_, - resultContainerProperty c "queueCountStats" QueueCountStatsId Q.queueCountStats Q.queueCountChanged_, - resultContainerProperty c "enqueueCount" EnqueueCountId Q.enqueueCount Q.enqueueCountChanged_, - resultContainerProperty c "enqueueLostCount" EnqueueLostCountId Q.enqueueLostCount Q.enqueueLostCountChanged_, - resultContainerProperty c "enqueueStoreCount" EnqueueStoreCountId Q.enqueueStoreCount Q.enqueueStoreCountChanged_, - resultContainerProperty c "dequeueCount" DequeueCountId Q.dequeueCount Q.dequeueCountChanged_, - resultContainerProperty c "dequeueExtractCount" DequeueExtractCountId Q.dequeueExtractCount Q.dequeueExtractCountChanged_, - resultContainerProperty c "queueLoadFactor" QueueLoadFactorId Q.queueLoadFactor Q.queueLoadFactorChanged_, - resultContainerIntegProperty c "enqueueRate" EnqueueRateId Q.enqueueRate, - resultContainerIntegProperty c "enqueueStoreRate" EnqueueStoreRateId Q.enqueueStoreRate, - resultContainerIntegProperty c "dequeueRate" DequeueRateId Q.dequeueRate, - resultContainerIntegProperty c "dequeueExtractRate" DequeueExtractRateId Q.dequeueExtractRate, - resultContainerProperty c "queueWaitTime" QueueWaitTimeId Q.queueWaitTime Q.queueWaitTimeChanged_, - resultContainerProperty c "queueTotalWaitTime" QueueTotalWaitTimeId Q.queueTotalWaitTime Q.queueTotalWaitTimeChanged_, - resultContainerProperty c "enqueueWaitTime" EnqueueWaitTimeId Q.enqueueWaitTime Q.enqueueWaitTimeChanged_, - resultContainerProperty c "dequeueWaitTime" DequeueWaitTimeId Q.dequeueWaitTime Q.dequeueWaitTimeChanged_, - resultContainerProperty c "queueRate" QueueRateId Q.queueRate Q.queueRateChanged_ ] } - --- | Return the summary by the specified finite queue. -queueResultSummary :: (Show si, Show sm, Show so) - => ResultContainer (Q.Queue si sm so a) - -- ^ the queue container - -> ResultSource -queueResultSummary c = - ResultObjectSource $ - ResultObject { - resultObjectName = resultContainerName c, - resultObjectId = resultContainerId c, - resultObjectTypeId = FiniteQueueId, - resultObjectSignal = resultContainerSignal c, - resultObjectSummary = queueResultSummary c, - resultObjectProperties = [ - resultContainerConstProperty c "queueMaxCount" QueueMaxCountId Q.queueMaxCount, - resultContainerProperty c "queueCountStats" QueueCountStatsId Q.queueCountStats Q.queueCountChanged_, - resultContainerProperty c "enqueueCount" EnqueueCountId Q.enqueueCount Q.enqueueCountChanged_, - resultContainerProperty c "enqueueLostCount" EnqueueLostCountId Q.enqueueLostCount Q.enqueueLostCountChanged_, - resultContainerProperty c "enqueueStoreCount" EnqueueStoreCountId Q.enqueueStoreCount Q.enqueueStoreCountChanged_, - resultContainerProperty c "dequeueCount" DequeueCountId Q.dequeueCount Q.dequeueCountChanged_, - resultContainerProperty c "dequeueExtractCount" DequeueExtractCountId Q.dequeueExtractCount Q.dequeueExtractCountChanged_, - resultContainerProperty c "queueLoadFactor" QueueLoadFactorId Q.queueLoadFactor Q.queueLoadFactorChanged_, - resultContainerProperty c "queueWaitTime" QueueWaitTimeId Q.queueWaitTime Q.queueWaitTimeChanged_, - resultContainerProperty c "queueRate" QueueRateId Q.queueRate Q.queueRateChanged_ ] } - --- | Return a source by the specified infinite queue. -infiniteQueueResultSource :: (Show sm, Show so, - ResultItemable (ResultValue sm), - ResultItemable (ResultValue so)) - => ResultContainer (IQ.Queue sm so a) - -- ^ the queue container - -> ResultSource -infiniteQueueResultSource c = - ResultObjectSource $ - ResultObject { - resultObjectName = resultContainerName c, - resultObjectId = resultContainerId c, - resultObjectTypeId = FiniteQueueId, - resultObjectSignal = resultContainerSignal c, - resultObjectSummary = infiniteQueueResultSummary c, - resultObjectProperties = [ - resultContainerConstProperty c "enqueueStoringStrategy" EnqueueStoringStrategyId IQ.enqueueStoringStrategy, - resultContainerConstProperty c "dequeueStrategy" DequeueStrategyId IQ.dequeueStrategy, - resultContainerProperty c "queueNull" QueueNullId IQ.queueNull IQ.queueNullChanged_, - resultContainerProperty c "queueCount" QueueCountId IQ.queueCount IQ.queueCountChanged_, - resultContainerProperty c "queueCountStats" QueueCountStatsId IQ.queueCountStats IQ.queueCountChanged_, - resultContainerProperty c "enqueueStoreCount" EnqueueStoreCountId IQ.enqueueStoreCount IQ.enqueueStoreCountChanged_, - resultContainerProperty c "dequeueCount" DequeueCountId IQ.dequeueCount IQ.dequeueCountChanged_, - resultContainerProperty c "dequeueExtractCount" DequeueExtractCountId IQ.dequeueExtractCount IQ.dequeueExtractCountChanged_, - resultContainerIntegProperty c "enqueueStoreRate" EnqueueStoreRateId IQ.enqueueStoreRate, - resultContainerIntegProperty c "dequeueRate" DequeueRateId IQ.dequeueRate, - resultContainerIntegProperty c "dequeueExtractRate" DequeueExtractRateId IQ.dequeueExtractRate, - resultContainerProperty c "queueWaitTime" QueueWaitTimeId IQ.queueWaitTime IQ.queueWaitTimeChanged_, - resultContainerProperty c "dequeueWaitTime" DequeueWaitTimeId IQ.dequeueWaitTime IQ.dequeueWaitTimeChanged_, - resultContainerProperty c "queueRate" QueueRateId IQ.queueRate IQ.queueRateChanged_ ] } - --- | Return the summary by the specified infinite queue. -infiniteQueueResultSummary :: (Show sm, Show so) - => ResultContainer (IQ.Queue sm so a) - -- ^ the queue container - -> ResultSource -infiniteQueueResultSummary c = - ResultObjectSource $ - ResultObject { - resultObjectName = resultContainerName c, - resultObjectId = resultContainerId c, - resultObjectTypeId = FiniteQueueId, - resultObjectSignal = resultContainerSignal c, - resultObjectSummary = infiniteQueueResultSummary c, - resultObjectProperties = [ - resultContainerProperty c "queueCountStats" QueueCountStatsId IQ.queueCountStats IQ.queueCountChanged_, - resultContainerProperty c "enqueueStoreCount" EnqueueStoreCountId IQ.enqueueStoreCount IQ.enqueueStoreCountChanged_, - resultContainerProperty c "dequeueCount" DequeueCountId IQ.dequeueCount IQ.dequeueCountChanged_, - resultContainerProperty c "dequeueExtractCount" DequeueExtractCountId IQ.dequeueExtractCount IQ.dequeueExtractCountChanged_, - resultContainerProperty c "queueWaitTime" QueueWaitTimeId IQ.queueWaitTime IQ.queueWaitTimeChanged_, - resultContainerProperty c "queueRate" QueueRateId IQ.queueRate IQ.queueRateChanged_ ] } - --- | Return a source by the specified arrival timer. -arrivalTimerResultSource :: ResultContainer ArrivalTimer - -- ^ the arrival timer container - -> ResultSource -arrivalTimerResultSource c = - ResultObjectSource $ - ResultObject { - resultObjectName = resultContainerName c, - resultObjectId = resultContainerId c, - resultObjectTypeId = ArrivalTimerId, - resultObjectSignal = resultContainerSignal c, - resultObjectSummary = arrivalTimerResultSummary c, - resultObjectProperties = [ - resultContainerProperty c "processingTime" ArrivalProcessingTimeId arrivalProcessingTime arrivalProcessingTimeChanged_ ] } - --- | Return the summary by the specified arrival timer. -arrivalTimerResultSummary :: ResultContainer ArrivalTimer - -- ^ the arrival timer container - -> ResultSource -arrivalTimerResultSummary c = - ResultObjectSource $ - ResultObject { - resultObjectName = resultContainerName c, - resultObjectId = resultContainerId c, - resultObjectTypeId = ArrivalTimerId, - resultObjectSignal = resultContainerSignal c, - resultObjectSummary = arrivalTimerResultSummary c, - resultObjectProperties = [ - resultContainerProperty c "processingTime" ArrivalProcessingTimeId arrivalProcessingTime arrivalProcessingTimeChanged_ ] } - --- | Return a source by the specified server. -serverResultSource :: (Show s, ResultItemable (ResultValue s)) - => ResultContainer (Server s a b) - -- ^ the server container - -> ResultSource -serverResultSource c = - ResultObjectSource $ - ResultObject { - resultObjectName = resultContainerName c, - resultObjectId = resultContainerId c, - resultObjectTypeId = ServerId, - resultObjectSignal = resultContainerSignal c, - resultObjectSummary = serverResultSummary c, - resultObjectProperties = [ - resultContainerConstProperty c "initState" ServerInitStateId serverInitState, - resultContainerProperty c "state" ServerStateId serverState serverStateChanged_, - resultContainerProperty c "totalInputWaitTime" ServerTotalInputWaitTimeId serverTotalInputWaitTime serverTotalInputWaitTimeChanged_, - resultContainerProperty c "totalProcessingTime" ServerTotalProcessingTimeId serverTotalProcessingTime serverTotalProcessingTimeChanged_, - resultContainerProperty c "totalOutputWaitTime" ServerTotalOutputWaitTimeId serverTotalOutputWaitTime serverTotalOutputWaitTimeChanged_, - resultContainerProperty c "inputWaitTime" ServerInputWaitTimeId serverInputWaitTime serverInputWaitTimeChanged_, - resultContainerProperty c "processingTime" ServerProcessingTimeId serverProcessingTime serverProcessingTimeChanged_, - resultContainerProperty c "outputWaitTime" ServerOutputWaitTimeId serverOutputWaitTime serverOutputWaitTimeChanged_, - resultContainerProperty c "inputWaitFactor" ServerInputWaitFactorId serverInputWaitFactor serverInputWaitFactorChanged_, - resultContainerProperty c "processingFactor" ServerProcessingFactorId serverProcessingFactor serverProcessingFactorChanged_, - resultContainerProperty c "outputWaitFactor" ServerOutputWaitFactorId serverOutputWaitFactor serverOutputWaitFactorChanged_ ] } - --- | Return the summary by the specified server. -serverResultSummary :: ResultContainer (Server s a b) - -- ^ the server container - -> ResultSource -serverResultSummary c = - ResultObjectSource $ - ResultObject { - resultObjectName = resultContainerName c, - resultObjectId = resultContainerId c, - resultObjectTypeId = ServerId, - resultObjectSignal = resultContainerSignal c, - resultObjectSummary = serverResultSummary c, - resultObjectProperties = [ - resultContainerProperty c "inputWaitTime" ServerInputWaitTimeId serverInputWaitTime serverInputWaitTimeChanged_, - resultContainerProperty c "processingTime" ServerProcessingTimeId serverProcessingTime serverProcessingTimeChanged_, - resultContainerProperty c "outputWaitTime" ServerOutputWaitTimeId serverOutputWaitTime serverOutputWaitTimeChanged_, - resultContainerProperty c "inputWaitFactor" ServerInputWaitFactorId serverInputWaitFactor serverInputWaitFactorChanged_, - resultContainerProperty c "processingFactor" ServerProcessingFactorId serverProcessingFactor serverProcessingFactorChanged_, - resultContainerProperty c "outputWaitFactor" ServerOutputWaitFactorId serverOutputWaitFactor serverOutputWaitFactorChanged_ ] } - --- | Return an arbitrary text as a separator source. -textResultSource :: String -> ResultSource -textResultSource text = - ResultSeparatorSource $ - ResultSeparator { resultSeparatorText = text } - --- | Return the source of the modeling time. -timeResultSource :: ResultSource -timeResultSource = resultSource' "t" TimeId time - --- | Make an integer subscript -intSubscript :: Int -> ResultName -intSubscript i = "[" ++ show i ++ "]" - -instance ResultComputing m => ResultProvider (m Double) where - - resultSource' name i m = - ResultItemSource $ ResultItem $ computeResultValue name i m - -instance ResultComputing m => ResultProvider (m [Double]) where - - resultSource' name i m = - ResultItemSource $ ResultItem $ computeResultValue name i m - -instance ResultComputing m => ResultProvider (m (SamplingStats Double)) where - - resultSource' name i m = - ResultItemSource $ ResultItem $ computeResultValue name i m - -instance ResultComputing m => ResultProvider (m (TimingStats Double)) where - - resultSource' name i m = - ResultItemSource $ ResultItem $ computeResultValue name i m - -instance ResultComputing m => ResultProvider (m Int) where - - resultSource' name i m = - ResultItemSource $ ResultItem $ computeResultValue name i m - -instance ResultComputing m => ResultProvider (m [Int]) where - - resultSource' name i m = - ResultItemSource $ ResultItem $ computeResultValue name i m - -instance ResultComputing m => ResultProvider (m (SamplingStats Int)) where - - resultSource' name i m = - ResultItemSource $ ResultItem $ computeResultValue name i m - -instance ResultComputing m => ResultProvider (m (TimingStats Int)) where - - resultSource' name i m = - ResultItemSource $ ResultItem $ computeResultValue name i m - -instance ResultComputing m => ResultProvider (m String) where - - resultSource' name i m = - ResultItemSource $ ResultItem $ computeResultValue name i m - -instance ResultProvider p => ResultProvider [p] where - - resultSource' name i m = - resultSource' name i $ ResultListWithSubscript m subscript where - subscript = map snd $ zip m $ map intSubscript [0..] - -instance (Show i, Ix i, ResultProvider p) => ResultProvider (A.Array i p) where - - resultSource' name i m = - resultSource' name i $ ResultListWithSubscript items subscript where - items = A.elems m - subscript = map (\i -> "[" ++ show i ++ "]") (A.indices m) - -#ifndef __HASTE__ - -instance ResultProvider p => ResultProvider (V.Vector p) where - - resultSource' name i m = - resultSource' name i $ ResultVectorWithSubscript m subscript where - subscript = V.imap (\i x -> intSubscript i) m - -#endif - --- | Represents a list with the specified subscript. -data ResultListWithSubscript p = - ResultListWithSubscript [p] [String] - --- | Represents an array with the specified subscript. -data ResultArrayWithSubscript i p = - ResultArrayWithSubscript (A.Array i p) (A.Array i String) - -#ifndef __HASTE__ - --- | Represents a vector with the specified subscript. -data ResultVectorWithSubscript p = - ResultVectorWithSubscript (V.Vector p) (V.Vector String) - -#endif - -instance ResultProvider p => ResultProvider (ResultListWithSubscript p) where - - resultSource' name i (ResultListWithSubscript xs ys) = - ResultVectorSource $ - memoResultVectorSignal $ - memoResultVectorSummary $ - ResultVector { resultVectorName = name, - resultVectorId = i, - resultVectorItems = axs, - resultVectorSubscript = ays, - resultVectorSignal = undefined, - resultVectorSummary = undefined } - where - bnds = (0, length xs - 1) - axs = A.listArray bnds items - ays = A.listArray bnds ys - items = - flip map (zip ys xs) $ \(y, x) -> - let name' = name ++ y - in resultSource' name' (VectorItemId y) x - items' = map resultSourceSummary items - -instance (Show i, Ix i, ResultProvider p) => ResultProvider (ResultArrayWithSubscript i p) where - - resultSource' name i (ResultArrayWithSubscript xs ys) = - resultSource' name i $ ResultListWithSubscript items subscript where - items = A.elems xs - subscript = A.elems ys - -#ifndef __HASTE__ - -instance ResultProvider p => ResultProvider (ResultVectorWithSubscript p) where - - resultSource' name i (ResultVectorWithSubscript xs ys) = - ResultVectorSource $ - memoResultVectorSignal $ - memoResultVectorSummary $ - ResultVector { resultVectorName = name, - resultVectorId = i, - resultVectorItems = axs, - resultVectorSubscript = ays, - resultVectorSignal = undefined, - resultVectorSummary = undefined } - where - bnds = (0, V.length xs - 1) - axs = A.listArray bnds (V.toList items) - ays = A.listArray bnds (V.toList ys) - items = - V.generate (V.length xs) $ \i -> - let x = xs V.! i - y = ys V.! i - name' = name ++ y - in resultSource' name' (VectorItemId y) x - items' = V.map resultSourceSummary items - -#endif - -instance (Ix i, Show i, ResultComputing m) => ResultProvider (m (A.Array i Double)) where - - resultSource' name i m = - ResultItemSource $ ResultItem $ fmap A.elems $ computeResultValue name i m - -instance (Ix i, Show i, ResultComputing m) => ResultProvider (m (A.Array i Int)) where - - resultSource' name i m = - ResultItemSource $ ResultItem $ fmap A.elems $ computeResultValue name i m - -#ifndef __HASTE__ - -instance ResultComputing m => ResultProvider (m (V.Vector Double)) where - - resultSource' name i m = - ResultItemSource $ ResultItem $ fmap V.toList $ computeResultValue name i m - -instance ResultComputing m => ResultProvider (m (V.Vector Int)) where - - resultSource' name i m = - ResultItemSource $ ResultItem $ fmap V.toList $ computeResultValue name i m - -#endif - -instance (Show si, Show sm, Show so, - ResultItemable (ResultValue si), - ResultItemable (ResultValue sm), - ResultItemable (ResultValue so)) - => ResultProvider (Q.Queue si sm so a) where - - resultSource' name i m = - queueResultSource $ ResultContainer name i m (ResultSignal $ Q.queueChanged_ m) - -instance (Show sm, Show so, - ResultItemable (ResultValue sm), - ResultItemable (ResultValue so)) - => ResultProvider (IQ.Queue sm so a) where - - resultSource' name i m = - infiniteQueueResultSource $ ResultContainer name i m (ResultSignal $ IQ.queueChanged_ m) - -instance ResultProvider ArrivalTimer where - - resultSource' name i m = - arrivalTimerResultSource $ ResultContainer name i m (ResultSignal $ arrivalProcessingTimeChanged_ m) - -instance (Show s, ResultItemable (ResultValue s)) => ResultProvider (Server s a b) where - - resultSource' name i m = - serverResultSource $ ResultContainer name i m (ResultSignal $ serverChanged_ m) ++{-# LANGUAGE CPP, FlexibleContexts, FlexibleInstances, UndecidableInstances, ExistentialQuantification #-}++-- |+-- Module : Simulation.Aivika.Results+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- The module allows exporting the simulation results from the model.+--+module Simulation.Aivika.Results+ (-- * Definitions Focused on Modeling+ Results,+ ResultTransform,+ ResultName,+ ResultProvider(..),+ results,+ expandResults,+ resultSummary,+ resultByName,+ resultByProperty,+ resultById,+ resultByIndex,+ resultBySubscript,+ ResultComputing(..),+ ResultComputation(..),+ ResultListWithSubscript(..),+ ResultArrayWithSubscript(..),+#ifndef __HASTE__+ ResultVectorWithSubscript(..),+#endif+ -- * Definitions Focused on Using the Library+ ResultExtract(..),+ extractIntResults,+ extractIntListResults,+ extractIntStatsResults,+ extractIntStatsEitherResults,+ extractIntTimingStatsResults,+ extractDoubleResults,+ extractDoubleListResults,+ extractDoubleStatsResults,+ extractDoubleStatsEitherResults,+ extractDoubleTimingStatsResults,+ extractStringResults,+ ResultPredefinedSignals(..),+ newResultPredefinedSignals,+ resultSignal,+ pureResultSignal,+ -- * Definitions Focused on Extending the Library + ResultSourceMap,+ ResultSource(..),+ ResultItem(..),+ ResultItemable(..),+ resultItemToIntStatsEitherValue,+ resultItemToDoubleStatsEitherValue,+ ResultObject(..),+ ResultProperty(..),+ ResultVector(..),+ memoResultVectorSignal,+ memoResultVectorSummary,+ ResultSeparator(..),+ ResultValue(..),+ voidResultValue,+ ResultContainer(..),+ resultContainerPropertySource,+ resultContainerConstProperty,+ resultContainerIntegProperty,+ resultContainerProperty,+ resultContainerMapProperty,+ resultValueToContainer,+ resultContainerToValue,+ ResultData,+ ResultSignal(..),+ maybeResultSignal,+ textResultSource,+ timeResultSource,+ resultSourceToIntValues,+ resultSourceToIntListValues,+ resultSourceToIntStatsValues,+ resultSourceToIntStatsEitherValues,+ resultSourceToIntTimingStatsValues,+ resultSourceToDoubleValues,+ resultSourceToDoubleListValues,+ resultSourceToDoubleStatsValues,+ resultSourceToDoubleStatsEitherValues,+ resultSourceToDoubleTimingStatsValues,+ resultSourceToStringValues,+ resultSourceMap,+ resultSourceList,+ resultsToIntValues,+ resultsToIntListValues,+ resultsToIntStatsValues,+ resultsToIntStatsEitherValues,+ resultsToIntTimingStatsValues,+ resultsToDoubleValues,+ resultsToDoubleListValues,+ resultsToDoubleStatsValues,+ resultsToDoubleStatsEitherValues,+ resultsToDoubleTimingStatsValues,+ resultsToStringValues,+ composeResults,+ computeResultValue) where++import Control.Monad+import Control.Monad.Trans++import qualified Data.Map as M+import qualified Data.Array as A++#ifndef __HASTE__+import qualified Data.Vector as V+#endif++import Data.Ix+import Data.Maybe+import Data.Monoid++import Simulation.Aivika.Parameter+import Simulation.Aivika.Simulation+import Simulation.Aivika.Dynamics+import Simulation.Aivika.Event+import Simulation.Aivika.Signal+import Simulation.Aivika.Statistics+import Simulation.Aivika.Statistics.Accumulator+import Simulation.Aivika.Ref+import qualified Simulation.Aivika.Ref.Plain as LR+import Simulation.Aivika.Var+import Simulation.Aivika.QueueStrategy+import qualified Simulation.Aivika.Queue as Q+import qualified Simulation.Aivika.Queue.Infinite as IQ+import Simulation.Aivika.Arrival+import Simulation.Aivika.Server+import Simulation.Aivika.Activity+import Simulation.Aivika.Results.Locale++-- | A name used for indentifying the results when generating output.+type ResultName = String++-- | Represents a provider of the simulation results. It is usually something, or+-- an array of something, or a list of such values which can be simulated to get data.+class ResultProvider p where+ + -- | Return the source of simulation results by the specified name, description and provider. + resultSource :: ResultName -> ResultDescription -> p -> ResultSource+ resultSource name descr = resultSource' name (UserDefinedResultId descr)++ -- | Return the source of simulation results by the specified name, identifier and provider. + resultSource' :: ResultName -> ResultId -> p -> ResultSource++-- | It associates the result sources with their names.+type ResultSourceMap = M.Map ResultName ResultSource++-- | Encapsulates the result source.+data ResultSource = ResultItemSource ResultItem+ -- ^ The source consisting of a single item.+ | ResultObjectSource ResultObject+ -- ^ An object-like source.+ | ResultVectorSource ResultVector+ -- ^ A vector-like structure.+ | ResultSeparatorSource ResultSeparator+ -- ^ This is a separator text.++-- | The simulation results represented by a single item.+data ResultItem = forall a. ResultItemable a => ResultItem a++-- | Represents a type class for actual representing the items.+class ResultItemable a where++ -- | The item name.+ resultItemName :: a -> ResultName+ + -- | The item identifier.+ resultItemId :: a -> ResultId++ -- | Whether the item emits a signal.+ resultItemSignal :: a -> ResultSignal++ -- | Return an expanded version of the item, for example,+ -- when the statistics item is exanded to an object+ -- having the corresponded properties for count, average,+ -- deviation, minimum, maximum and so on.+ resultItemExpansion :: a -> ResultSource+ + -- | Return usually a short version of the item, i.e. its summary,+ -- but values of some data types such as statistics can be+ -- implicitly expanded to an object with the corresponded+ -- properties.+ resultItemSummary :: a -> ResultSource+ + -- | Return integer numbers in time points.+ resultItemToIntValue :: a -> ResultValue Int++ -- | Return lists of integer numbers in time points. + resultItemToIntListValue :: a -> ResultValue [Int]++ -- | Return statistics based on integer numbers.+ resultItemToIntStatsValue :: a -> ResultValue (SamplingStats Int)++ -- | Return timing statistics based on integer numbers.+ resultItemToIntTimingStatsValue :: a -> ResultValue (TimingStats Int)++ -- | Return double numbers in time points.+ resultItemToDoubleValue :: a -> ResultValue Double+ + -- | Return lists of double numbers in time points. + resultItemToDoubleListValue :: a -> ResultValue [Double]++ -- | Return statistics based on double numbers.+ resultItemToDoubleStatsValue :: a -> ResultValue (SamplingStats Double)++ -- | Return timing statistics based on integer numbers.+ resultItemToDoubleTimingStatsValue :: a -> ResultValue (TimingStats Double)++ -- | Return string representations in time points.+ resultItemToStringValue :: a -> ResultValue String++-- | Return a version optimised for fast aggregation of the statistics based on integer numbers.+resultItemToIntStatsEitherValue :: ResultItemable a => a -> ResultValue (Either Int (SamplingStats Int))+resultItemToIntStatsEitherValue x =+ case resultValueData x1 of+ Just a1 -> fmap Left x1+ Nothing ->+ case resultValueData x2 of+ Just a2 -> fmap Right x2+ Nothing -> voidResultValue x2+ where+ x1 = resultItemToIntValue x+ x2 = resultItemToIntStatsValue x++-- | Return a version optimised for fast aggregation of the statistics based on double floating point numbers.+resultItemToDoubleStatsEitherValue :: ResultItemable a => a -> ResultValue (Either Double (SamplingStats Double))+resultItemToDoubleStatsEitherValue x =+ case resultValueData x1 of+ Just a1 -> fmap Left x1+ Nothing ->+ case resultValueData x2 of+ Just a2 -> fmap Right x2+ Nothing -> voidResultValue x2+ where+ x1 = resultItemToDoubleValue x+ x2 = resultItemToDoubleStatsValue x++-- | The simulation results represented by an object having properties.+data ResultObject =+ ResultObject { resultObjectName :: ResultName,+ -- ^ The object name.+ resultObjectId :: ResultId,+ -- ^ The object identifier.+ resultObjectTypeId :: ResultId,+ -- ^ The object type identifier.+ resultObjectProperties :: [ResultProperty],+ -- ^ The object properties.+ resultObjectSignal :: ResultSignal,+ -- ^ A combined signal if present.+ resultObjectSummary :: ResultSource+ -- ^ A short version of the object, i.e. its summary.+ }++-- | The object property containing the simulation results.+data ResultProperty =+ ResultProperty { resultPropertyLabel :: ResultName,+ -- ^ The property short label.+ resultPropertyId :: ResultId,+ -- ^ The property identifier.+ resultPropertySource :: ResultSource+ -- ^ The simulation results supplied by the property.+ }++-- | The simulation results represented by a vector.+data ResultVector =+ ResultVector { resultVectorName :: ResultName,+ -- ^ The vector name.+ resultVectorId :: ResultId,+ -- ^ The vector identifier.+ resultVectorItems :: A.Array Int ResultSource,+ -- ^ The results supplied by the vector items.+ resultVectorSubscript :: A.Array Int ResultName,+ -- ^ The subscript used as a suffix to create item names.+ resultVectorSignal :: ResultSignal,+ -- ^ A combined signal if present.+ resultVectorSummary :: ResultSource+ -- ^ A short version of the vector, i.e. summary.+ }++-- | Calculate the result vector signal and memoize it in a new vector.+memoResultVectorSignal :: ResultVector -> ResultVector+memoResultVectorSignal x =+ x { resultVectorSignal =+ foldr (<>) mempty $ map resultSourceSignal $ A.elems $ resultVectorItems x }++-- | Calculate the result vector summary and memoize it in a new vector.+memoResultVectorSummary :: ResultVector -> ResultVector+memoResultVectorSummary x =+ x { resultVectorSummary =+ ResultVectorSource $+ x { resultVectorItems =+ A.array bnds [(i, resultSourceSummary e) | (i, e) <- ies] } }+ where+ arr = resultVectorItems x+ bnds = A.bounds arr+ ies = A.assocs arr++-- | It separates the simulation results when printing.+data ResultSeparator =+ ResultSeparator { resultSeparatorText :: String+ -- ^ The separator text.+ }++-- | A parameterised value that actually represents a generalised result item that have no parametric type.+data ResultValue e =+ ResultValue { resultValueName :: ResultName,+ -- ^ The value name.+ resultValueId :: ResultId,+ -- ^ The value identifier.+ resultValueData :: ResultData e,+ -- ^ Simulation data supplied by the value.+ resultValueSignal :: ResultSignal+ -- ^ Whether the value emits a signal when changing simulation data.+ }++instance Functor ResultValue where+ fmap f x = x { resultValueData = fmap (fmap f) (resultValueData x) }++-- | Return a new value with the discarded simulation results.+voidResultValue :: ResultValue a -> ResultValue b+voidResultValue x = x { resultValueData = Nothing }++-- | A container of the simulation results such as queue, server or array.+data ResultContainer e =+ ResultContainer { resultContainerName :: ResultName,+ -- ^ The container name.+ resultContainerId :: ResultId,+ -- ^ The container identifier.+ resultContainerData :: e,+ -- ^ The container data.+ resultContainerSignal :: ResultSignal+ -- ^ Whether the container emits a signal when changing simulation data.+ }++instance Functor ResultContainer where+ fmap f x = x { resultContainerData = f (resultContainerData x) }++-- | Create a new property source by the specified container.+resultContainerPropertySource :: ResultItemable (ResultValue b)+ => ResultContainer a+ -- ^ the container+ -> ResultName+ -- ^ the property label+ -> ResultId+ -- ^ the property identifier+ -> (a -> ResultData b)+ -- ^ get the specified data from the container+ -> (a -> ResultSignal)+ -- ^ get the data signal from the container+ -> ResultSource+resultContainerPropertySource cont name i f g =+ ResultItemSource $+ ResultItem $+ ResultValue {+ resultValueName = (resultContainerName cont) ++ "." ++ name,+ resultValueId = i,+ resultValueData = f (resultContainerData cont),+ resultValueSignal = g (resultContainerData cont) }++-- | Create a constant property by the specified container.+resultContainerConstProperty :: ResultItemable (ResultValue b)+ => ResultContainer a+ -- ^ the container+ -> ResultName+ -- ^ the property label+ -> ResultId+ -- ^ the property identifier+ -> (a -> b)+ -- ^ get the specified data from the container+ -> ResultProperty+resultContainerConstProperty cont name i f =+ ResultProperty {+ resultPropertyLabel = name,+ resultPropertyId = i,+ resultPropertySource =+ resultContainerPropertySource cont name i (Just . return . f) (const EmptyResultSignal) }+ +-- | Create by the specified container a property that changes in the integration time points, or it is supposed to be such one.+resultContainerIntegProperty :: ResultItemable (ResultValue b)+ => ResultContainer a+ -- ^ the container+ -> ResultName+ -- ^ the property label+ -> ResultId+ -- ^ the property identifier+ -> (a -> Event b)+ -- ^ get the specified data from the container+ -> ResultProperty+resultContainerIntegProperty cont name i f =+ ResultProperty {+ resultPropertyLabel = name,+ resultPropertyId = i,+ resultPropertySource =+ resultContainerPropertySource cont name i (Just . f) (const UnknownResultSignal) }+ +-- | Create a property by the specified container.+resultContainerProperty :: ResultItemable (ResultValue b)+ => ResultContainer a+ -- ^ the container+ -> ResultName+ -- ^ the property label+ -> ResultId+ -- ^ the property identifier+ -> (a -> Event b)+ -- ^ get the specified data from the container+ -> (a -> Signal ())+ -- ^ get a signal triggered when changing data.+ -> ResultProperty+resultContainerProperty cont name i f g = + ResultProperty {+ resultPropertyLabel = name,+ resultPropertyId = i,+ resultPropertySource =+ resultContainerPropertySource cont name i (Just . f) (ResultSignal . g) }++-- | Create by the specified container a mapped property which is recomputed each time again and again.+resultContainerMapProperty :: ResultItemable (ResultValue b)+ => ResultContainer (ResultData a)+ -- ^ the container+ -> ResultName+ -- ^ the property label+ -> ResultId+ -- ^ the property identifier+ -> (a -> b)+ -- ^ recompute the specified data+ -> ResultProperty+resultContainerMapProperty cont name i f = + ResultProperty {+ resultPropertyLabel = name,+ resultPropertyId = i,+ resultPropertySource =+ resultContainerPropertySource cont name i (fmap $ fmap f) (const $ resultContainerSignal cont) }++-- | Convert the result value to a container with the specified object identifier. +resultValueToContainer :: ResultValue a -> ResultContainer (ResultData a)+resultValueToContainer x =+ ResultContainer {+ resultContainerName = resultValueName x,+ resultContainerId = resultValueId x,+ resultContainerData = resultValueData x,+ resultContainerSignal = resultValueSignal x }++-- | Convert the result container to a value.+resultContainerToValue :: ResultContainer (ResultData a) -> ResultValue a+resultContainerToValue x =+ ResultValue {+ resultValueName = resultContainerName x,+ resultValueId = resultContainerId x,+ resultValueData = resultContainerData x,+ resultValueSignal = resultContainerSignal x }++-- | Represents the very simulation results.+type ResultData e = Maybe (Event e)++-- | Whether an object containing the results emits a signal notifying about change of data.+data ResultSignal = EmptyResultSignal+ -- ^ There is no signal at all.+ | UnknownResultSignal+ -- ^ The signal is unknown, but the entity probably changes.+ | ResultSignal (Signal ())+ -- ^ When the signal is precisely specified.+ | ResultSignalMix (Signal ())+ -- ^ When the specified signal was combined with unknown signal.++instance Monoid ResultSignal where++ mempty = EmptyResultSignal++ mappend EmptyResultSignal z = z++ mappend UnknownResultSignal EmptyResultSignal = UnknownResultSignal+ mappend UnknownResultSignal UnknownResultSignal = UnknownResultSignal+ mappend UnknownResultSignal (ResultSignal x) = ResultSignalMix x+ mappend UnknownResultSignal z@(ResultSignalMix x) = z+ + mappend z@(ResultSignal x) EmptyResultSignal = z+ mappend (ResultSignal x) UnknownResultSignal = ResultSignalMix x+ mappend (ResultSignal x) (ResultSignal y) = ResultSignal (x <> y)+ mappend (ResultSignal x) (ResultSignalMix y) = ResultSignalMix (x <> y)+ + mappend z@(ResultSignalMix x) EmptyResultSignal = z+ mappend z@(ResultSignalMix x) UnknownResultSignal = z+ mappend (ResultSignalMix x) (ResultSignal y) = ResultSignalMix (x <> y)+ mappend (ResultSignalMix x) (ResultSignalMix y) = ResultSignalMix (x <> y)++-- | Construct a new result signal by the specified optional pure signal.+maybeResultSignal :: Maybe (Signal ()) -> ResultSignal+maybeResultSignal (Just x) = ResultSignal x+maybeResultSignal Nothing = EmptyResultSignal++instance ResultItemable (ResultValue Int) where++ resultItemName = resultValueName+ resultItemId = resultValueId+ resultItemSignal = resultValueSignal+ + resultItemToIntValue = id+ resultItemToIntListValue = fmap return+ resultItemToIntStatsValue = fmap returnSamplingStats+ resultItemToIntTimingStatsValue = voidResultValue++ resultItemToDoubleValue = fmap fromIntegral+ resultItemToDoubleListValue = fmap (return . fromIntegral)+ resultItemToDoubleStatsValue = fmap (returnSamplingStats . fromIntegral)+ resultItemToDoubleTimingStatsValue = voidResultValue++ resultItemToStringValue = fmap show++ resultItemExpansion = ResultItemSource . ResultItem+ resultItemSummary = ResultItemSource . ResultItem++instance ResultItemable (ResultValue Double) where++ resultItemName = resultValueName+ resultItemId = resultValueId+ resultItemSignal = resultValueSignal+ + resultItemToIntValue = voidResultValue+ resultItemToIntListValue = voidResultValue+ resultItemToIntStatsValue = voidResultValue+ resultItemToIntTimingStatsValue = voidResultValue+ + resultItemToDoubleValue = id+ resultItemToDoubleListValue = fmap return+ resultItemToDoubleStatsValue = fmap returnSamplingStats+ resultItemToDoubleTimingStatsValue = voidResultValue++ resultItemToStringValue = fmap show+ + resultItemExpansion = ResultItemSource . ResultItem+ resultItemSummary = ResultItemSource . ResultItem++instance ResultItemable (ResultValue [Int]) where++ resultItemName = resultValueName+ resultItemId = resultValueId+ resultItemSignal = resultValueSignal+ + resultItemToIntValue = voidResultValue+ resultItemToIntListValue = id+ resultItemToIntStatsValue = fmap listSamplingStats+ resultItemToIntTimingStatsValue = voidResultValue++ resultItemToDoubleValue = voidResultValue+ resultItemToDoubleListValue = fmap (map fromIntegral)+ resultItemToDoubleStatsValue = fmap (fromIntSamplingStats . listSamplingStats)+ resultItemToDoubleTimingStatsValue = voidResultValue++ resultItemToStringValue = fmap show+ + resultItemExpansion = ResultItemSource . ResultItem+ resultItemSummary = ResultItemSource . ResultItem++instance ResultItemable (ResultValue [Double]) where++ resultItemName = resultValueName+ resultItemId = resultValueId+ resultItemSignal = resultValueSignal+ + resultItemToIntValue = voidResultValue+ resultItemToIntListValue = voidResultValue+ resultItemToIntStatsValue = voidResultValue+ resultItemToIntTimingStatsValue = voidResultValue+ + resultItemToDoubleValue = voidResultValue+ resultItemToDoubleListValue = id+ resultItemToDoubleStatsValue = fmap listSamplingStats+ resultItemToDoubleTimingStatsValue = voidResultValue++ resultItemToStringValue = fmap show+ + resultItemExpansion = ResultItemSource . ResultItem+ resultItemSummary = ResultItemSource . ResultItem++instance ResultItemable (ResultValue (SamplingStats Int)) where++ resultItemName = resultValueName+ resultItemId = resultValueId+ resultItemSignal = resultValueSignal+ + resultItemToIntValue = voidResultValue+ resultItemToIntListValue = voidResultValue+ resultItemToIntStatsValue = id+ resultItemToIntTimingStatsValue = voidResultValue++ resultItemToDoubleValue = voidResultValue+ resultItemToDoubleListValue = voidResultValue+ resultItemToDoubleStatsValue = fmap fromIntSamplingStats+ resultItemToDoubleTimingStatsValue = voidResultValue++ resultItemToStringValue = fmap show+ + resultItemExpansion = samplingStatsResultSource+ resultItemSummary = samplingStatsResultSummary++instance ResultItemable (ResultValue (SamplingStats Double)) where++ resultItemName = resultValueName+ resultItemId = resultValueId+ resultItemSignal = resultValueSignal+ + resultItemToIntValue = voidResultValue+ resultItemToIntListValue = voidResultValue+ resultItemToIntStatsValue = voidResultValue+ resultItemToIntTimingStatsValue = voidResultValue+ + resultItemToDoubleValue = voidResultValue+ resultItemToDoubleListValue = voidResultValue+ resultItemToDoubleStatsValue = id+ resultItemToDoubleTimingStatsValue = voidResultValue++ resultItemToStringValue = fmap show+ + resultItemExpansion = samplingStatsResultSource+ resultItemSummary = samplingStatsResultSummary++instance ResultItemable (ResultValue (TimingStats Int)) where++ resultItemName = resultValueName+ resultItemId = resultValueId+ resultItemSignal = resultValueSignal+ + resultItemToIntValue = voidResultValue+ resultItemToIntListValue = voidResultValue+ resultItemToIntStatsValue = voidResultValue+ resultItemToIntTimingStatsValue = id++ resultItemToDoubleValue = voidResultValue+ resultItemToDoubleListValue = voidResultValue+ resultItemToDoubleStatsValue = voidResultValue+ resultItemToDoubleTimingStatsValue = fmap fromIntTimingStats++ resultItemToStringValue = fmap show+ + resultItemExpansion = timingStatsResultSource+ resultItemSummary = timingStatsResultSummary++instance ResultItemable (ResultValue (TimingStats Double)) where++ resultItemName = resultValueName+ resultItemId = resultValueId+ resultItemSignal = resultValueSignal+ + resultItemToIntValue = voidResultValue+ resultItemToIntListValue = voidResultValue+ resultItemToIntStatsValue = voidResultValue+ resultItemToIntTimingStatsValue = voidResultValue++ resultItemToDoubleValue = voidResultValue+ resultItemToDoubleListValue = voidResultValue+ resultItemToDoubleStatsValue = voidResultValue+ resultItemToDoubleTimingStatsValue = id++ resultItemToStringValue = fmap show+ + resultItemExpansion = timingStatsResultSource+ resultItemSummary = timingStatsResultSummary++instance ResultItemable (ResultValue Bool) where++ resultItemName = resultValueName+ resultItemId = resultValueId+ resultItemSignal = resultValueSignal+ + resultItemToIntValue = voidResultValue+ resultItemToIntListValue = voidResultValue+ resultItemToIntStatsValue = voidResultValue+ resultItemToIntTimingStatsValue = voidResultValue++ resultItemToDoubleValue = voidResultValue+ resultItemToDoubleListValue = voidResultValue+ resultItemToDoubleStatsValue = voidResultValue+ resultItemToDoubleTimingStatsValue = voidResultValue++ resultItemToStringValue = fmap show++ resultItemExpansion = ResultItemSource . ResultItem+ resultItemSummary = ResultItemSource . ResultItem++instance ResultItemable (ResultValue String) where++ resultItemName = resultValueName+ resultItemId = resultValueId+ resultItemSignal = resultValueSignal+ + resultItemToIntValue = voidResultValue+ resultItemToIntListValue = voidResultValue+ resultItemToIntStatsValue = voidResultValue+ resultItemToIntTimingStatsValue = voidResultValue++ resultItemToDoubleValue = voidResultValue+ resultItemToDoubleListValue = voidResultValue+ resultItemToDoubleStatsValue = voidResultValue+ resultItemToDoubleTimingStatsValue = voidResultValue++ resultItemToStringValue = id++ resultItemExpansion = ResultItemSource . ResultItem+ resultItemSummary = ResultItemSource . ResultItem++instance ResultItemable (ResultValue ()) where++ resultItemName = resultValueName+ resultItemId = resultValueId+ resultItemSignal = resultValueSignal+ + resultItemToIntValue = voidResultValue+ resultItemToIntListValue = voidResultValue+ resultItemToIntStatsValue = voidResultValue+ resultItemToIntTimingStatsValue = voidResultValue++ resultItemToDoubleValue = voidResultValue+ resultItemToDoubleListValue = voidResultValue+ resultItemToDoubleStatsValue = voidResultValue+ resultItemToDoubleTimingStatsValue = voidResultValue++ resultItemToStringValue = fmap show++ resultItemExpansion = ResultItemSource . ResultItem+ resultItemSummary = ResultItemSource . ResultItem++instance ResultItemable (ResultValue FCFS) where++ resultItemName = resultValueName+ resultItemId = resultValueId+ resultItemSignal = resultValueSignal+ + resultItemToIntValue = voidResultValue+ resultItemToIntListValue = voidResultValue+ resultItemToIntStatsValue = voidResultValue+ resultItemToIntTimingStatsValue = voidResultValue++ resultItemToDoubleValue = voidResultValue+ resultItemToDoubleListValue = voidResultValue+ resultItemToDoubleStatsValue = voidResultValue+ resultItemToDoubleTimingStatsValue = voidResultValue++ resultItemToStringValue = fmap show++ resultItemExpansion = ResultItemSource . ResultItem+ resultItemSummary = ResultItemSource . ResultItem++instance ResultItemable (ResultValue LCFS) where++ resultItemName = resultValueName+ resultItemId = resultValueId+ resultItemSignal = resultValueSignal+ + resultItemToIntValue = voidResultValue+ resultItemToIntListValue = voidResultValue+ resultItemToIntStatsValue = voidResultValue+ resultItemToIntTimingStatsValue = voidResultValue++ resultItemToDoubleValue = voidResultValue+ resultItemToDoubleListValue = voidResultValue+ resultItemToDoubleStatsValue = voidResultValue+ resultItemToDoubleTimingStatsValue = voidResultValue++ resultItemToStringValue = fmap show++ resultItemExpansion = ResultItemSource . ResultItem+ resultItemSummary = ResultItemSource . ResultItem++instance ResultItemable (ResultValue SIRO) where++ resultItemName = resultValueName+ resultItemId = resultValueId+ resultItemSignal = resultValueSignal+ + resultItemToIntValue = voidResultValue+ resultItemToIntListValue = voidResultValue+ resultItemToIntStatsValue = voidResultValue+ resultItemToIntTimingStatsValue = voidResultValue++ resultItemToDoubleValue = voidResultValue+ resultItemToDoubleListValue = voidResultValue+ resultItemToDoubleStatsValue = voidResultValue+ resultItemToDoubleTimingStatsValue = voidResultValue++ resultItemToStringValue = fmap show++ resultItemExpansion = ResultItemSource . ResultItem+ resultItemSummary = ResultItemSource . ResultItem++instance ResultItemable (ResultValue StaticPriorities) where++ resultItemName = resultValueName+ resultItemId = resultValueId+ resultItemSignal = resultValueSignal+ + resultItemToIntValue = voidResultValue+ resultItemToIntListValue = voidResultValue+ resultItemToIntStatsValue = voidResultValue+ resultItemToIntTimingStatsValue = voidResultValue++ resultItemToDoubleValue = voidResultValue+ resultItemToDoubleListValue = voidResultValue+ resultItemToDoubleStatsValue = voidResultValue+ resultItemToDoubleTimingStatsValue = voidResultValue++ resultItemToStringValue = fmap show++ resultItemExpansion = ResultItemSource . ResultItem+ resultItemSummary = ResultItemSource . ResultItem++-- | Flatten the result source.+flattenResultSource :: ResultSource -> [ResultItem]+flattenResultSource (ResultItemSource x) = [x]+flattenResultSource (ResultObjectSource x) =+ concat $ map (flattenResultSource . resultPropertySource) $ resultObjectProperties x+flattenResultSource (ResultVectorSource x) =+ concat $ map flattenResultSource $ A.elems $ resultVectorItems x+flattenResultSource (ResultSeparatorSource x) = []++-- | Return the result source name.+resultSourceName :: ResultSource -> ResultName+resultSourceName (ResultItemSource (ResultItem x)) = resultItemName x+resultSourceName (ResultObjectSource x) = resultObjectName x+resultSourceName (ResultVectorSource x) = resultVectorName x+resultSourceName (ResultSeparatorSource x) = []++-- | Expand the result source returning a more detailed version expanding the properties as possible.+expandResultSource :: ResultSource -> ResultSource+expandResultSource (ResultItemSource (ResultItem x)) = resultItemExpansion x+expandResultSource (ResultObjectSource x) =+ ResultObjectSource $+ x { resultObjectProperties =+ flip fmap (resultObjectProperties x) $ \p ->+ p { resultPropertySource = expandResultSource (resultPropertySource p) } }+expandResultSource (ResultVectorSource x) =+ ResultVectorSource $+ x { resultVectorItems =+ A.array bnds [(i, expandResultSource e) | (i, e) <- ies] }+ where arr = resultVectorItems x+ bnds = A.bounds arr+ ies = A.assocs arr+expandResultSource z@(ResultSeparatorSource x) = z++-- | Return a summarised and usually more short version of the result source expanding the main properties or excluding auxiliary properties if required.+resultSourceSummary :: ResultSource -> ResultSource+resultSourceSummary (ResultItemSource (ResultItem x)) = resultItemSummary x+resultSourceSummary (ResultObjectSource x) = resultObjectSummary x+resultSourceSummary (ResultVectorSource x) = resultVectorSummary x+resultSourceSummary z@(ResultSeparatorSource x) = z++-- | Return a signal emitted by the source.+resultSourceSignal :: ResultSource -> ResultSignal+resultSourceSignal (ResultItemSource (ResultItem x)) = resultItemSignal x+resultSourceSignal (ResultObjectSource x) = resultObjectSignal x+resultSourceSignal (ResultVectorSource x) = resultVectorSignal x+resultSourceSignal (ResultSeparatorSource x) = EmptyResultSignal++-- | Represent the result source as integer numbers.+resultSourceToIntValues :: ResultSource -> [ResultValue Int]+resultSourceToIntValues = map (\(ResultItem x) -> resultItemToIntValue x) . flattenResultSource++-- | Represent the result source as lists of integer numbers.+resultSourceToIntListValues :: ResultSource -> [ResultValue [Int]]+resultSourceToIntListValues = map (\(ResultItem x) -> resultItemToIntListValue x) . flattenResultSource++-- | Represent the result source as statistics based on integer numbers.+resultSourceToIntStatsValues :: ResultSource -> [ResultValue (SamplingStats Int)]+resultSourceToIntStatsValues = map (\(ResultItem x) -> resultItemToIntStatsValue x) . flattenResultSource++-- | Represent the result source as statistics based on integer numbers and optimised for fast aggregation.+resultSourceToIntStatsEitherValues :: ResultSource -> [ResultValue (Either Int (SamplingStats Int))]+resultSourceToIntStatsEitherValues = map (\(ResultItem x) -> resultItemToIntStatsEitherValue x) . flattenResultSource++-- | Represent the result source as timing statistics based on integer numbers.+resultSourceToIntTimingStatsValues :: ResultSource -> [ResultValue (TimingStats Int)]+resultSourceToIntTimingStatsValues = map (\(ResultItem x) -> resultItemToIntTimingStatsValue x) . flattenResultSource++-- | Represent the result source as double floating point numbers.+resultSourceToDoubleValues :: ResultSource -> [ResultValue Double]+resultSourceToDoubleValues = map (\(ResultItem x) -> resultItemToDoubleValue x) . flattenResultSource++-- | Represent the result source as lists of double floating point numbers.+resultSourceToDoubleListValues :: ResultSource -> [ResultValue [Double]]+resultSourceToDoubleListValues = map (\(ResultItem x) -> resultItemToDoubleListValue x) . flattenResultSource++-- | Represent the result source as statistics based on double floating point numbers.+resultSourceToDoubleStatsValues :: ResultSource -> [ResultValue (SamplingStats Double)]+resultSourceToDoubleStatsValues = map (\(ResultItem x) -> resultItemToDoubleStatsValue x) . flattenResultSource++-- | Represent the result source as statistics based on double floating point numbers and optimised for fast aggregation.+resultSourceToDoubleStatsEitherValues :: ResultSource -> [ResultValue (Either Double (SamplingStats Double))]+resultSourceToDoubleStatsEitherValues = map (\(ResultItem x) -> resultItemToDoubleStatsEitherValue x) . flattenResultSource++-- | Represent the result source as timing statistics based on double floating point numbers.+resultSourceToDoubleTimingStatsValues :: ResultSource -> [ResultValue (TimingStats Double)]+resultSourceToDoubleTimingStatsValues = map (\(ResultItem x) -> resultItemToDoubleTimingStatsValue x) . flattenResultSource++-- | Represent the result source as string values.+resultSourceToStringValues :: ResultSource -> [ResultValue String]+resultSourceToStringValues = map (\(ResultItem x) -> resultItemToStringValue x) . flattenResultSource++-- | It contains the results of simulation.+data Results =+ Results { resultSourceMap :: ResultSourceMap,+ -- ^ The sources of simulation results as a map of associated names.+ resultSourceList :: [ResultSource]+ -- ^ The sources of simulation results as an ordered list.+ }++-- | It transforms the results of simulation.+type ResultTransform = Results -> Results++-- | It representes the predefined signals provided by every simulation model.+data ResultPredefinedSignals =+ ResultPredefinedSignals { resultSignalInIntegTimes :: Signal Double,+ -- ^ The signal triggered in the integration time points.+ resultSignalInStartTime :: Signal Double,+ -- ^ The signal triggered in the start time.+ resultSignalInStopTime :: Signal Double+ -- ^ The signal triggered in the stop time.+ }++-- | Create the predefined signals provided by every simulation model.+newResultPredefinedSignals :: Simulation ResultPredefinedSignals+newResultPredefinedSignals = runDynamicsInStartTime $ runEventWith EarlierEvents d where+ d = do signalInIntegTimes <- newSignalInIntegTimes+ signalInStartTime <- newSignalInStartTime+ signalInStopTime <- newSignalInStopTime+ return ResultPredefinedSignals { resultSignalInIntegTimes = signalInIntegTimes,+ resultSignalInStartTime = signalInStartTime,+ resultSignalInStopTime = signalInStopTime }++instance Monoid Results where++ mempty = results mempty+ mappend x y = results $ resultSourceList x <> resultSourceList y++-- | Prepare the simulation results.+results :: [ResultSource] -> Results+results ms =+ Results { resultSourceMap = M.fromList $ map (\x -> (resultSourceName x, x)) ms,+ resultSourceList = ms }++-- | Represent the results as integer numbers.+resultsToIntValues :: Results -> [ResultValue Int]+resultsToIntValues = concat . map resultSourceToIntValues . resultSourceList++-- | Represent the results as lists of integer numbers.+resultsToIntListValues :: Results -> [ResultValue [Int]]+resultsToIntListValues = concat . map resultSourceToIntListValues . resultSourceList++-- | Represent the results as statistics based on integer numbers.+resultsToIntStatsValues :: Results -> [ResultValue (SamplingStats Int)]+resultsToIntStatsValues = concat . map resultSourceToIntStatsValues . resultSourceList++-- | Represent the results as statistics based on integer numbers and optimised for fast aggregation.+resultsToIntStatsEitherValues :: Results -> [ResultValue (Either Int (SamplingStats Int))]+resultsToIntStatsEitherValues = concat . map resultSourceToIntStatsEitherValues . resultSourceList++-- | Represent the results as timing statistics based on integer numbers.+resultsToIntTimingStatsValues :: Results -> [ResultValue (TimingStats Int)]+resultsToIntTimingStatsValues = concat . map resultSourceToIntTimingStatsValues . resultSourceList++-- | Represent the results as double floating point numbers.+resultsToDoubleValues :: Results -> [ResultValue Double]+resultsToDoubleValues = concat . map resultSourceToDoubleValues . resultSourceList++-- | Represent the results as lists of double floating point numbers.+resultsToDoubleListValues :: Results -> [ResultValue [Double]]+resultsToDoubleListValues = concat . map resultSourceToDoubleListValues . resultSourceList++-- | Represent the results as statistics based on double floating point numbers.+resultsToDoubleStatsValues :: Results -> [ResultValue (SamplingStats Double)]+resultsToDoubleStatsValues = concat . map resultSourceToDoubleStatsValues . resultSourceList++-- | Represent the results as statistics based on double floating point numbers and optimised for fast aggregation.+resultsToDoubleStatsEitherValues :: Results -> [ResultValue (Either Double (SamplingStats Double))]+resultsToDoubleStatsEitherValues = concat . map resultSourceToDoubleStatsEitherValues . resultSourceList++-- | Represent the results as timing statistics based on double floating point numbers.+resultsToDoubleTimingStatsValues :: Results -> [ResultValue (TimingStats Double)]+resultsToDoubleTimingStatsValues = concat . map resultSourceToDoubleTimingStatsValues . resultSourceList++-- | Represent the results as string values.+resultsToStringValues :: Results -> [ResultValue String]+resultsToStringValues = concat . map resultSourceToStringValues . resultSourceList++-- | Return a signal emitted by the specified results.+resultSignal :: Results -> ResultSignal+resultSignal = mconcat . map resultSourceSignal . resultSourceList++-- | Return an expanded version of the simulation results expanding the properties as possible, which+-- takes place for expanding statistics to show the count, average, deviation, minimum, maximum etc.+-- as separate values.+expandResults :: ResultTransform+expandResults = results . map expandResultSource . resultSourceList++-- | Return a short version of the simulation results, i.e. their summary, expanding the main properties+-- or excluding auxiliary properties if required.+resultSummary :: ResultTransform+resultSummary = results . map resultSourceSummary . resultSourceList++-- | Take a result by its name.+resultByName :: ResultName -> ResultTransform+resultByName name rs =+ case M.lookup name (resultSourceMap rs) of+ Just x -> results [x]+ Nothing ->+ error $+ "Not found result source with name " ++ name +++ ": resultByName"++-- | Take a result from the object with the specified property label,+-- but it is more preferrable to refer to the property by its 'ResultId'+-- identifier with help of the 'resultById' function.+resultByProperty :: ResultName -> ResultTransform+resultByProperty label rs = flip composeResults rs loop+ where+ loop x =+ case x of+ ResultObjectSource s ->+ let ps =+ flip filter (resultObjectProperties s) $ \p ->+ resultPropertyLabel p == label+ in case ps of+ [] ->+ error $+ "Not found property " ++ label +++ " for object " ++ resultObjectName s +++ ": resultByProperty"+ ps ->+ map resultPropertySource ps+ ResultVectorSource s ->+ concat $ map loop $ A.elems $ resultVectorItems s+ x ->+ error $+ "Result source " ++ resultSourceName x +++ " is neither object, nor vector " +++ ": resultByProperty"++-- | Take a result from the object with the specified identifier. It can identify+-- an item, object property, the object iself, vector or its elements.+resultById :: ResultId -> ResultTransform+resultById i rs = flip composeResults rs loop+ where+ loop x =+ case x of+ ResultItemSource (ResultItem s) ->+ if resultItemId s == i+ then [x]+ else error $+ "Expected to find item with Id = " ++ show i +++ ", while the item " ++ resultItemName s +++ " has actual Id = " ++ show (resultItemId s) +++ ": resultById"+ ResultObjectSource s ->+ if resultObjectId s == i+ then [x]+ else let ps =+ flip filter (resultObjectProperties s) $ \p ->+ resultPropertyId p == i+ in case ps of+ [] ->+ error $+ "Not found property with Id = " ++ show i +++ " for object " ++ resultObjectName s +++ " that has actual Id = " ++ show (resultObjectId s) +++ ": resultById"+ ps ->+ map resultPropertySource ps+ ResultVectorSource s ->+ if resultVectorId s == i+ then [x]+ else concat $ map loop $ A.elems $ resultVectorItems s+ x ->+ error $+ "Result source " ++ resultSourceName x +++ " is neither item, nor object, nor vector " +++ ": resultById"++-- | Take a result from the vector by the specified integer index.+resultByIndex :: Int -> ResultTransform+resultByIndex index rs = flip composeResults rs loop+ where+ loop x =+ case x of+ ResultVectorSource s ->+ [resultVectorItems s A.! index] + x ->+ error $+ "Result source " ++ resultSourceName x +++ " is not vector " +++ ": resultByIndex"++-- | Take a result from the vector by the specified string subscript.+resultBySubscript :: ResultName -> ResultTransform+resultBySubscript subscript rs = flip composeResults rs loop+ where+ loop x =+ case x of+ ResultVectorSource s ->+ let ys = A.elems $ resultVectorItems s+ zs = A.elems $ resultVectorSubscript s+ ps =+ flip filter (zip ys zs) $ \(y, z) ->+ z == subscript+ in case ps of+ [] ->+ error $+ "Not found subscript " ++ subscript +++ " for vector " ++ resultVectorName s +++ ": resultBySubscript"+ ps ->+ map fst ps+ x ->+ error $+ "Result source " ++ resultSourceName x +++ " is not vector " +++ ": resultBySubscript"++-- | Compose the results using the specified transformation function.+composeResults :: (ResultSource -> [ResultSource]) -> ResultTransform+composeResults f =+ results . concat . map f . resultSourceList++-- | Concatenate the results using the specified list of transformation functions.+concatResults :: [ResultTransform] -> ResultTransform+concatResults trs rs =+ results $ concat $ map (\tr -> resultSourceList $ tr rs) trs++-- | Append the results using the specified transformation functions.+appendResults :: ResultTransform -> ResultTransform -> ResultTransform+appendResults x y =+ concatResults [x, y]++-- | Return a pure signal as a result of combination of the predefined signals+-- with the specified result signal usually provided by the sources.+--+-- The signal returned is triggered when the source signal is triggered.+-- The pure signal is also triggered in the integration time points+-- if the source signal is unknown or it was combined with any unknown signal.+pureResultSignal :: ResultPredefinedSignals -> ResultSignal -> Signal ()+pureResultSignal rs EmptyResultSignal =+ void (resultSignalInStartTime rs)+pureResultSignal rs UnknownResultSignal =+ void (resultSignalInIntegTimes rs)+pureResultSignal rs (ResultSignal s) =+ void (resultSignalInStartTime rs) <> void (resultSignalInStopTime rs) <> s+pureResultSignal rs (ResultSignalMix s) =+ void (resultSignalInIntegTimes rs) <> s++-- | Defines a final result extract: its name, values and other data.+data ResultExtract e =+ ResultExtract { resultExtractName :: ResultName,+ -- ^ The result name.+ resultExtractId :: ResultId,+ -- ^ The result identifier.+ resultExtractData :: Event e,+ -- ^ The result values.+ resultExtractSignal :: ResultSignal+ -- ^ Whether the result emits a signal.+ }++-- | Extract the results as integer values, or raise a conversion error.+extractIntResults :: Results -> [ResultExtract Int]+extractIntResults rs = flip map (resultsToIntValues rs) $ \x ->+ let n = resultValueName x+ i = resultValueId x+ a = resultValueData x+ s = resultValueSignal x+ in case a of+ Nothing ->+ error $+ "Cannot represent variable " ++ n +++ " as a source of integer values: extractIntResults"+ Just a ->+ ResultExtract n i a s++-- | Extract the results as lists of integer values, or raise a conversion error.+extractIntListResults :: Results -> [ResultExtract [Int]]+extractIntListResults rs = flip map (resultsToIntListValues rs) $ \x ->+ let n = resultValueName x+ i = resultValueId x+ a = resultValueData x+ s = resultValueSignal x+ in case a of+ Nothing ->+ error $+ "Cannot represent variable " ++ n +++ " as a source of lists of integer values: extractIntListResults"+ Just a ->+ ResultExtract n i a s++-- | Extract the results as statistics based on integer values,+-- or raise a conversion error.+extractIntStatsResults :: Results -> [ResultExtract (SamplingStats Int)]+extractIntStatsResults rs = flip map (resultsToIntStatsValues rs) $ \x ->+ let n = resultValueName x+ i = resultValueId x+ a = resultValueData x+ s = resultValueSignal x+ in case a of+ Nothing ->+ error $+ "Cannot represent variable " ++ n +++ " as a source of statistics based on integer values: extractIntStatsResults"+ Just a ->+ ResultExtract n i a s++-- | Extract the results as statistics based on integer values and optimised+-- for fast aggregation, or raise a conversion error.+extractIntStatsEitherResults :: Results -> [ResultExtract (Either Int (SamplingStats Int))]+extractIntStatsEitherResults rs = flip map (resultsToIntStatsEitherValues rs) $ \x ->+ let n = resultValueName x+ i = resultValueId x+ a = resultValueData x+ s = resultValueSignal x+ in case a of+ Nothing ->+ error $+ "Cannot represent variable " ++ n +++ " as a source of statistics based on integer values: extractIntStatsEitherResults"+ Just a ->+ ResultExtract n i a s++-- | Extract the results as timing statistics based on integer values,+-- or raise a conversion error.+extractIntTimingStatsResults :: Results -> [ResultExtract (TimingStats Int)]+extractIntTimingStatsResults rs = flip map (resultsToIntTimingStatsValues rs) $ \x ->+ let n = resultValueName x+ i = resultValueId x+ a = resultValueData x+ s = resultValueSignal x+ in case a of+ Nothing ->+ error $+ "Cannot represent variable " ++ n +++ " as a source of timing statistics based on integer values: extractIntTimingStatsResults"+ Just a ->+ ResultExtract n i a s++-- | Extract the results as double floating point values, or raise a conversion error.+extractDoubleResults :: Results -> [ResultExtract Double]+extractDoubleResults rs = flip map (resultsToDoubleValues rs) $ \x ->+ let n = resultValueName x+ i = resultValueId x+ a = resultValueData x+ s = resultValueSignal x+ in case a of+ Nothing ->+ error $+ "Cannot represent variable " ++ n +++ " as a source of double floating point values: extractDoubleResults"+ Just a ->+ ResultExtract n i a s++-- | Extract the results as lists of double floating point values,+-- or raise a conversion error.+extractDoubleListResults :: Results -> [ResultExtract [Double]]+extractDoubleListResults rs = flip map (resultsToDoubleListValues rs) $ \x ->+ let n = resultValueName x+ i = resultValueId x+ a = resultValueData x+ s = resultValueSignal x+ in case a of+ Nothing ->+ error $+ "Cannot represent variable " ++ n +++ " as a source of lists of double floating point values: extractDoubleListResults"+ Just a ->+ ResultExtract n i a s++-- | Extract the results as statistics based on double floating point values,+-- or raise a conversion error.+extractDoubleStatsResults :: Results -> [ResultExtract (SamplingStats Double)]+extractDoubleStatsResults rs = flip map (resultsToDoubleStatsValues rs) $ \x ->+ let n = resultValueName x+ i = resultValueId x+ a = resultValueData x+ s = resultValueSignal x+ in case a of+ Nothing ->+ error $+ "Cannot represent variable " ++ n +++ " as a source of statistics based on double floating point values: extractDoubleStatsResults"+ Just a ->+ ResultExtract n i a s++-- | Extract the results as statistics based on double floating point values+-- and optimised for fast aggregation, or raise a conversion error.+extractDoubleStatsEitherResults :: Results -> [ResultExtract (Either Double (SamplingStats Double))]+extractDoubleStatsEitherResults rs = flip map (resultsToDoubleStatsEitherValues rs) $ \x ->+ let n = resultValueName x+ i = resultValueId x+ a = resultValueData x+ s = resultValueSignal x+ in case a of+ Nothing ->+ error $+ "Cannot represent variable " ++ n +++ " as a source of statistics based on double floating point values: extractDoubleStatsEitherResults"+ Just a ->+ ResultExtract n i a s++-- | Extract the results as timing statistics based on double floating point values,+-- or raise a conversion error.+extractDoubleTimingStatsResults :: Results -> [ResultExtract (TimingStats Double)]+extractDoubleTimingStatsResults rs = flip map (resultsToDoubleTimingStatsValues rs) $ \x ->+ let n = resultValueName x+ i = resultValueId x+ a = resultValueData x+ s = resultValueSignal x+ in case a of+ Nothing ->+ error $+ "Cannot represent variable " ++ n +++ " as a source of timing statistics based on double floating point values: extractDoubleTimingStatsResults"+ Just a ->+ ResultExtract n i a s++-- | Extract the results as string values, or raise a conversion error.+extractStringResults :: Results -> [ResultExtract String]+extractStringResults rs = flip map (resultsToStringValues rs) $ \x ->+ let n = resultValueName x+ i = resultValueId x+ a = resultValueData x+ s = resultValueSignal x+ in case a of+ Nothing ->+ error $+ "Cannot represent variable " ++ n +++ " as a source of string values: extractStringResults"+ Just a ->+ ResultExtract n i a s++-- | Represents a computation that can return the simulation data.+class ResultComputing m where++ -- | Compute data with the results of simulation.+ computeResultData :: m a -> ResultData a++ -- | Return the signal triggered when data change if such a signal exists.+ computeResultSignal :: m a -> ResultSignal++-- | Return a new result value by the specified name, identifier and computation.+computeResultValue :: ResultComputing m+ => ResultName+ -- ^ the result name+ -> ResultId+ -- ^ the result identifier+ -> m a+ -- ^ the result computation+ -> ResultValue a+computeResultValue name i m =+ ResultValue {+ resultValueName = name,+ resultValueId = i,+ resultValueData = computeResultData m,+ resultValueSignal = computeResultSignal m }++-- | Represents a computation that can return the simulation data.+data ResultComputation a =+ ResultComputation { resultComputationData :: ResultData a,+ -- ^ Return data from the computation.+ resultComputationSignal :: ResultSignal+ -- ^ Return a signal from the computation.+ }++instance ResultComputing ResultComputation where++ computeResultData = resultComputationData+ computeResultSignal = resultComputationSignal++instance ResultComputing Parameter where++ computeResultData = Just . liftParameter+ computeResultSignal = const UnknownResultSignal++instance ResultComputing Simulation where++ computeResultData = Just . liftSimulation+ computeResultSignal = const UnknownResultSignal++instance ResultComputing Dynamics where++ computeResultData = Just . liftDynamics+ computeResultSignal = const UnknownResultSignal++instance ResultComputing Event where++ computeResultData = Just . id+ computeResultSignal = const UnknownResultSignal++instance ResultComputing Ref where++ computeResultData = Just . readRef+ computeResultSignal = ResultSignal . refChanged_++instance ResultComputing LR.Ref where++ computeResultData = Just . LR.readRef+ computeResultSignal = const UnknownResultSignal++instance ResultComputing Var where++ computeResultData = Just . readVar+ computeResultSignal = ResultSignal . varChanged_++instance ResultComputing Signalable where++ computeResultData = Just . readSignalable+ computeResultSignal = ResultSignal . signalableChanged_+ +-- | Return a source by the specified statistics.+samplingStatsResultSource :: (ResultItemable (ResultValue a),+ ResultItemable (ResultValue (SamplingStats a)))+ => ResultValue (SamplingStats a)+ -- ^ the statistics+ -> ResultSource+samplingStatsResultSource x =+ ResultObjectSource $+ ResultObject {+ resultObjectName = resultValueName x,+ resultObjectId = resultValueId x,+ resultObjectTypeId = SamplingStatsId,+ resultObjectSignal = resultValueSignal x,+ resultObjectSummary = samplingStatsResultSummary x,+ resultObjectProperties = [+ resultContainerMapProperty c "count" SamplingStatsCountId samplingStatsCount,+ resultContainerMapProperty c "mean" SamplingStatsMeanId samplingStatsMean,+ resultContainerMapProperty c "mean2" SamplingStatsMean2Id samplingStatsMean2,+ resultContainerMapProperty c "std" SamplingStatsDeviationId samplingStatsDeviation,+ resultContainerMapProperty c "var" SamplingStatsVarianceId samplingStatsVariance,+ resultContainerMapProperty c "min" SamplingStatsMinId samplingStatsMin,+ resultContainerMapProperty c "max" SamplingStatsMaxId samplingStatsMax ] }+ where+ c = resultValueToContainer x++-- | Return the summary by the specified statistics.+samplingStatsResultSummary :: ResultItemable (ResultValue (SamplingStats a))+ => ResultValue (SamplingStats a)+ -- ^ the statistics+ -> ResultSource+samplingStatsResultSummary = ResultItemSource . ResultItem . resultItemToStringValue + +-- | Return a source by the specified timing statistics.+timingStatsResultSource :: (TimingData a,+ ResultItemable (ResultValue a),+ ResultItemable (ResultValue (TimingStats a)))+ => ResultValue (TimingStats a)+ -- ^ the statistics+ -> ResultSource+timingStatsResultSource x =+ ResultObjectSource $+ ResultObject {+ resultObjectName = resultValueName x,+ resultObjectId = resultValueId x,+ resultObjectTypeId = TimingStatsId,+ resultObjectSignal = resultValueSignal x,+ resultObjectSummary = timingStatsResultSummary x,+ resultObjectProperties = [+ resultContainerMapProperty c "count" TimingStatsCountId timingStatsCount,+ resultContainerMapProperty c "mean" TimingStatsMeanId timingStatsMean,+ resultContainerMapProperty c "std" TimingStatsDeviationId timingStatsDeviation,+ resultContainerMapProperty c "var" TimingStatsVarianceId timingStatsVariance,+ resultContainerMapProperty c "min" TimingStatsMinId timingStatsMin,+ resultContainerMapProperty c "max" TimingStatsMaxId timingStatsMax,+ resultContainerMapProperty c "minTime" TimingStatsMinTimeId timingStatsMinTime,+ resultContainerMapProperty c "maxTime" TimingStatsMaxTimeId timingStatsMaxTime,+ resultContainerMapProperty c "startTime" TimingStatsStartTimeId timingStatsStartTime,+ resultContainerMapProperty c "lastTime" TimingStatsLastTimeId timingStatsLastTime,+ resultContainerMapProperty c "sum" TimingStatsSumId timingStatsSum,+ resultContainerMapProperty c "sum2" TimingStatsSum2Id timingStatsSum2 ] }+ where+ c = resultValueToContainer x++-- | Return the summary by the specified timing statistics.+timingStatsResultSummary :: (TimingData a, ResultItemable (ResultValue (TimingStats a)))+ => ResultValue (TimingStats a) + -- ^ the statistics+ -> ResultSource+timingStatsResultSummary = ResultItemSource . ResultItem . resultItemToStringValue+ +-- | Return a source by the specified finite queue.+queueResultSource :: (Show si, Show sm, Show so,+ ResultItemable (ResultValue si),+ ResultItemable (ResultValue sm),+ ResultItemable (ResultValue so))+ => ResultContainer (Q.Queue si sm so a)+ -- ^ the queue container+ -> ResultSource+queueResultSource c =+ ResultObjectSource $+ ResultObject {+ resultObjectName = resultContainerName c,+ resultObjectId = resultContainerId c,+ resultObjectTypeId = FiniteQueueId,+ resultObjectSignal = resultContainerSignal c,+ resultObjectSummary = queueResultSummary c,+ resultObjectProperties = [+ resultContainerConstProperty c "enqueueStrategy" EnqueueStrategyId Q.enqueueStrategy,+ resultContainerConstProperty c "enqueueStoringStrategy" EnqueueStoringStrategyId Q.enqueueStoringStrategy,+ resultContainerConstProperty c "dequeueStrategy" DequeueStrategyId Q.dequeueStrategy,+ resultContainerProperty c "queueNull" QueueNullId Q.queueNull Q.queueNullChanged_,+ resultContainerProperty c "queueFull" QueueFullId Q.queueFull Q.queueFullChanged_,+ resultContainerConstProperty c "queueMaxCount" QueueMaxCountId Q.queueMaxCount,+ resultContainerProperty c "queueCount" QueueCountId Q.queueCount Q.queueCountChanged_,+ resultContainerProperty c "queueCountStats" QueueCountStatsId Q.queueCountStats Q.queueCountChanged_,+ resultContainerProperty c "enqueueCount" EnqueueCountId Q.enqueueCount Q.enqueueCountChanged_,+ resultContainerProperty c "enqueueLostCount" EnqueueLostCountId Q.enqueueLostCount Q.enqueueLostCountChanged_,+ resultContainerProperty c "enqueueStoreCount" EnqueueStoreCountId Q.enqueueStoreCount Q.enqueueStoreCountChanged_,+ resultContainerProperty c "dequeueCount" DequeueCountId Q.dequeueCount Q.dequeueCountChanged_,+ resultContainerProperty c "dequeueExtractCount" DequeueExtractCountId Q.dequeueExtractCount Q.dequeueExtractCountChanged_,+ resultContainerProperty c "queueLoadFactor" QueueLoadFactorId Q.queueLoadFactor Q.queueLoadFactorChanged_,+ resultContainerIntegProperty c "enqueueRate" EnqueueRateId Q.enqueueRate,+ resultContainerIntegProperty c "enqueueStoreRate" EnqueueStoreRateId Q.enqueueStoreRate,+ resultContainerIntegProperty c "dequeueRate" DequeueRateId Q.dequeueRate,+ resultContainerIntegProperty c "dequeueExtractRate" DequeueExtractRateId Q.dequeueExtractRate,+ resultContainerProperty c "queueWaitTime" QueueWaitTimeId Q.queueWaitTime Q.queueWaitTimeChanged_,+ resultContainerProperty c "queueTotalWaitTime" QueueTotalWaitTimeId Q.queueTotalWaitTime Q.queueTotalWaitTimeChanged_,+ resultContainerProperty c "enqueueWaitTime" EnqueueWaitTimeId Q.enqueueWaitTime Q.enqueueWaitTimeChanged_,+ resultContainerProperty c "dequeueWaitTime" DequeueWaitTimeId Q.dequeueWaitTime Q.dequeueWaitTimeChanged_,+ resultContainerProperty c "queueRate" QueueRateId Q.queueRate Q.queueRateChanged_ ] }++-- | Return the summary by the specified finite queue.+queueResultSummary :: (Show si, Show sm, Show so)+ => ResultContainer (Q.Queue si sm so a)+ -- ^ the queue container+ -> ResultSource+queueResultSummary c =+ ResultObjectSource $+ ResultObject {+ resultObjectName = resultContainerName c,+ resultObjectId = resultContainerId c,+ resultObjectTypeId = FiniteQueueId,+ resultObjectSignal = resultContainerSignal c,+ resultObjectSummary = queueResultSummary c,+ resultObjectProperties = [+ resultContainerConstProperty c "queueMaxCount" QueueMaxCountId Q.queueMaxCount,+ resultContainerProperty c "queueCountStats" QueueCountStatsId Q.queueCountStats Q.queueCountChanged_,+ resultContainerProperty c "enqueueCount" EnqueueCountId Q.enqueueCount Q.enqueueCountChanged_,+ resultContainerProperty c "enqueueLostCount" EnqueueLostCountId Q.enqueueLostCount Q.enqueueLostCountChanged_,+ resultContainerProperty c "enqueueStoreCount" EnqueueStoreCountId Q.enqueueStoreCount Q.enqueueStoreCountChanged_,+ resultContainerProperty c "dequeueCount" DequeueCountId Q.dequeueCount Q.dequeueCountChanged_,+ resultContainerProperty c "dequeueExtractCount" DequeueExtractCountId Q.dequeueExtractCount Q.dequeueExtractCountChanged_,+ resultContainerProperty c "queueLoadFactor" QueueLoadFactorId Q.queueLoadFactor Q.queueLoadFactorChanged_,+ resultContainerProperty c "queueWaitTime" QueueWaitTimeId Q.queueWaitTime Q.queueWaitTimeChanged_,+ resultContainerProperty c "queueRate" QueueRateId Q.queueRate Q.queueRateChanged_ ] }++-- | Return a source by the specified infinite queue.+infiniteQueueResultSource :: (Show sm, Show so,+ ResultItemable (ResultValue sm),+ ResultItemable (ResultValue so))+ => ResultContainer (IQ.Queue sm so a)+ -- ^ the queue container+ -> ResultSource+infiniteQueueResultSource c =+ ResultObjectSource $+ ResultObject {+ resultObjectName = resultContainerName c,+ resultObjectId = resultContainerId c,+ resultObjectTypeId = FiniteQueueId,+ resultObjectSignal = resultContainerSignal c,+ resultObjectSummary = infiniteQueueResultSummary c,+ resultObjectProperties = [+ resultContainerConstProperty c "enqueueStoringStrategy" EnqueueStoringStrategyId IQ.enqueueStoringStrategy,+ resultContainerConstProperty c "dequeueStrategy" DequeueStrategyId IQ.dequeueStrategy,+ resultContainerProperty c "queueNull" QueueNullId IQ.queueNull IQ.queueNullChanged_,+ resultContainerProperty c "queueCount" QueueCountId IQ.queueCount IQ.queueCountChanged_,+ resultContainerProperty c "queueCountStats" QueueCountStatsId IQ.queueCountStats IQ.queueCountChanged_,+ resultContainerProperty c "enqueueStoreCount" EnqueueStoreCountId IQ.enqueueStoreCount IQ.enqueueStoreCountChanged_,+ resultContainerProperty c "dequeueCount" DequeueCountId IQ.dequeueCount IQ.dequeueCountChanged_,+ resultContainerProperty c "dequeueExtractCount" DequeueExtractCountId IQ.dequeueExtractCount IQ.dequeueExtractCountChanged_,+ resultContainerIntegProperty c "enqueueStoreRate" EnqueueStoreRateId IQ.enqueueStoreRate,+ resultContainerIntegProperty c "dequeueRate" DequeueRateId IQ.dequeueRate,+ resultContainerIntegProperty c "dequeueExtractRate" DequeueExtractRateId IQ.dequeueExtractRate,+ resultContainerProperty c "queueWaitTime" QueueWaitTimeId IQ.queueWaitTime IQ.queueWaitTimeChanged_,+ resultContainerProperty c "dequeueWaitTime" DequeueWaitTimeId IQ.dequeueWaitTime IQ.dequeueWaitTimeChanged_,+ resultContainerProperty c "queueRate" QueueRateId IQ.queueRate IQ.queueRateChanged_ ] }++-- | Return the summary by the specified infinite queue.+infiniteQueueResultSummary :: (Show sm, Show so)+ => ResultContainer (IQ.Queue sm so a)+ -- ^ the queue container+ -> ResultSource+infiniteQueueResultSummary c =+ ResultObjectSource $+ ResultObject {+ resultObjectName = resultContainerName c,+ resultObjectId = resultContainerId c,+ resultObjectTypeId = FiniteQueueId,+ resultObjectSignal = resultContainerSignal c,+ resultObjectSummary = infiniteQueueResultSummary c,+ resultObjectProperties = [+ resultContainerProperty c "queueCountStats" QueueCountStatsId IQ.queueCountStats IQ.queueCountChanged_,+ resultContainerProperty c "enqueueStoreCount" EnqueueStoreCountId IQ.enqueueStoreCount IQ.enqueueStoreCountChanged_,+ resultContainerProperty c "dequeueCount" DequeueCountId IQ.dequeueCount IQ.dequeueCountChanged_,+ resultContainerProperty c "dequeueExtractCount" DequeueExtractCountId IQ.dequeueExtractCount IQ.dequeueExtractCountChanged_,+ resultContainerProperty c "queueWaitTime" QueueWaitTimeId IQ.queueWaitTime IQ.queueWaitTimeChanged_,+ resultContainerProperty c "queueRate" QueueRateId IQ.queueRate IQ.queueRateChanged_ ] }+ +-- | Return a source by the specified arrival timer.+arrivalTimerResultSource :: ResultContainer ArrivalTimer+ -- ^ the arrival timer container+ -> ResultSource+arrivalTimerResultSource c =+ ResultObjectSource $+ ResultObject {+ resultObjectName = resultContainerName c,+ resultObjectId = resultContainerId c,+ resultObjectTypeId = ArrivalTimerId,+ resultObjectSignal = resultContainerSignal c,+ resultObjectSummary = arrivalTimerResultSummary c,+ resultObjectProperties = [+ resultContainerProperty c "processingTime" ArrivalProcessingTimeId arrivalProcessingTime arrivalProcessingTimeChanged_ ] }++-- | Return the summary by the specified arrival timer.+arrivalTimerResultSummary :: ResultContainer ArrivalTimer+ -- ^ the arrival timer container+ -> ResultSource+arrivalTimerResultSummary c =+ ResultObjectSource $+ ResultObject {+ resultObjectName = resultContainerName c,+ resultObjectId = resultContainerId c,+ resultObjectTypeId = ArrivalTimerId,+ resultObjectSignal = resultContainerSignal c,+ resultObjectSummary = arrivalTimerResultSummary c,+ resultObjectProperties = [+ resultContainerProperty c "processingTime" ArrivalProcessingTimeId arrivalProcessingTime arrivalProcessingTimeChanged_ ] }++-- | Return a source by the specified server.+serverResultSource :: (Show s, ResultItemable (ResultValue s))+ => ResultContainer (Server s a b)+ -- ^ the server container+ -> ResultSource+serverResultSource c =+ ResultObjectSource $+ ResultObject {+ resultObjectName = resultContainerName c,+ resultObjectId = resultContainerId c,+ resultObjectTypeId = ServerId,+ resultObjectSignal = resultContainerSignal c,+ resultObjectSummary = serverResultSummary c,+ resultObjectProperties = [+ resultContainerConstProperty c "initState" ServerInitStateId serverInitState,+ resultContainerProperty c "state" ServerStateId serverState serverStateChanged_,+ resultContainerProperty c "totalInputWaitTime" ServerTotalInputWaitTimeId serverTotalInputWaitTime serverTotalInputWaitTimeChanged_,+ resultContainerProperty c "totalProcessingTime" ServerTotalProcessingTimeId serverTotalProcessingTime serverTotalProcessingTimeChanged_,+ resultContainerProperty c "totalOutputWaitTime" ServerTotalOutputWaitTimeId serverTotalOutputWaitTime serverTotalOutputWaitTimeChanged_,+ resultContainerProperty c "inputWaitTime" ServerInputWaitTimeId serverInputWaitTime serverInputWaitTimeChanged_,+ resultContainerProperty c "processingTime" ServerProcessingTimeId serverProcessingTime serverProcessingTimeChanged_,+ resultContainerProperty c "outputWaitTime" ServerOutputWaitTimeId serverOutputWaitTime serverOutputWaitTimeChanged_,+ resultContainerProperty c "inputWaitFactor" ServerInputWaitFactorId serverInputWaitFactor serverInputWaitFactorChanged_,+ resultContainerProperty c "processingFactor" ServerProcessingFactorId serverProcessingFactor serverProcessingFactorChanged_,+ resultContainerProperty c "outputWaitFactor" ServerOutputWaitFactorId serverOutputWaitFactor serverOutputWaitFactorChanged_ ] }++-- | Return the summary by the specified server.+serverResultSummary :: ResultContainer (Server s a b)+ -- ^ the server container+ -> ResultSource+serverResultSummary c =+ ResultObjectSource $+ ResultObject {+ resultObjectName = resultContainerName c,+ resultObjectId = resultContainerId c,+ resultObjectTypeId = ServerId,+ resultObjectSignal = resultContainerSignal c,+ resultObjectSummary = serverResultSummary c,+ resultObjectProperties = [+ resultContainerProperty c "inputWaitTime" ServerInputWaitTimeId serverInputWaitTime serverInputWaitTimeChanged_,+ resultContainerProperty c "processingTime" ServerProcessingTimeId serverProcessingTime serverProcessingTimeChanged_,+ resultContainerProperty c "outputWaitTime" ServerOutputWaitTimeId serverOutputWaitTime serverOutputWaitTimeChanged_,+ resultContainerProperty c "inputWaitFactor" ServerInputWaitFactorId serverInputWaitFactor serverInputWaitFactorChanged_,+ resultContainerProperty c "processingFactor" ServerProcessingFactorId serverProcessingFactor serverProcessingFactorChanged_,+ resultContainerProperty c "outputWaitFactor" ServerOutputWaitFactorId serverOutputWaitFactor serverOutputWaitFactorChanged_ ] }++-- | Return a source by the specified activity.+activityResultSource :: (Show s, ResultItemable (ResultValue s))+ => ResultContainer (Activity s a b)+ -- ^ the activity container+ -> ResultSource+activityResultSource c =+ ResultObjectSource $+ ResultObject {+ resultObjectName = resultContainerName c,+ resultObjectId = resultContainerId c,+ resultObjectTypeId = ActivityId,+ resultObjectSignal = resultContainerSignal c,+ resultObjectSummary = activityResultSummary c,+ resultObjectProperties = [+ resultContainerConstProperty c "initState" ActivityInitStateId activityInitState,+ resultContainerProperty c "state" ActivityStateId activityState activityStateChanged_,+ resultContainerProperty c "totalUtilisationTime" ActivityTotalUtilisationTimeId activityTotalUtilisationTime activityTotalUtilisationTimeChanged_,+ resultContainerProperty c "totalIdleTime" ActivityTotalIdleTimeId activityTotalIdleTime activityTotalIdleTimeChanged_,+ resultContainerProperty c "utilisationTime" ActivityUtilisationTimeId activityUtilisationTime activityUtilisationTimeChanged_,+ resultContainerProperty c "idleTime" ActivityIdleTimeId activityIdleTime activityIdleTimeChanged_,+ resultContainerProperty c "utilisationFactor" ActivityUtilisationFactorId activityUtilisationFactor activityUtilisationFactorChanged_,+ resultContainerProperty c "idleFactor" ActivityIdleTimeId activityIdleFactor activityIdleFactorChanged_ ] }++-- | Return a summary by the specified activity.+activityResultSummary :: ResultContainer (Activity s a b)+ -- ^ the activity container+ -> ResultSource+activityResultSummary c =+ ResultObjectSource $+ ResultObject {+ resultObjectName = resultContainerName c,+ resultObjectId = resultContainerId c,+ resultObjectTypeId = ActivityId,+ resultObjectSignal = resultContainerSignal c,+ resultObjectSummary = activityResultSummary c,+ resultObjectProperties = [+ resultContainerProperty c "utilisationTime" ActivityUtilisationTimeId activityUtilisationTime activityUtilisationTimeChanged_,+ resultContainerProperty c "idleTime" ActivityIdleTimeId activityIdleTime activityIdleTimeChanged_,+ resultContainerProperty c "utilisationFactor" ActivityUtilisationFactorId activityUtilisationFactor activityUtilisationFactorChanged_,+ resultContainerProperty c "idleFactor" ActivityIdleTimeId activityIdleFactor activityIdleFactorChanged_ ] }++-- | Return an arbitrary text as a separator source.+textResultSource :: String -> ResultSource+textResultSource text =+ ResultSeparatorSource $+ ResultSeparator { resultSeparatorText = text }++-- | Return the source of the modeling time.+timeResultSource :: ResultSource+timeResultSource = resultSource' "t" TimeId time+ +-- | Make an integer subscript+intSubscript :: Int -> ResultName+intSubscript i = "[" ++ show i ++ "]"++instance ResultComputing m => ResultProvider (m Double) where++ resultSource' name i m =+ ResultItemSource $ ResultItem $ computeResultValue name i m++instance ResultComputing m => ResultProvider (m [Double]) where++ resultSource' name i m =+ ResultItemSource $ ResultItem $ computeResultValue name i m++instance ResultComputing m => ResultProvider (m (SamplingStats Double)) where++ resultSource' name i m =+ ResultItemSource $ ResultItem $ computeResultValue name i m++instance ResultComputing m => ResultProvider (m (TimingStats Double)) where++ resultSource' name i m =+ ResultItemSource $ ResultItem $ computeResultValue name i m++instance ResultComputing m => ResultProvider (m Int) where++ resultSource' name i m =+ ResultItemSource $ ResultItem $ computeResultValue name i m++instance ResultComputing m => ResultProvider (m [Int]) where++ resultSource' name i m =+ ResultItemSource $ ResultItem $ computeResultValue name i m++instance ResultComputing m => ResultProvider (m (SamplingStats Int)) where++ resultSource' name i m =+ ResultItemSource $ ResultItem $ computeResultValue name i m++instance ResultComputing m => ResultProvider (m (TimingStats Int)) where++ resultSource' name i m =+ ResultItemSource $ ResultItem $ computeResultValue name i m++instance ResultComputing m => ResultProvider (m String) where++ resultSource' name i m =+ ResultItemSource $ ResultItem $ computeResultValue name i m++instance ResultProvider p => ResultProvider [p] where++ resultSource' name i m =+ resultSource' name i $ ResultListWithSubscript m subscript where+ subscript = map snd $ zip m $ map intSubscript [0..]++instance (Show i, Ix i, ResultProvider p) => ResultProvider (A.Array i p) where++ resultSource' name i m =+ resultSource' name i $ ResultListWithSubscript items subscript where+ items = A.elems m+ subscript = map (\i -> "[" ++ show i ++ "]") (A.indices m)++#ifndef __HASTE__++instance ResultProvider p => ResultProvider (V.Vector p) where+ + resultSource' name i m =+ resultSource' name i $ ResultVectorWithSubscript m subscript where+ subscript = V.imap (\i x -> intSubscript i) m++#endif++-- | Represents a list with the specified subscript.+data ResultListWithSubscript p =+ ResultListWithSubscript [p] [String]++-- | Represents an array with the specified subscript.+data ResultArrayWithSubscript i p =+ ResultArrayWithSubscript (A.Array i p) (A.Array i String)++#ifndef __HASTE__++-- | Represents a vector with the specified subscript.+data ResultVectorWithSubscript p =+ ResultVectorWithSubscript (V.Vector p) (V.Vector String)++#endif++instance ResultProvider p => ResultProvider (ResultListWithSubscript p) where++ resultSource' name i (ResultListWithSubscript xs ys) =+ ResultVectorSource $+ memoResultVectorSignal $+ memoResultVectorSummary $+ ResultVector { resultVectorName = name,+ resultVectorId = i,+ resultVectorItems = axs,+ resultVectorSubscript = ays,+ resultVectorSignal = undefined,+ resultVectorSummary = undefined }+ where+ bnds = (0, length xs - 1)+ axs = A.listArray bnds items+ ays = A.listArray bnds ys+ items =+ flip map (zip ys xs) $ \(y, x) ->+ let name' = name ++ y+ in resultSource' name' (VectorItemId y) x+ items' = map resultSourceSummary items+ +instance (Show i, Ix i, ResultProvider p) => ResultProvider (ResultArrayWithSubscript i p) where++ resultSource' name i (ResultArrayWithSubscript xs ys) =+ resultSource' name i $ ResultListWithSubscript items subscript where+ items = A.elems xs+ subscript = A.elems ys+ +#ifndef __HASTE__++instance ResultProvider p => ResultProvider (ResultVectorWithSubscript p) where++ resultSource' name i (ResultVectorWithSubscript xs ys) =+ ResultVectorSource $+ memoResultVectorSignal $+ memoResultVectorSummary $+ ResultVector { resultVectorName = name,+ resultVectorId = i,+ resultVectorItems = axs,+ resultVectorSubscript = ays,+ resultVectorSignal = undefined,+ resultVectorSummary = undefined }+ where+ bnds = (0, V.length xs - 1)+ axs = A.listArray bnds (V.toList items)+ ays = A.listArray bnds (V.toList ys)+ items =+ V.generate (V.length xs) $ \i ->+ let x = xs V.! i+ y = ys V.! i+ name' = name ++ y+ in resultSource' name' (VectorItemId y) x+ items' = V.map resultSourceSummary items++#endif++instance (Ix i, Show i, ResultComputing m) => ResultProvider (m (A.Array i Double)) where++ resultSource' name i m =+ ResultItemSource $ ResultItem $ fmap A.elems $ computeResultValue name i m++instance (Ix i, Show i, ResultComputing m) => ResultProvider (m (A.Array i Int)) where++ resultSource' name i m =+ ResultItemSource $ ResultItem $ fmap A.elems $ computeResultValue name i m++#ifndef __HASTE__++instance ResultComputing m => ResultProvider (m (V.Vector Double)) where++ resultSource' name i m =+ ResultItemSource $ ResultItem $ fmap V.toList $ computeResultValue name i m++instance ResultComputing m => ResultProvider (m (V.Vector Int)) where++ resultSource' name i m =+ ResultItemSource $ ResultItem $ fmap V.toList $ computeResultValue name i m++#endif++instance (Show si, Show sm, Show so,+ ResultItemable (ResultValue si),+ ResultItemable (ResultValue sm),+ ResultItemable (ResultValue so))+ => ResultProvider (Q.Queue si sm so a) where++ resultSource' name i m =+ queueResultSource $ ResultContainer name i m (ResultSignal $ Q.queueChanged_ m)++instance (Show sm, Show so,+ ResultItemable (ResultValue sm),+ ResultItemable (ResultValue so))+ => ResultProvider (IQ.Queue sm so a) where++ resultSource' name i m =+ infiniteQueueResultSource $ ResultContainer name i m (ResultSignal $ IQ.queueChanged_ m)++instance ResultProvider ArrivalTimer where++ resultSource' name i m =+ arrivalTimerResultSource $ ResultContainer name i m (ResultSignal $ arrivalProcessingTimeChanged_ m)++instance (Show s, ResultItemable (ResultValue s)) => ResultProvider (Server s a b) where++ resultSource' name i m =+ serverResultSource $ ResultContainer name i m (ResultSignal $ serverChanged_ m)++instance (Show s, ResultItemable (ResultValue s)) => ResultProvider (Activity s a b) where++ resultSource' name i m =+ activityResultSource $ ResultContainer name i m (ResultSignal $ activityChanged_ m)
Simulation/Aivika/Results/IO.hs view
@@ -1,476 +1,476 @@- --- | --- Module : Simulation.Aivika.Results.IO --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- The module allows printing and converting the 'Simulation' 'Results' to a 'String'. --- -module Simulation.Aivika.Results.IO - (-- * Basic Types - ResultSourcePrint, - ResultSourceShowS, - -- * Printing the Results - printResultsWithTime, - printResultsInStartTime, - printResultsInStopTime, - printResultsInIntegTimes, - printResultsInTime, - printResultsInTimes, - -- * Simulating and Printing the Results - printSimulationResultsInStartTime, - printSimulationResultsInStopTime, - printSimulationResultsInIntegTimes, - printSimulationResultsInTime, - printSimulationResultsInTimes, - -- * Showing the Results - showResultsWithTime, - showResultsInStartTime, - showResultsInStopTime, - showResultsInIntegTimes, - showResultsInTime, - showResultsInTimes, - -- * Simulating and Showing the Results - showSimulationResultsInStartTime, - showSimulationResultsInStopTime, - showSimulationResultsInIntegTimes, - showSimulationResultsInTime, - showSimulationResultsInTimes, - -- * Printing the Result Source - hPrintResultSourceIndented, - hPrintResultSource, - hPrintResultSourceInRussian, - hPrintResultSourceInEnglish, - printResultSourceIndented, - printResultSource, - printResultSourceInRussian, - printResultSourceInEnglish, - -- * Showing the Result Source - showResultSourceIndented, - showResultSource, - showResultSourceInRussian, - showResultSourceInEnglish) where - -import Control.Monad -import Control.Monad.Trans - -import qualified Data.Map as M -import qualified Data.Array as A - -import System.IO - -import Simulation.Aivika.Specs -import Simulation.Aivika.Simulation -import Simulation.Aivika.Dynamics -import Simulation.Aivika.Event -import Simulation.Aivika.Results -import Simulation.Aivika.Results.Locale - --- | This is a function that shows the simulation results within --- the 'Event' computation synchronized with the event queue. -type ResultSourceShowS = ResultSource -> Event ShowS - --- | This is a function that prints the simulation results within --- the 'Event' computation synchronized with the event queue. -type ResultSourcePrint = ResultSource -> Event () - --- | Print a localised text representation of the results by the specified source --- and with the given indent. -hPrintResultSourceIndented :: Handle - -- ^ a handle - -> Int - -- ^ an indent - -> ResultLocalisation - -- ^ a localisation - -> ResultSourcePrint -hPrintResultSourceIndented h indent loc source@(ResultItemSource (ResultItem x)) = - hPrintResultSourceIndentedLabelled h indent (resultItemName x) loc source -hPrintResultSourceIndented h indent loc source@(ResultVectorSource x) = - hPrintResultSourceIndentedLabelled h indent (resultVectorName x) loc source -hPrintResultSourceIndented h indent loc source@(ResultObjectSource x) = - hPrintResultSourceIndentedLabelled h indent (resultObjectName x) loc source -hPrintResultSourceIndented h indent loc source@(ResultSeparatorSource x) = - hPrintResultSourceIndentedLabelled h indent (resultSeparatorText x) loc source - --- | Print an indented and labelled text representation of the results by --- the specified source. -hPrintResultSourceIndentedLabelled :: Handle - -- ^ a handle - -> Int - -- ^ an indent - -> ResultName - -- ^ a label - -> ResultLocalisation - -- ^ a localisation - -> ResultSourcePrint -hPrintResultSourceIndentedLabelled h indent label loc (ResultItemSource (ResultItem x)) = - case resultValueData (resultItemToStringValue x) of - Just m -> - do a <- m - let tab = replicate indent ' ' - liftIO $ - do hPutStr h tab - hPutStr h "-- " - hPutStr h (loc $ resultItemId x) - hPutStrLn h "" - hPutStr h tab - hPutStr h label - hPutStr h " = " - hPutStrLn h a - hPutStrLn h "" - _ -> - error $ - "Expected to see a string value for variable " ++ - (resultItemName x) ++ ": hPrintResultSourceIndentedLabelled" -hPrintResultSourceIndentedLabelled h indent label loc (ResultVectorSource x) = - do let tab = replicate indent ' ' - liftIO $ - do hPutStr h tab - hPutStr h "-- " - hPutStr h (loc $ resultVectorId x) - hPutStrLn h "" - hPutStr h tab - hPutStr h label - hPutStrLn h ":" - hPutStrLn h "" - let items = A.elems (resultVectorItems x) - subscript = A.elems (resultVectorSubscript x) - forM_ (zip items subscript) $ \(i, s) -> - hPrintResultSourceIndentedLabelled h (indent + 2) (label ++ s) loc i -hPrintResultSourceIndentedLabelled h indent label loc (ResultObjectSource x) = - do let tab = replicate indent ' ' - liftIO $ - do hPutStr h tab - hPutStr h "-- " - hPutStr h (loc $ resultObjectId x) - hPutStrLn h "" - hPutStr h tab - hPutStr h label - hPutStrLn h ":" - hPutStrLn h "" - forM_ (resultObjectProperties x) $ \p -> - do let indent' = 2 + indent - tab' = " " ++ tab - label' = resultPropertyLabel p - source' = resultPropertySource p - hPrintResultSourceIndentedLabelled h indent' label' loc source' -hPrintResultSourceIndentedLabelled h indent label loc (ResultSeparatorSource x) = - do let tab = replicate indent ' ' - liftIO $ - do hPutStr h tab - hPutStr h label - hPutStrLn h "" - hPutStrLn h "" - --- | Print a localised text representation of the results by the specified source --- and with the given indent. -printResultSourceIndented :: Int - -- ^ an indent - -> ResultLocalisation - -- ^ a localisation - -> ResultSourcePrint -printResultSourceIndented = hPrintResultSourceIndented stdout - --- | Print a localised text representation of the results by the specified source. -hPrintResultSource :: Handle - -- ^ a handle - -> ResultLocalisation - -- ^ a localisation - -> ResultSourcePrint -hPrintResultSource h = hPrintResultSourceIndented h 0 - --- | Print a localised text representation of the results by the specified source. -printResultSource :: ResultLocalisation - -- ^ a localisation - -> ResultSourcePrint -printResultSource = hPrintResultSource stdout - --- | Print in Russian a text representation of the results by the specified source. -hPrintResultSourceInRussian :: Handle -> ResultSourcePrint -hPrintResultSourceInRussian h = hPrintResultSource h russianResultLocalisation - --- | Print in English a text representation of the results by the specified source. -hPrintResultSourceInEnglish :: Handle -> ResultSourcePrint -hPrintResultSourceInEnglish h = hPrintResultSource h englishResultLocalisation - --- | Print in Russian a text representation of the results by the specified source. -printResultSourceInRussian :: ResultSourcePrint -printResultSourceInRussian = hPrintResultSourceInRussian stdout - --- | Print in English a text representation of the results by the specified source. -printResultSourceInEnglish :: ResultSourcePrint -printResultSourceInEnglish = hPrintResultSourceInEnglish stdout - --- | Show a localised text representation of the results by the specified source --- and with the given indent. -showResultSourceIndented :: Int - -- ^ an indent - -> ResultLocalisation - -- ^ a localisation - -> ResultSourceShowS -showResultSourceIndented indent loc source@(ResultItemSource (ResultItem x)) = - showResultSourceIndentedLabelled indent (resultItemName x) loc source -showResultSourceIndented indent loc source@(ResultVectorSource x) = - showResultSourceIndentedLabelled indent (resultVectorName x) loc source -showResultSourceIndented indent loc source@(ResultObjectSource x) = - showResultSourceIndentedLabelled indent (resultObjectName x) loc source -showResultSourceIndented indent loc source@(ResultSeparatorSource x) = - showResultSourceIndentedLabelled indent (resultSeparatorText x) loc source - --- | Show an indented and labelled text representation of the results by the specified source. -showResultSourceIndentedLabelled :: Int - -- ^ an indent - -> String - -- ^ a label - -> ResultLocalisation - -- ^ a localisation - -> ResultSourceShowS -showResultSourceIndentedLabelled indent label loc (ResultItemSource (ResultItem x)) = - case resultValueData (resultItemToStringValue x) of - Just m -> - do a <- m - let tab = replicate indent ' ' - return $ - showString tab . - showString "-- " . - showString (loc $ resultItemId x) . - showString "\n" . - showString tab . - showString label . - showString " = " . - showString a . - showString "\n\n" - _ -> - error $ - "Expected to see a string value for variable " ++ - (resultItemName x) ++ ": showResultSourceIndentedLabelled" -showResultSourceIndentedLabelled indent label loc (ResultVectorSource x) = - do let tab = replicate indent ' ' - items = A.elems (resultVectorItems x) - subscript = A.elems (resultVectorSubscript x) - contents <- - forM (zip items subscript) $ \(i, s) -> - showResultSourceIndentedLabelled (indent + 2) (label ++ s) loc i - let showContents = foldr (.) id contents - return $ - showString tab . - showString "-- " . - showString (loc $ resultVectorId x) . - showString "\n" . - showString tab . - showString label . - showString ":\n\n" . - showContents -showResultSourceIndentedLabelled indent label loc (ResultObjectSource x) = - do let tab = replicate indent ' ' - contents <- - forM (resultObjectProperties x) $ \p -> - do let indent' = 2 + indent - tab' = " " ++ tab - label' = resultPropertyLabel p - output' = resultPropertySource p - showResultSourceIndentedLabelled indent' label' loc output' - let showContents = foldr (.) id contents - return $ - showString tab . - showString "-- " . - showString (loc $ resultObjectId x) . - showString "\n" . - showString tab . - showString label . - showString ":\n\n" . - showContents -showResultSourceIndentedLabelled indent label loc (ResultSeparatorSource x) = - do let tab = replicate indent ' ' - return $ - showString tab . - showString label . - showString "\n\n" - --- | Show a localised text representation of the results by the specified source. -showResultSource :: ResultLocalisation - -- ^ a localisation - -> ResultSourceShowS -showResultSource = showResultSourceIndented 0 - --- | Show in Russian a text representation of the results by the specified source. -showResultSourceInRussian :: ResultSourceShowS -showResultSourceInRussian = showResultSource russianResultLocalisation - --- | Show in English a text representation of the results by the specified source. -showResultSourceInEnglish :: ResultSourceShowS -showResultSourceInEnglish = showResultSource englishResultLocalisation - --- | Print the results with the information about the modeling time. -printResultsWithTime :: ResultSourcePrint -> Results -> Event () -printResultsWithTime print results = - do let x1 = textResultSource "----------" - x2 = timeResultSource - x3 = textResultSource "" - xs = resultSourceList results - print x1 - print x2 - -- print x3 - mapM_ print xs - -- print x3 - --- | Print the simulation results in start time. -printResultsInStartTime :: ResultSourcePrint -> Results -> Simulation () -printResultsInStartTime print results = - runEventInStartTime $ printResultsWithTime print results - --- | Print the simulation results in stop time. -printResultsInStopTime :: ResultSourcePrint -> Results -> Simulation () -printResultsInStopTime print results = - runEventInStopTime $ printResultsWithTime print results - --- | Print the simulation results in the integration time points. -printResultsInIntegTimes :: ResultSourcePrint -> Results -> Simulation () -printResultsInIntegTimes print results = - do let loop (m : ms) = m >> loop ms - loop [] = return () - ms <- runDynamicsInIntegTimes $ runEvent $ - printResultsWithTime print results - liftIO $ loop ms - --- | Print the simulation results in the specified time. -printResultsInTime :: Double -> ResultSourcePrint -> Results -> Simulation () -printResultsInTime t print results = - runDynamicsInTime t $ runEvent $ - printResultsWithTime print results - --- | Print the simulation results in the specified time points. -printResultsInTimes :: [Double] -> ResultSourcePrint -> Results -> Simulation () -printResultsInTimes ts print results = - do let loop (m : ms) = m >> loop ms - loop [] = return () - ms <- runDynamicsInTimes ts $ runEvent $ - printResultsWithTime print results - liftIO $ loop ms - --- | Show the results with the information about the modeling time. -showResultsWithTime :: ResultSourceShowS -> Results -> Event ShowS -showResultsWithTime f results = - do let x1 = textResultSource "----------" - x2 = timeResultSource - x3 = textResultSource "" - xs = resultSourceList results - y1 <- f x1 - y2 <- f x2 - y3 <- f x3 - ys <- forM xs f - return $ - y1 . - y2 . - -- y3 . - foldr (.) id ys - -- y3 - --- | Show the simulation results in start time. -showResultsInStartTime :: ResultSourceShowS -> Results -> Simulation ShowS -showResultsInStartTime f results = - runEventInStartTime $ showResultsWithTime f results - --- | Show the simulation results in stop time. -showResultsInStopTime :: ResultSourceShowS -> Results -> Simulation ShowS -showResultsInStopTime f results = - runEventInStopTime $ showResultsWithTime f results - --- | Show the simulation results in the integration time points. --- --- It may consume much memory, for we have to traverse all the integration --- points to create the resulting function within the 'Simulation' computation. -showResultsInIntegTimes :: ResultSourceShowS -> Results -> Simulation ShowS -showResultsInIntegTimes f results = - do let loop (m : ms) = return (.) `ap` m `ap` loop ms - loop [] = return id - ms <- runDynamicsInIntegTimes $ runEvent $ - showResultsWithTime f results - liftIO $ loop ms - --- | Show the simulation results in the specified time point. -showResultsInTime :: Double -> ResultSourceShowS -> Results -> Simulation ShowS -showResultsInTime t f results = - runDynamicsInTime t $ runEvent $ - showResultsWithTime f results - --- | Show the simulation results in the specified time points. --- --- It may consume much memory, for we have to traverse all the specified --- points to create the resulting function within the 'Simulation' computation. -showResultsInTimes :: [Double] -> ResultSourceShowS -> Results -> Simulation ShowS -showResultsInTimes ts f results = - do let loop (m : ms) = return (.) `ap` m `ap` loop ms - loop [] = return id - ms <- runDynamicsInTimes ts $ runEvent $ - showResultsWithTime f results - liftIO $ loop ms - --- | Run the simulation and then print the results in the start time. -printSimulationResultsInStartTime :: ResultSourcePrint -> Simulation Results -> Specs -> IO () -printSimulationResultsInStartTime print model specs = - flip runSimulation specs $ - model >>= printResultsInStartTime print - --- | Run the simulation and then print the results in the final time. -printSimulationResultsInStopTime :: ResultSourcePrint -> Simulation Results -> Specs -> IO () -printSimulationResultsInStopTime print model specs = - flip runSimulation specs $ - model >>= printResultsInStopTime print - --- | Run the simulation and then print the results in the integration time points. -printSimulationResultsInIntegTimes :: ResultSourcePrint -> Simulation Results -> Specs -> IO () -printSimulationResultsInIntegTimes print model specs = - flip runSimulation specs $ - model >>= printResultsInIntegTimes print - --- | Run the simulation and then print the results in the specified time point. -printSimulationResultsInTime :: Double -> ResultSourcePrint -> Simulation Results -> Specs -> IO () -printSimulationResultsInTime t print model specs = - flip runSimulation specs $ - model >>= printResultsInTime t print - --- | Run the simulation and then print the results in the specified time points. -printSimulationResultsInTimes :: [Double] -> ResultSourcePrint -> Simulation Results -> Specs -> IO () -printSimulationResultsInTimes ts print model specs = - flip runSimulation specs $ - model >>= printResultsInTimes ts print - --- | Run the simulation and then show the results in the start time. -showSimulationResultsInStartTime :: ResultSourceShowS -> Simulation Results -> Specs -> IO ShowS -showSimulationResultsInStartTime f model specs = - flip runSimulation specs $ - model >>= showResultsInStartTime f - --- | Run the simulation and then show the results in the final time. -showSimulationResultsInStopTime :: ResultSourceShowS -> Simulation Results -> Specs -> IO ShowS -showSimulationResultsInStopTime f model specs = - flip runSimulation specs $ - model >>= showResultsInStopTime f - --- | Run the simulation and then show the results in the integration time points. --- --- It may consume much memory, for we have to traverse all the integration --- points to create the resulting function within the 'IO' computation. -showSimulationResultsInIntegTimes :: ResultSourceShowS -> Simulation Results -> Specs -> IO ShowS -showSimulationResultsInIntegTimes f model specs = - flip runSimulation specs $ - model >>= showResultsInIntegTimes f - --- | Run the simulation and then show the results in the integration time point. -showSimulationResultsInTime :: Double -> ResultSourceShowS -> Simulation Results -> Specs -> IO ShowS -showSimulationResultsInTime t f model specs = - flip runSimulation specs $ - model >>= showResultsInTime t f - --- | Run the simulation and then show the results in the specified time points. --- --- It may consume much memory, for we have to traverse all the specified --- points to create the resulting function within the 'IO' computation. -showSimulationResultsInTimes :: [Double] -> ResultSourceShowS -> Simulation Results -> Specs -> IO ShowS -showSimulationResultsInTimes ts f model specs = - flip runSimulation specs $ - model >>= showResultsInTimes ts f ++-- |+-- Module : Simulation.Aivika.Results.IO+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- The module allows printing and converting the 'Simulation' 'Results' to a 'String'.+--+module Simulation.Aivika.Results.IO+ (-- * Basic Types+ ResultSourcePrint,+ ResultSourceShowS,+ -- * Printing the Results+ printResultsWithTime,+ printResultsInStartTime,+ printResultsInStopTime,+ printResultsInIntegTimes,+ printResultsInTime,+ printResultsInTimes,+ -- * Simulating and Printing the Results+ printSimulationResultsInStartTime,+ printSimulationResultsInStopTime,+ printSimulationResultsInIntegTimes,+ printSimulationResultsInTime,+ printSimulationResultsInTimes,+ -- * Showing the Results+ showResultsWithTime,+ showResultsInStartTime,+ showResultsInStopTime,+ showResultsInIntegTimes,+ showResultsInTime,+ showResultsInTimes,+ -- * Simulating and Showing the Results+ showSimulationResultsInStartTime,+ showSimulationResultsInStopTime,+ showSimulationResultsInIntegTimes,+ showSimulationResultsInTime,+ showSimulationResultsInTimes,+ -- * Printing the Result Source+ hPrintResultSourceIndented,+ hPrintResultSource,+ hPrintResultSourceInRussian,+ hPrintResultSourceInEnglish,+ printResultSourceIndented,+ printResultSource,+ printResultSourceInRussian,+ printResultSourceInEnglish,+ -- * Showing the Result Source+ showResultSourceIndented,+ showResultSource,+ showResultSourceInRussian,+ showResultSourceInEnglish) where++import Control.Monad+import Control.Monad.Trans++import qualified Data.Map as M+import qualified Data.Array as A++import System.IO++import Simulation.Aivika.Specs+import Simulation.Aivika.Simulation+import Simulation.Aivika.Dynamics+import Simulation.Aivika.Event+import Simulation.Aivika.Results+import Simulation.Aivika.Results.Locale++-- | This is a function that shows the simulation results within+-- the 'Event' computation synchronized with the event queue.+type ResultSourceShowS = ResultSource -> Event ShowS++-- | This is a function that prints the simulation results within+-- the 'Event' computation synchronized with the event queue.+type ResultSourcePrint = ResultSource -> Event ()++-- | Print a localised text representation of the results by the specified source+-- and with the given indent.+hPrintResultSourceIndented :: Handle+ -- ^ a handle+ -> Int+ -- ^ an indent+ -> ResultLocalisation+ -- ^ a localisation+ -> ResultSourcePrint+hPrintResultSourceIndented h indent loc source@(ResultItemSource (ResultItem x)) =+ hPrintResultSourceIndentedLabelled h indent (resultItemName x) loc source+hPrintResultSourceIndented h indent loc source@(ResultVectorSource x) =+ hPrintResultSourceIndentedLabelled h indent (resultVectorName x) loc source+hPrintResultSourceIndented h indent loc source@(ResultObjectSource x) =+ hPrintResultSourceIndentedLabelled h indent (resultObjectName x) loc source+hPrintResultSourceIndented h indent loc source@(ResultSeparatorSource x) =+ hPrintResultSourceIndentedLabelled h indent (resultSeparatorText x) loc source++-- | Print an indented and labelled text representation of the results by+-- the specified source.+hPrintResultSourceIndentedLabelled :: Handle+ -- ^ a handle+ -> Int+ -- ^ an indent+ -> ResultName+ -- ^ a label+ -> ResultLocalisation+ -- ^ a localisation+ -> ResultSourcePrint+hPrintResultSourceIndentedLabelled h indent label loc (ResultItemSource (ResultItem x)) =+ case resultValueData (resultItemToStringValue x) of+ Just m ->+ do a <- m+ let tab = replicate indent ' '+ liftIO $+ do hPutStr h tab+ hPutStr h "-- "+ hPutStr h (loc $ resultItemId x)+ hPutStrLn h ""+ hPutStr h tab+ hPutStr h label+ hPutStr h " = "+ hPutStrLn h a+ hPutStrLn h ""+ _ ->+ error $+ "Expected to see a string value for variable " +++ (resultItemName x) ++ ": hPrintResultSourceIndentedLabelled"+hPrintResultSourceIndentedLabelled h indent label loc (ResultVectorSource x) =+ do let tab = replicate indent ' '+ liftIO $+ do hPutStr h tab+ hPutStr h "-- "+ hPutStr h (loc $ resultVectorId x)+ hPutStrLn h ""+ hPutStr h tab+ hPutStr h label+ hPutStrLn h ":"+ hPutStrLn h ""+ let items = A.elems (resultVectorItems x)+ subscript = A.elems (resultVectorSubscript x)+ forM_ (zip items subscript) $ \(i, s) ->+ hPrintResultSourceIndentedLabelled h (indent + 2) (label ++ s) loc i+hPrintResultSourceIndentedLabelled h indent label loc (ResultObjectSource x) =+ do let tab = replicate indent ' '+ liftIO $+ do hPutStr h tab+ hPutStr h "-- "+ hPutStr h (loc $ resultObjectId x)+ hPutStrLn h ""+ hPutStr h tab+ hPutStr h label+ hPutStrLn h ":"+ hPutStrLn h ""+ forM_ (resultObjectProperties x) $ \p ->+ do let indent' = 2 + indent+ tab' = " " ++ tab+ label' = resultPropertyLabel p+ source' = resultPropertySource p+ hPrintResultSourceIndentedLabelled h indent' label' loc source'+hPrintResultSourceIndentedLabelled h indent label loc (ResultSeparatorSource x) =+ do let tab = replicate indent ' '+ liftIO $+ do hPutStr h tab+ hPutStr h label+ hPutStrLn h ""+ hPutStrLn h ""++-- | Print a localised text representation of the results by the specified source+-- and with the given indent.+printResultSourceIndented :: Int+ -- ^ an indent+ -> ResultLocalisation+ -- ^ a localisation+ -> ResultSourcePrint+printResultSourceIndented = hPrintResultSourceIndented stdout++-- | Print a localised text representation of the results by the specified source.+hPrintResultSource :: Handle+ -- ^ a handle+ -> ResultLocalisation+ -- ^ a localisation+ -> ResultSourcePrint+hPrintResultSource h = hPrintResultSourceIndented h 0++-- | Print a localised text representation of the results by the specified source.+printResultSource :: ResultLocalisation+ -- ^ a localisation+ -> ResultSourcePrint+printResultSource = hPrintResultSource stdout++-- | Print in Russian a text representation of the results by the specified source.+hPrintResultSourceInRussian :: Handle -> ResultSourcePrint+hPrintResultSourceInRussian h = hPrintResultSource h russianResultLocalisation++-- | Print in English a text representation of the results by the specified source.+hPrintResultSourceInEnglish :: Handle -> ResultSourcePrint+hPrintResultSourceInEnglish h = hPrintResultSource h englishResultLocalisation++-- | Print in Russian a text representation of the results by the specified source.+printResultSourceInRussian :: ResultSourcePrint+printResultSourceInRussian = hPrintResultSourceInRussian stdout++-- | Print in English a text representation of the results by the specified source.+printResultSourceInEnglish :: ResultSourcePrint+printResultSourceInEnglish = hPrintResultSourceInEnglish stdout++-- | Show a localised text representation of the results by the specified source+-- and with the given indent.+showResultSourceIndented :: Int+ -- ^ an indent+ -> ResultLocalisation+ -- ^ a localisation+ -> ResultSourceShowS+showResultSourceIndented indent loc source@(ResultItemSource (ResultItem x)) =+ showResultSourceIndentedLabelled indent (resultItemName x) loc source+showResultSourceIndented indent loc source@(ResultVectorSource x) =+ showResultSourceIndentedLabelled indent (resultVectorName x) loc source+showResultSourceIndented indent loc source@(ResultObjectSource x) =+ showResultSourceIndentedLabelled indent (resultObjectName x) loc source+showResultSourceIndented indent loc source@(ResultSeparatorSource x) =+ showResultSourceIndentedLabelled indent (resultSeparatorText x) loc source++-- | Show an indented and labelled text representation of the results by the specified source.+showResultSourceIndentedLabelled :: Int+ -- ^ an indent+ -> String+ -- ^ a label+ -> ResultLocalisation+ -- ^ a localisation+ -> ResultSourceShowS+showResultSourceIndentedLabelled indent label loc (ResultItemSource (ResultItem x)) =+ case resultValueData (resultItemToStringValue x) of+ Just m ->+ do a <- m+ let tab = replicate indent ' '+ return $+ showString tab .+ showString "-- " .+ showString (loc $ resultItemId x) .+ showString "\n" .+ showString tab .+ showString label .+ showString " = " .+ showString a .+ showString "\n\n"+ _ ->+ error $+ "Expected to see a string value for variable " +++ (resultItemName x) ++ ": showResultSourceIndentedLabelled"+showResultSourceIndentedLabelled indent label loc (ResultVectorSource x) =+ do let tab = replicate indent ' '+ items = A.elems (resultVectorItems x)+ subscript = A.elems (resultVectorSubscript x)+ contents <-+ forM (zip items subscript) $ \(i, s) ->+ showResultSourceIndentedLabelled (indent + 2) (label ++ s) loc i+ let showContents = foldr (.) id contents+ return $+ showString tab .+ showString "-- " .+ showString (loc $ resultVectorId x) .+ showString "\n" .+ showString tab .+ showString label .+ showString ":\n\n" .+ showContents+showResultSourceIndentedLabelled indent label loc (ResultObjectSource x) =+ do let tab = replicate indent ' '+ contents <-+ forM (resultObjectProperties x) $ \p ->+ do let indent' = 2 + indent+ tab' = " " ++ tab+ label' = resultPropertyLabel p+ output' = resultPropertySource p+ showResultSourceIndentedLabelled indent' label' loc output'+ let showContents = foldr (.) id contents+ return $+ showString tab .+ showString "-- " .+ showString (loc $ resultObjectId x) .+ showString "\n" .+ showString tab .+ showString label .+ showString ":\n\n" .+ showContents+showResultSourceIndentedLabelled indent label loc (ResultSeparatorSource x) =+ do let tab = replicate indent ' '+ return $+ showString tab .+ showString label .+ showString "\n\n"++-- | Show a localised text representation of the results by the specified source.+showResultSource :: ResultLocalisation+ -- ^ a localisation+ -> ResultSourceShowS+showResultSource = showResultSourceIndented 0++-- | Show in Russian a text representation of the results by the specified source.+showResultSourceInRussian :: ResultSourceShowS+showResultSourceInRussian = showResultSource russianResultLocalisation++-- | Show in English a text representation of the results by the specified source.+showResultSourceInEnglish :: ResultSourceShowS+showResultSourceInEnglish = showResultSource englishResultLocalisation++-- | Print the results with the information about the modeling time.+printResultsWithTime :: ResultSourcePrint -> Results -> Event ()+printResultsWithTime print results =+ do let x1 = textResultSource "----------"+ x2 = timeResultSource+ x3 = textResultSource ""+ xs = resultSourceList results+ print x1+ print x2+ -- print x3+ mapM_ print xs+ -- print x3++-- | Print the simulation results in start time.+printResultsInStartTime :: ResultSourcePrint -> Results -> Simulation ()+printResultsInStartTime print results =+ runEventInStartTime $ printResultsWithTime print results++-- | Print the simulation results in stop time.+printResultsInStopTime :: ResultSourcePrint -> Results -> Simulation ()+printResultsInStopTime print results =+ runEventInStopTime $ printResultsWithTime print results++-- | Print the simulation results in the integration time points.+printResultsInIntegTimes :: ResultSourcePrint -> Results -> Simulation ()+printResultsInIntegTimes print results =+ do let loop (m : ms) = m >> loop ms+ loop [] = return ()+ ms <- runDynamicsInIntegTimes $ runEvent $+ printResultsWithTime print results+ liftIO $ loop ms++-- | Print the simulation results in the specified time.+printResultsInTime :: Double -> ResultSourcePrint -> Results -> Simulation ()+printResultsInTime t print results =+ runDynamicsInTime t $ runEvent $+ printResultsWithTime print results++-- | Print the simulation results in the specified time points.+printResultsInTimes :: [Double] -> ResultSourcePrint -> Results -> Simulation ()+printResultsInTimes ts print results =+ do let loop (m : ms) = m >> loop ms+ loop [] = return ()+ ms <- runDynamicsInTimes ts $ runEvent $+ printResultsWithTime print results+ liftIO $ loop ms++-- | Show the results with the information about the modeling time.+showResultsWithTime :: ResultSourceShowS -> Results -> Event ShowS+showResultsWithTime f results =+ do let x1 = textResultSource "----------"+ x2 = timeResultSource+ x3 = textResultSource ""+ xs = resultSourceList results+ y1 <- f x1+ y2 <- f x2+ y3 <- f x3+ ys <- forM xs f+ return $+ y1 .+ y2 .+ -- y3 .+ foldr (.) id ys+ -- y3++-- | Show the simulation results in start time.+showResultsInStartTime :: ResultSourceShowS -> Results -> Simulation ShowS+showResultsInStartTime f results =+ runEventInStartTime $ showResultsWithTime f results++-- | Show the simulation results in stop time.+showResultsInStopTime :: ResultSourceShowS -> Results -> Simulation ShowS+showResultsInStopTime f results =+ runEventInStopTime $ showResultsWithTime f results++-- | Show the simulation results in the integration time points.+--+-- It may consume much memory, for we have to traverse all the integration+-- points to create the resulting function within the 'Simulation' computation.+showResultsInIntegTimes :: ResultSourceShowS -> Results -> Simulation ShowS+showResultsInIntegTimes f results =+ do let loop (m : ms) = return (.) `ap` m `ap` loop ms+ loop [] = return id+ ms <- runDynamicsInIntegTimes $ runEvent $+ showResultsWithTime f results+ liftIO $ loop ms++-- | Show the simulation results in the specified time point.+showResultsInTime :: Double -> ResultSourceShowS -> Results -> Simulation ShowS+showResultsInTime t f results =+ runDynamicsInTime t $ runEvent $+ showResultsWithTime f results++-- | Show the simulation results in the specified time points.+--+-- It may consume much memory, for we have to traverse all the specified+-- points to create the resulting function within the 'Simulation' computation.+showResultsInTimes :: [Double] -> ResultSourceShowS -> Results -> Simulation ShowS+showResultsInTimes ts f results =+ do let loop (m : ms) = return (.) `ap` m `ap` loop ms+ loop [] = return id+ ms <- runDynamicsInTimes ts $ runEvent $+ showResultsWithTime f results+ liftIO $ loop ms++-- | Run the simulation and then print the results in the start time.+printSimulationResultsInStartTime :: ResultSourcePrint -> Simulation Results -> Specs -> IO ()+printSimulationResultsInStartTime print model specs =+ flip runSimulation specs $+ model >>= printResultsInStartTime print++-- | Run the simulation and then print the results in the final time.+printSimulationResultsInStopTime :: ResultSourcePrint -> Simulation Results -> Specs -> IO ()+printSimulationResultsInStopTime print model specs =+ flip runSimulation specs $+ model >>= printResultsInStopTime print++-- | Run the simulation and then print the results in the integration time points.+printSimulationResultsInIntegTimes :: ResultSourcePrint -> Simulation Results -> Specs -> IO ()+printSimulationResultsInIntegTimes print model specs =+ flip runSimulation specs $+ model >>= printResultsInIntegTimes print++-- | Run the simulation and then print the results in the specified time point.+printSimulationResultsInTime :: Double -> ResultSourcePrint -> Simulation Results -> Specs -> IO ()+printSimulationResultsInTime t print model specs =+ flip runSimulation specs $+ model >>= printResultsInTime t print++-- | Run the simulation and then print the results in the specified time points.+printSimulationResultsInTimes :: [Double] -> ResultSourcePrint -> Simulation Results -> Specs -> IO ()+printSimulationResultsInTimes ts print model specs =+ flip runSimulation specs $+ model >>= printResultsInTimes ts print++-- | Run the simulation and then show the results in the start time.+showSimulationResultsInStartTime :: ResultSourceShowS -> Simulation Results -> Specs -> IO ShowS+showSimulationResultsInStartTime f model specs =+ flip runSimulation specs $+ model >>= showResultsInStartTime f++-- | Run the simulation and then show the results in the final time.+showSimulationResultsInStopTime :: ResultSourceShowS -> Simulation Results -> Specs -> IO ShowS+showSimulationResultsInStopTime f model specs =+ flip runSimulation specs $+ model >>= showResultsInStopTime f++-- | Run the simulation and then show the results in the integration time points.+--+-- It may consume much memory, for we have to traverse all the integration+-- points to create the resulting function within the 'IO' computation.+showSimulationResultsInIntegTimes :: ResultSourceShowS -> Simulation Results -> Specs -> IO ShowS+showSimulationResultsInIntegTimes f model specs =+ flip runSimulation specs $+ model >>= showResultsInIntegTimes f++-- | Run the simulation and then show the results in the integration time point.+showSimulationResultsInTime :: Double -> ResultSourceShowS -> Simulation Results -> Specs -> IO ShowS+showSimulationResultsInTime t f model specs =+ flip runSimulation specs $+ model >>= showResultsInTime t f++-- | Run the simulation and then show the results in the specified time points.+--+-- It may consume much memory, for we have to traverse all the specified+-- points to create the resulting function within the 'IO' computation.+showSimulationResultsInTimes :: [Double] -> ResultSourceShowS -> Simulation Results -> Specs -> IO ShowS+showSimulationResultsInTimes ts f model specs =+ flip runSimulation specs $+ model >>= showResultsInTimes ts f
Simulation/Aivika/Results/Locale.hs view
@@ -1,340 +1,377 @@- --- | --- Module : Simulation.Aivika.Results.Locale --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- The module defines locales for outputting and printing the simulation results. --- -module Simulation.Aivika.Results.Locale - (-- * Basic Types - ResultLocale, - ResultLocalisation, - ResultDescription, - -- * Locale Codes - russianResultLocale, - englishResultLocale, - -- * Localisations - lookupResultLocalisation, - russianResultLocalisation, - englishResultLocalisation, - -- * Unique Identifiers - ResultId(..)) where - -import qualified Data.Map as M - -import Simulation.Aivika.Dynamics -import Simulation.Aivika.Statistics -import Simulation.Aivika.Statistics.Accumulator -import qualified Simulation.Aivika.Queue as Q -import qualified Simulation.Aivika.Queue.Infinite as IQ -import Simulation.Aivika.Arrival -import Simulation.Aivika.Server - --- | A locale to output the simulation results. --- --- Examples are: @\"ru\", @\"en\" etc. -type ResultLocale = String - --- | It localises the description of simulation results. -type ResultLocalisation = ResultId -> ResultDescription - --- | A description used for describing the results when generating output. -type ResultDescription = String - --- | The result entity identifier. -data ResultId = TimeId - -- ^ A 'time' computation. - | VectorId - -- ^ Describes a vector. - | VectorItemId String - -- ^ Describes a vector item with the specified subscript. - | SamplingStatsId - -- ^ A 'SamplingStats' value. - | SamplingStatsCountId - -- ^ Property 'samplingStatsCount'. - | SamplingStatsMinId - -- ^ Property 'samplingStatsMin'. - | SamplingStatsMaxId - -- ^ Property 'samplingStatsMax'. - | SamplingStatsMeanId - -- ^ Property 'samplingStatsMean'. - | SamplingStatsMean2Id - -- ^ Property 'samplingStatsMean2'. - | SamplingStatsVarianceId - -- ^ Property 'samplingStatsVariance'. - | SamplingStatsDeviationId - -- ^ Property 'samplingStatsDeviation'. - | TimingStatsId - -- ^ A 'TimingStats' value. - | TimingStatsCountId - -- ^ Property 'timingStatsCount'. - | TimingStatsMinId - -- ^ Property 'timingStatsMin'. - | TimingStatsMaxId - -- ^ Property 'timingStatsMax'. - | TimingStatsMeanId - -- ^ Property 'timingStatsMean'. - | TimingStatsVarianceId - -- ^ Property 'timingStatsVariance'. - | TimingStatsDeviationId - -- ^ Property 'timingStatsDeviation'. - | TimingStatsMinTimeId - -- ^ Property 'timingStatsMinTime'. - | TimingStatsMaxTimeId - -- ^ Property 'timingStatsMaxTime'. - | TimingStatsStartTimeId - -- ^ Property 'timingStatsStartTime'. - | TimingStatsLastTimeId - -- ^ Property 'timingStatsLastTime'. - | TimingStatsSumId - -- ^ Property 'timingStatsSum'. - | TimingStatsSum2Id - -- ^ Property 'timingStatsSum2'. - | FiniteQueueId - -- ^ A finite 'Q.Queue'. - | InfiniteQueueId - -- ^ An infinite 'IQ.Queue'. - | EnqueueStrategyId - -- ^ Property 'Q.enqueueStrategy'. - | EnqueueStoringStrategyId - -- ^ Property 'Q.enqueueStoringStrategy'. - | DequeueStrategyId - -- ^ Property 'Q.dequeueStrategy'. - | QueueNullId - -- ^ Property 'Q.queueNull'. - | QueueFullId - -- ^ Property 'Q.queueFull'. - | QueueMaxCountId - -- ^ Property 'Q.queueMaxCount'. - | QueueCountId - -- ^ Property 'Q.queueCount'. - | QueueCountStatsId - -- ^ Property 'Q.queueCountStats'. - | EnqueueCountId - -- ^ Property 'Q.enqueueCount'. - | EnqueueLostCountId - -- ^ Property 'Q.enqueueLostCount'. - | EnqueueStoreCountId - -- ^ Property 'Q.enqueueStoreCount'. - | DequeueCountId - -- ^ Property 'Q.dequeueCount'. - | DequeueExtractCountId - -- ^ Property 'Q.dequeueExtractCount'. - | QueueLoadFactorId - -- ^ Property 'Q.queueLoadFactor'. - | EnqueueRateId - -- ^ Property 'Q.enqueueRate'. - | EnqueueStoreRateId - -- ^ Property 'Q.enqueueStoreRate'. - | DequeueRateId - -- ^ Property 'Q.dequeueRate'. - | DequeueExtractRateId - -- ^ Property 'Q.dequeueExtractRate'. - | QueueWaitTimeId - -- ^ Property 'Q.queueWaitTime'. - | QueueTotalWaitTimeId - -- ^ Property 'Q.queueTotalWaitTime'. - | EnqueueWaitTimeId - -- ^ Property 'Q.enqueueWaitTime'. - | DequeueWaitTimeId - -- ^ Property 'Q.dequeueWaitTime'. - | QueueRateId - -- ^ Property 'Q.queueRate'. - | ArrivalTimerId - -- ^ An 'ArrivalTimer'. - | ArrivalProcessingTimeId - -- ^ Property 'arrivalProcessingTime'. - | ServerId - -- ^ Represents a 'Server'. - | ServerInitStateId - -- ^ Property 'serverInitState'. - | ServerStateId - -- ^ Property 'serverState'. - | ServerTotalInputWaitTimeId - -- ^ Property 'serverTotalInputWaitTime'. - | ServerTotalProcessingTimeId - -- ^ Property 'serverTotalProcessingTime'. - | ServerTotalOutputWaitTimeId - -- ^ Property 'serverTotalOutputWaitTime'. - | ServerInputWaitTimeId - -- ^ Property 'serverInputWaitTime'. - | ServerProcessingTimeId - -- ^ Property 'serverProcessingTime'. - | ServerOutputWaitTimeId - -- ^ Property 'serverOutputWaitTime'. - | ServerInputWaitFactorId - -- ^ Property 'serverInputWaitFactor'. - | ServerProcessingFactorId - -- ^ Property 'serverProcessingFactor'. - | ServerOutputWaitFactorId - -- ^ Property 'serverOutputWaitFactor'. - | UserDefinedResultId ResultDescription - -- ^ An user defined description. - | LocalisedResultId (M.Map ResultLocale ResultDescription) - -- ^ A localised property or object name. - deriving (Eq, Ord, Show) - --- | The Russian locale. -russianResultLocale :: ResultLocale -russianResultLocale = "ru" - --- | The English locale. -englishResultLocale :: ResultLocale -englishResultLocale = "en" - --- | The Russian localisation of the simulation results. -russianResultLocalisation :: ResultLocalisation -russianResultLocalisation TimeId = "модельное время" -russianResultLocalisation VectorId = "вектор" -russianResultLocalisation (VectorItemId x) = "элемент с индексом " ++ x -russianResultLocalisation SamplingStatsId = "сводная статистика" -russianResultLocalisation SamplingStatsCountId = "количество" -russianResultLocalisation SamplingStatsMinId = "минимальное значение" -russianResultLocalisation SamplingStatsMaxId = "максимальное значение" -russianResultLocalisation SamplingStatsMeanId = "среднее значение" -russianResultLocalisation SamplingStatsMean2Id = "среднее квадратов" -russianResultLocalisation SamplingStatsVarianceId = "дисперсия" -russianResultLocalisation SamplingStatsDeviationId = "среднеквадратическое отклонение" -russianResultLocalisation TimingStatsId = "временная статистика" -russianResultLocalisation TimingStatsCountId = "количество" -russianResultLocalisation TimingStatsMinId = "минимальное значение" -russianResultLocalisation TimingStatsMaxId = "максимальное значение" -russianResultLocalisation TimingStatsMeanId = "среднее значение" -russianResultLocalisation TimingStatsVarianceId = "дисперсия" -russianResultLocalisation TimingStatsDeviationId = "среднеквадратическое отклонение" -russianResultLocalisation TimingStatsMinTimeId = "время достижения минимума" -russianResultLocalisation TimingStatsMaxTimeId = "время достижения максимума" -russianResultLocalisation TimingStatsStartTimeId = "начальное время сбора статистики" -russianResultLocalisation TimingStatsLastTimeId = "конечное время сбора статистики" -russianResultLocalisation TimingStatsSumId = "сумма" -russianResultLocalisation TimingStatsSum2Id = "сумма квадратов" -russianResultLocalisation FiniteQueueId = "конечная очередь" -russianResultLocalisation InfiniteQueueId = "бесконечная очередь" -russianResultLocalisation EnqueueStrategyId = "стратегия добавления элементов" -russianResultLocalisation EnqueueStoringStrategyId = "стратегия хранения элементов" -russianResultLocalisation DequeueStrategyId = "стратегия извлечения элементов" -russianResultLocalisation QueueNullId = "очередь пуста?" -russianResultLocalisation QueueFullId = "очередь заполнена?" -russianResultLocalisation QueueMaxCountId = "емкость очереди" -russianResultLocalisation QueueCountId = "текущий размер очереди" -russianResultLocalisation QueueCountStatsId = "статистика по размеру очереди" -russianResultLocalisation EnqueueCountId = "общее количество попыток добавить элементы" -russianResultLocalisation EnqueueLostCountId = "общее количество неудачных попыток добавить элементы" -russianResultLocalisation EnqueueStoreCountId = "общее количество сохраненных элементов" -russianResultLocalisation DequeueCountId = "общее количество запросов на извлечение элементов" -russianResultLocalisation DequeueExtractCountId = "общее количество извлеченных элементов" -russianResultLocalisation QueueLoadFactorId = "коэфф. загрузки (размер, поделенный на емкость)" -russianResultLocalisation EnqueueRateId = "количество попыток добавить на ед. времени" -russianResultLocalisation EnqueueStoreRateId = "количество сохраненных на ед. времени" -russianResultLocalisation DequeueRateId = "количество запросов на извлечение в ед. времени" -russianResultLocalisation DequeueExtractRateId = "количество извлеченных на ед. времени" -russianResultLocalisation QueueWaitTimeId = "время ожидания (сохранили -> извлекли)" -russianResultLocalisation QueueTotalWaitTimeId = "общее время ожидания (попытались добавить -> извлекли)" -russianResultLocalisation EnqueueWaitTimeId = "время ожидания добавления (попытались добавить -> сохранили)" -russianResultLocalisation DequeueWaitTimeId = "время ожидания извлечения (запросили извлечь -> извлекли)" -russianResultLocalisation QueueRateId = "усредненная скорость (как средняя длина очереди на среднее время ожидания)" -russianResultLocalisation ArrivalTimerId = "как долго обрабатываются заявки?" -russianResultLocalisation ArrivalProcessingTimeId = "время обработки заявки" -russianResultLocalisation ServerId = "сервер" -russianResultLocalisation ServerInitStateId = "начальное состояние" -russianResultLocalisation ServerStateId = "текущее состояние" -russianResultLocalisation ServerTotalInputWaitTimeId = "общее время блокировки в ожидании ввода" -russianResultLocalisation ServerTotalProcessingTimeId = "общее время, потраченное на саму обработку заданий" -russianResultLocalisation ServerTotalOutputWaitTimeId = "общее время блокировки при попытке доставить вывод" -russianResultLocalisation ServerInputWaitTimeId = "время блокировки в ожидании ввода" -russianResultLocalisation ServerProcessingTimeId = "время, потраченное на саму обработку заданий" -russianResultLocalisation ServerOutputWaitTimeId = "время блокировки при попытке доставить вывод" -russianResultLocalisation ServerInputWaitFactorId = "относительное время блокировки в ожидании ввода (от 0 до 1)" -russianResultLocalisation ServerProcessingFactorId = "относительное время, потраченное на саму обработку заданий (от 0 до 1)" -russianResultLocalisation ServerOutputWaitFactorId = "относительное время блокировки при попытке доставить вывод (от 0 до 1)" -russianResultLocalisation (UserDefinedResultId m) = m -russianResultLocalisation x@(LocalisedResultId m) = - lookupResultLocalisation russianResultLocale x - --- | The English localisation of the simulation results. -englishResultLocalisation :: ResultLocalisation -englishResultLocalisation TimeId = "simulation time" -englishResultLocalisation VectorId = "vector" -englishResultLocalisation (VectorItemId x) = "item #" ++ x -englishResultLocalisation SamplingStatsId = "statistics summary" -englishResultLocalisation SamplingStatsCountId = "count" -englishResultLocalisation SamplingStatsMinId = "minimum" -englishResultLocalisation SamplingStatsMaxId = "maximum" -englishResultLocalisation SamplingStatsMeanId = "mean" -englishResultLocalisation SamplingStatsMean2Id = "mean square" -englishResultLocalisation SamplingStatsVarianceId = "variance" -englishResultLocalisation SamplingStatsDeviationId = "deviation" -englishResultLocalisation TimingStatsId = "timing statistics" -englishResultLocalisation TimingStatsCountId = "count" -englishResultLocalisation TimingStatsMinId = "minimum" -englishResultLocalisation TimingStatsMaxId = "maximum" -englishResultLocalisation TimingStatsMeanId = "mean" -englishResultLocalisation TimingStatsVarianceId = "variance" -englishResultLocalisation TimingStatsDeviationId = "deviation" -englishResultLocalisation TimingStatsMinTimeId = "the time of minimum" -englishResultLocalisation TimingStatsMaxTimeId = "the time of maximum" -englishResultLocalisation TimingStatsStartTimeId = "the start time" -englishResultLocalisation TimingStatsLastTimeId = "the last time" -englishResultLocalisation TimingStatsSumId = "sum" -englishResultLocalisation TimingStatsSum2Id = "sum square" -englishResultLocalisation FiniteQueueId = "the finite queue" -englishResultLocalisation InfiniteQueueId = "the infinite queue" -englishResultLocalisation EnqueueStrategyId = "the enqueueing strategy" -englishResultLocalisation EnqueueStoringStrategyId = "the storing strategy" -englishResultLocalisation DequeueStrategyId = "the dequeueing strategy" -englishResultLocalisation QueueNullId = "is the queue empty?" -englishResultLocalisation QueueFullId = "is the queue full?" -englishResultLocalisation QueueMaxCountId = "the queue capacity" -englishResultLocalisation QueueCountId = "the current queue size" -englishResultLocalisation QueueCountStatsId = "the queue size statistics" -englishResultLocalisation EnqueueCountId = "a total number of attempts to enqueue the items" -englishResultLocalisation EnqueueLostCountId = "a total number of the lost items when trying to enqueue" -englishResultLocalisation EnqueueStoreCountId = "a total number of the stored items" -englishResultLocalisation DequeueCountId = "a total number of requests for dequeueing" -englishResultLocalisation DequeueExtractCountId = "a total number of the dequeued items" -englishResultLocalisation QueueLoadFactorId = "the queue load (its size divided by its capacity)" -englishResultLocalisation EnqueueRateId = "how many attempts to enqueue per time?" -englishResultLocalisation EnqueueStoreRateId = "how many items were stored per time?" -englishResultLocalisation DequeueRateId = "how many requests for dequeueing per time?" -englishResultLocalisation DequeueExtractRateId = "how many items were dequeued per time?" -englishResultLocalisation QueueWaitTimeId = "the wait time (stored -> dequeued)" -englishResultLocalisation QueueTotalWaitTimeId = "the total wait time (tried to enqueue -> dequeued)" -englishResultLocalisation EnqueueWaitTimeId = "the enqueue wait time (tried to enqueue -> stored)" -englishResultLocalisation DequeueWaitTimeId = "the dequeue wait time (requested for dequeueing -> dequeued)" -englishResultLocalisation QueueRateId = "the average queue rate (= queue size / wait time)" -englishResultLocalisation ArrivalTimerId = "how long the arrivals are processed?" -englishResultLocalisation ArrivalProcessingTimeId = "the processing time of arrivals" -englishResultLocalisation ServerId = "the server" -englishResultLocalisation ServerInitStateId = "the initial state" -englishResultLocalisation ServerStateId = "the current state" -englishResultLocalisation ServerTotalInputWaitTimeId = "the total time spent while waiting for input" -englishResultLocalisation ServerTotalProcessingTimeId = "the total time spent on actual processing the tasks" -englishResultLocalisation ServerTotalOutputWaitTimeId = "the total time spent on delivering the output" -englishResultLocalisation ServerInputWaitTimeId = "the time spent while waiting for input" -englishResultLocalisation ServerProcessingTimeId = "the time spent on processing the tasks" -englishResultLocalisation ServerOutputWaitTimeId = "the time spent on delivering the output" -englishResultLocalisation ServerInputWaitFactorId = "the relative time spent while waiting for input (from 0 to 1)" -englishResultLocalisation ServerProcessingFactorId = "the relative time spent on processing the tasks (from 0 to 1)" -englishResultLocalisation ServerOutputWaitFactorId = "the relative time spent on delivering the output (from 0 to 1)" -englishResultLocalisation (UserDefinedResultId m) = m -englishResultLocalisation x@(LocalisedResultId m) = - lookupResultLocalisation englishResultLocale x - --- | Lookup a localisation by the specified locale. -lookupResultLocalisation :: ResultLocale -> ResultLocalisation -lookupResultLocalisation loc (UserDefinedResultId m) = m -lookupResultLocalisation loc (LocalisedResultId m) = - case M.lookup loc m of - Just x -> x - Nothing -> - case M.lookup russianResultLocale m of - Just x -> x - Nothing -> - case M.lookup englishResultLocale m of - Just x -> x - Nothing -> "" -lookupResultLocalisation loc resultId = russianResultLocalisation resultId ++-- |+-- Module : Simulation.Aivika.Results.Locale+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- The module defines locales for outputting and printing the simulation results.+--+module Simulation.Aivika.Results.Locale+ (-- * Basic Types+ ResultLocale,+ ResultLocalisation,+ ResultDescription,+ -- * Locale Codes+ russianResultLocale,+ englishResultLocale,+ -- * Localisations+ lookupResultLocalisation,+ russianResultLocalisation,+ englishResultLocalisation,+ -- * Unique Identifiers+ ResultId(..)) where++import qualified Data.Map as M++import Simulation.Aivika.Dynamics+import Simulation.Aivika.Statistics+import Simulation.Aivika.Statistics.Accumulator+import qualified Simulation.Aivika.Queue as Q+import qualified Simulation.Aivika.Queue.Infinite as IQ+import Simulation.Aivika.Arrival+import Simulation.Aivika.Server+import Simulation.Aivika.Activity++-- | A locale to output the simulation results.+--+-- Examples are: @\"ru\", @\"en\" etc.+type ResultLocale = String++-- | It localises the description of simulation results.+type ResultLocalisation = ResultId -> ResultDescription++-- | A description used for describing the results when generating output.+type ResultDescription = String++-- | The result entity identifier.+data ResultId = TimeId+ -- ^ A 'time' computation.+ | VectorId+ -- ^ Describes a vector.+ | VectorItemId String+ -- ^ Describes a vector item with the specified subscript.+ | SamplingStatsId+ -- ^ A 'SamplingStats' value.+ | SamplingStatsCountId+ -- ^ Property 'samplingStatsCount'.+ | SamplingStatsMinId+ -- ^ Property 'samplingStatsMin'.+ | SamplingStatsMaxId+ -- ^ Property 'samplingStatsMax'.+ | SamplingStatsMeanId+ -- ^ Property 'samplingStatsMean'.+ | SamplingStatsMean2Id+ -- ^ Property 'samplingStatsMean2'.+ | SamplingStatsVarianceId+ -- ^ Property 'samplingStatsVariance'.+ | SamplingStatsDeviationId+ -- ^ Property 'samplingStatsDeviation'.+ | TimingStatsId+ -- ^ A 'TimingStats' value.+ | TimingStatsCountId+ -- ^ Property 'timingStatsCount'.+ | TimingStatsMinId+ -- ^ Property 'timingStatsMin'.+ | TimingStatsMaxId+ -- ^ Property 'timingStatsMax'.+ | TimingStatsMeanId+ -- ^ Property 'timingStatsMean'.+ | TimingStatsVarianceId+ -- ^ Property 'timingStatsVariance'.+ | TimingStatsDeviationId+ -- ^ Property 'timingStatsDeviation'.+ | TimingStatsMinTimeId+ -- ^ Property 'timingStatsMinTime'.+ | TimingStatsMaxTimeId+ -- ^ Property 'timingStatsMaxTime'.+ | TimingStatsStartTimeId+ -- ^ Property 'timingStatsStartTime'.+ | TimingStatsLastTimeId+ -- ^ Property 'timingStatsLastTime'.+ | TimingStatsSumId+ -- ^ Property 'timingStatsSum'.+ | TimingStatsSum2Id+ -- ^ Property 'timingStatsSum2'.+ | FiniteQueueId+ -- ^ A finite 'Q.Queue'.+ | InfiniteQueueId+ -- ^ An infinite 'IQ.Queue'.+ | EnqueueStrategyId+ -- ^ Property 'Q.enqueueStrategy'.+ | EnqueueStoringStrategyId+ -- ^ Property 'Q.enqueueStoringStrategy'.+ | DequeueStrategyId+ -- ^ Property 'Q.dequeueStrategy'.+ | QueueNullId+ -- ^ Property 'Q.queueNull'.+ | QueueFullId+ -- ^ Property 'Q.queueFull'.+ | QueueMaxCountId+ -- ^ Property 'Q.queueMaxCount'.+ | QueueCountId+ -- ^ Property 'Q.queueCount'.+ | QueueCountStatsId+ -- ^ Property 'Q.queueCountStats'.+ | EnqueueCountId+ -- ^ Property 'Q.enqueueCount'.+ | EnqueueLostCountId+ -- ^ Property 'Q.enqueueLostCount'.+ | EnqueueStoreCountId+ -- ^ Property 'Q.enqueueStoreCount'.+ | DequeueCountId+ -- ^ Property 'Q.dequeueCount'.+ | DequeueExtractCountId+ -- ^ Property 'Q.dequeueExtractCount'.+ | QueueLoadFactorId+ -- ^ Property 'Q.queueLoadFactor'.+ | EnqueueRateId+ -- ^ Property 'Q.enqueueRate'.+ | EnqueueStoreRateId+ -- ^ Property 'Q.enqueueStoreRate'.+ | DequeueRateId+ -- ^ Property 'Q.dequeueRate'.+ | DequeueExtractRateId+ -- ^ Property 'Q.dequeueExtractRate'.+ | QueueWaitTimeId+ -- ^ Property 'Q.queueWaitTime'.+ | QueueTotalWaitTimeId+ -- ^ Property 'Q.queueTotalWaitTime'.+ | EnqueueWaitTimeId+ -- ^ Property 'Q.enqueueWaitTime'.+ | DequeueWaitTimeId+ -- ^ Property 'Q.dequeueWaitTime'.+ | QueueRateId+ -- ^ Property 'Q.queueRate'.+ | ArrivalTimerId+ -- ^ An 'ArrivalTimer'.+ | ArrivalProcessingTimeId+ -- ^ Property 'arrivalProcessingTime'.+ | ServerId+ -- ^ Represents a 'Server'.+ | ServerInitStateId+ -- ^ Property 'serverInitState'.+ | ServerStateId+ -- ^ Property 'serverState'.+ | ServerTotalInputWaitTimeId+ -- ^ Property 'serverTotalInputWaitTime'.+ | ServerTotalProcessingTimeId+ -- ^ Property 'serverTotalProcessingTime'.+ | ServerTotalOutputWaitTimeId+ -- ^ Property 'serverTotalOutputWaitTime'.+ | ServerInputWaitTimeId+ -- ^ Property 'serverInputWaitTime'.+ | ServerProcessingTimeId+ -- ^ Property 'serverProcessingTime'.+ | ServerOutputWaitTimeId+ -- ^ Property 'serverOutputWaitTime'.+ | ServerInputWaitFactorId+ -- ^ Property 'serverInputWaitFactor'.+ | ServerProcessingFactorId+ -- ^ Property 'serverProcessingFactor'.+ | ServerOutputWaitFactorId+ -- ^ Property 'serverOutputWaitFactor'.+ | ActivityId+ -- ^ Represents an 'Activity'.+ | ActivityInitStateId+ -- ^ Property 'activityInitState'.+ | ActivityStateId+ -- ^ Property 'activityState'.+ | ActivityTotalUtilisationTimeId+ -- ^ Property 'activityTotalUtilisationTime'.+ | ActivityTotalIdleTimeId+ -- ^ Property 'activityTotalIdleTime'.+ | ActivityUtilisationTimeId+ -- ^ Property 'activityUtilisationTime'.+ | ActivityIdleTimeId+ -- ^ Property 'activityIdleTime'.+ | ActivityUtilisationFactorId+ -- ^ Property 'activityUtilisationFactor'.+ | ActivityIdleFactorId+ -- ^ Property 'activityIdleFactor'.+ | UserDefinedResultId ResultDescription+ -- ^ An user defined description.+ | LocalisedResultId (M.Map ResultLocale ResultDescription)+ -- ^ A localised property or object name.+ deriving (Eq, Ord, Show)++-- | The Russian locale.+russianResultLocale :: ResultLocale+russianResultLocale = "ru"++-- | The English locale.+englishResultLocale :: ResultLocale+englishResultLocale = "en"++-- | The Russian localisation of the simulation results.+russianResultLocalisation :: ResultLocalisation+russianResultLocalisation TimeId = "модельное время"+russianResultLocalisation VectorId = "вектор"+russianResultLocalisation (VectorItemId x) = "элемент с индексом " ++ x+russianResultLocalisation SamplingStatsId = "сводная статистика"+russianResultLocalisation SamplingStatsCountId = "количество"+russianResultLocalisation SamplingStatsMinId = "минимальное значение"+russianResultLocalisation SamplingStatsMaxId = "максимальное значение"+russianResultLocalisation SamplingStatsMeanId = "среднее значение"+russianResultLocalisation SamplingStatsMean2Id = "среднее квадратов"+russianResultLocalisation SamplingStatsVarianceId = "дисперсия"+russianResultLocalisation SamplingStatsDeviationId = "среднеквадратическое отклонение"+russianResultLocalisation TimingStatsId = "временная статистика"+russianResultLocalisation TimingStatsCountId = "количество"+russianResultLocalisation TimingStatsMinId = "минимальное значение"+russianResultLocalisation TimingStatsMaxId = "максимальное значение"+russianResultLocalisation TimingStatsMeanId = "среднее значение"+russianResultLocalisation TimingStatsVarianceId = "дисперсия"+russianResultLocalisation TimingStatsDeviationId = "среднеквадратическое отклонение"+russianResultLocalisation TimingStatsMinTimeId = "время достижения минимума"+russianResultLocalisation TimingStatsMaxTimeId = "время достижения максимума"+russianResultLocalisation TimingStatsStartTimeId = "начальное время сбора статистики"+russianResultLocalisation TimingStatsLastTimeId = "конечное время сбора статистики"+russianResultLocalisation TimingStatsSumId = "сумма"+russianResultLocalisation TimingStatsSum2Id = "сумма квадратов"+russianResultLocalisation FiniteQueueId = "конечная очередь"+russianResultLocalisation InfiniteQueueId = "бесконечная очередь"+russianResultLocalisation EnqueueStrategyId = "стратегия добавления элементов"+russianResultLocalisation EnqueueStoringStrategyId = "стратегия хранения элементов"+russianResultLocalisation DequeueStrategyId = "стратегия извлечения элементов"+russianResultLocalisation QueueNullId = "очередь пуста?"+russianResultLocalisation QueueFullId = "очередь заполнена?"+russianResultLocalisation QueueMaxCountId = "емкость очереди"+russianResultLocalisation QueueCountId = "текущий размер очереди"+russianResultLocalisation QueueCountStatsId = "статистика по размеру очереди"+russianResultLocalisation EnqueueCountId = "общее количество попыток добавить элементы"+russianResultLocalisation EnqueueLostCountId = "общее количество неудачных попыток добавить элементы"+russianResultLocalisation EnqueueStoreCountId = "общее количество сохраненных элементов"+russianResultLocalisation DequeueCountId = "общее количество запросов на извлечение элементов"+russianResultLocalisation DequeueExtractCountId = "общее количество извлеченных элементов"+russianResultLocalisation QueueLoadFactorId = "коэфф. загрузки (размер, поделенный на емкость)"+russianResultLocalisation EnqueueRateId = "количество попыток добавить на ед. времени"+russianResultLocalisation EnqueueStoreRateId = "количество сохраненных на ед. времени"+russianResultLocalisation DequeueRateId = "количество запросов на извлечение в ед. времени"+russianResultLocalisation DequeueExtractRateId = "количество извлеченных на ед. времени"+russianResultLocalisation QueueWaitTimeId = "время ожидания (сохранили -> извлекли)"+russianResultLocalisation QueueTotalWaitTimeId = "общее время ожидания (попытались добавить -> извлекли)"+russianResultLocalisation EnqueueWaitTimeId = "время ожидания добавления (попытались добавить -> сохранили)"+russianResultLocalisation DequeueWaitTimeId = "время ожидания извлечения (запросили извлечь -> извлекли)"+russianResultLocalisation QueueRateId = "усредненная скорость (как средняя длина очереди на среднее время ожидания)"+russianResultLocalisation ArrivalTimerId = "как долго обрабатываются заявки?"+russianResultLocalisation ArrivalProcessingTimeId = "время обработки заявки"+russianResultLocalisation ServerId = "сервер"+russianResultLocalisation ServerInitStateId = "начальное состояние"+russianResultLocalisation ServerStateId = "текущее состояние"+russianResultLocalisation ServerTotalInputWaitTimeId = "общее время блокировки в ожидании ввода"+russianResultLocalisation ServerTotalProcessingTimeId = "общее время, потраченное на саму обработку заданий"+russianResultLocalisation ServerTotalOutputWaitTimeId = "общее время блокировки при попытке доставить вывод"+russianResultLocalisation ServerInputWaitTimeId = "время блокировки в ожидании ввода"+russianResultLocalisation ServerProcessingTimeId = "время, потраченное на саму обработку заданий"+russianResultLocalisation ServerOutputWaitTimeId = "время блокировки при попытке доставить вывод"+russianResultLocalisation ServerInputWaitFactorId = "относительное время блокировки в ожидании ввода (от 0 до 1)"+russianResultLocalisation ServerProcessingFactorId = "относительное время, потраченное на саму обработку заданий (от 0 до 1)"+russianResultLocalisation ServerOutputWaitFactorId = "относительное время блокировки при попытке доставить вывод (от 0 до 1)"+russianResultLocalisation ActivityId = "активность"+russianResultLocalisation ActivityInitStateId = "начальное состояние"+russianResultLocalisation ActivityStateId = "текущее состояние"+russianResultLocalisation ActivityTotalUtilisationTimeId = "общее время использования"+russianResultLocalisation ActivityTotalIdleTimeId = "общее время простоя"+russianResultLocalisation ActivityUtilisationTimeId = "статистика времени использования"+russianResultLocalisation ActivityIdleTimeId = "статистика времени простоя"+russianResultLocalisation ActivityUtilisationFactorId = "относительное время использования (от 0 до 1)"+russianResultLocalisation ActivityIdleFactorId = "относительное время простоя (от 0 до 1)"+russianResultLocalisation (UserDefinedResultId m) = m+russianResultLocalisation x@(LocalisedResultId m) =+ lookupResultLocalisation russianResultLocale x++-- | The English localisation of the simulation results.+englishResultLocalisation :: ResultLocalisation+englishResultLocalisation TimeId = "simulation time"+englishResultLocalisation VectorId = "vector"+englishResultLocalisation (VectorItemId x) = "item #" ++ x+englishResultLocalisation SamplingStatsId = "statistics summary"+englishResultLocalisation SamplingStatsCountId = "count"+englishResultLocalisation SamplingStatsMinId = "minimum"+englishResultLocalisation SamplingStatsMaxId = "maximum"+englishResultLocalisation SamplingStatsMeanId = "mean"+englishResultLocalisation SamplingStatsMean2Id = "mean square"+englishResultLocalisation SamplingStatsVarianceId = "variance"+englishResultLocalisation SamplingStatsDeviationId = "deviation"+englishResultLocalisation TimingStatsId = "timing statistics"+englishResultLocalisation TimingStatsCountId = "count"+englishResultLocalisation TimingStatsMinId = "minimum"+englishResultLocalisation TimingStatsMaxId = "maximum"+englishResultLocalisation TimingStatsMeanId = "mean"+englishResultLocalisation TimingStatsVarianceId = "variance"+englishResultLocalisation TimingStatsDeviationId = "deviation"+englishResultLocalisation TimingStatsMinTimeId = "the time of minimum"+englishResultLocalisation TimingStatsMaxTimeId = "the time of maximum"+englishResultLocalisation TimingStatsStartTimeId = "the start time"+englishResultLocalisation TimingStatsLastTimeId = "the last time"+englishResultLocalisation TimingStatsSumId = "sum"+englishResultLocalisation TimingStatsSum2Id = "sum square"+englishResultLocalisation FiniteQueueId = "the finite queue"+englishResultLocalisation InfiniteQueueId = "the infinite queue"+englishResultLocalisation EnqueueStrategyId = "the enqueueing strategy"+englishResultLocalisation EnqueueStoringStrategyId = "the storing strategy"+englishResultLocalisation DequeueStrategyId = "the dequeueing strategy"+englishResultLocalisation QueueNullId = "is the queue empty?"+englishResultLocalisation QueueFullId = "is the queue full?"+englishResultLocalisation QueueMaxCountId = "the queue capacity"+englishResultLocalisation QueueCountId = "the current queue size"+englishResultLocalisation QueueCountStatsId = "the queue size statistics"+englishResultLocalisation EnqueueCountId = "a total number of attempts to enqueue the items"+englishResultLocalisation EnqueueLostCountId = "a total number of the lost items when trying to enqueue"+englishResultLocalisation EnqueueStoreCountId = "a total number of the stored items"+englishResultLocalisation DequeueCountId = "a total number of requests for dequeueing"+englishResultLocalisation DequeueExtractCountId = "a total number of the dequeued items"+englishResultLocalisation QueueLoadFactorId = "the queue load (its size divided by its capacity)"+englishResultLocalisation EnqueueRateId = "how many attempts to enqueue per time?"+englishResultLocalisation EnqueueStoreRateId = "how many items were stored per time?"+englishResultLocalisation DequeueRateId = "how many requests for dequeueing per time?"+englishResultLocalisation DequeueExtractRateId = "how many items were dequeued per time?"+englishResultLocalisation QueueWaitTimeId = "the wait time (stored -> dequeued)"+englishResultLocalisation QueueTotalWaitTimeId = "the total wait time (tried to enqueue -> dequeued)"+englishResultLocalisation EnqueueWaitTimeId = "the enqueue wait time (tried to enqueue -> stored)"+englishResultLocalisation DequeueWaitTimeId = "the dequeue wait time (requested for dequeueing -> dequeued)"+englishResultLocalisation QueueRateId = "the average queue rate (= queue size / wait time)"+englishResultLocalisation ArrivalTimerId = "how long the arrivals are processed?"+englishResultLocalisation ArrivalProcessingTimeId = "the processing time of arrivals"+englishResultLocalisation ServerId = "the server"+englishResultLocalisation ServerInitStateId = "the initial state"+englishResultLocalisation ServerStateId = "the current state"+englishResultLocalisation ServerTotalInputWaitTimeId = "the total time spent while waiting for input"+englishResultLocalisation ServerTotalProcessingTimeId = "the total time spent on actual processing the tasks"+englishResultLocalisation ServerTotalOutputWaitTimeId = "the total time spent on delivering the output"+englishResultLocalisation ServerInputWaitTimeId = "the time spent while waiting for input"+englishResultLocalisation ServerProcessingTimeId = "the time spent on processing the tasks"+englishResultLocalisation ServerOutputWaitTimeId = "the time spent on delivering the output"+englishResultLocalisation ServerInputWaitFactorId = "the relative time spent while waiting for input (from 0 to 1)"+englishResultLocalisation ServerProcessingFactorId = "the relative time spent on processing the tasks (from 0 to 1)"+englishResultLocalisation ServerOutputWaitFactorId = "the relative time spent on delivering the output (from 0 to 1)"+englishResultLocalisation ActivityId = "the activity"+englishResultLocalisation ActivityInitStateId = "the initial state"+englishResultLocalisation ActivityStateId = "the current state"+englishResultLocalisation ActivityTotalUtilisationTimeId = "the total time of utilisation"+englishResultLocalisation ActivityTotalIdleTimeId = "the total idle time"+englishResultLocalisation ActivityUtilisationTimeId = "the utilisation time"+englishResultLocalisation ActivityIdleTimeId = "the idle time"+englishResultLocalisation ActivityUtilisationFactorId = "the relative utilisation time (от 0 до 1)"+englishResultLocalisation ActivityIdleFactorId = "the relative idle time (от 0 до 1)"+englishResultLocalisation (UserDefinedResultId m) = m+englishResultLocalisation x@(LocalisedResultId m) =+ lookupResultLocalisation englishResultLocale x++-- | Lookup a localisation by the specified locale.+lookupResultLocalisation :: ResultLocale -> ResultLocalisation+lookupResultLocalisation loc (UserDefinedResultId m) = m+lookupResultLocalisation loc (LocalisedResultId m) =+ case M.lookup loc m of+ Just x -> x+ Nothing ->+ case M.lookup russianResultLocale m of+ Just x -> x+ Nothing ->+ case M.lookup englishResultLocale m of+ Just x -> x+ Nothing -> ""+lookupResultLocalisation loc resultId = russianResultLocalisation resultId
Simulation/Aivika/Server.hs view
@@ -1,510 +1,507 @@- --- | --- Module : Simulation.Aivika.Server --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- It models the server that prodives a service. -module Simulation.Aivika.Server - (-- * Server - Server, - newServer, - newStateServer, - -- * Processing - serverProcessor, - -- * Server Properties and Activities - serverInitState, - serverState, - serverTotalInputWaitTime, - serverTotalProcessingTime, - serverTotalOutputWaitTime, - serverInputWaitTime, - serverProcessingTime, - serverOutputWaitTime, - serverInputWaitFactor, - serverProcessingFactor, - serverOutputWaitFactor, - -- * Summary - serverSummary, - -- * Derived Signals for Properties - serverStateChanged, - serverStateChanged_, - serverTotalInputWaitTimeChanged, - serverTotalInputWaitTimeChanged_, - serverTotalProcessingTimeChanged, - serverTotalProcessingTimeChanged_, - serverTotalOutputWaitTimeChanged, - serverTotalOutputWaitTimeChanged_, - serverInputWaitTimeChanged, - serverInputWaitTimeChanged_, - serverProcessingTimeChanged, - serverProcessingTimeChanged_, - serverOutputWaitTimeChanged, - serverOutputWaitTimeChanged_, - serverInputWaitFactorChanged, - serverInputWaitFactorChanged_, - serverProcessingFactorChanged, - serverProcessingFactorChanged_, - serverOutputWaitFactorChanged, - serverOutputWaitFactorChanged_, - -- * Basic Signals - serverInputReceived, - serverTaskProcessed, - serverOutputProvided, - -- * Overall Signal - serverChanged_) where - -import Data.IORef -import Data.Monoid - -import Control.Monad.Trans -import Control.Arrow - -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@. - serverTotalInputWaitTimeRef :: 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. - serverTotalOutputWaitTimeRef :: IORef Double, - -- ^ The counted total time spent for delivering the output. - serverInputWaitTimeRef :: 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. - serverOutputWaitTimeRef :: 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 = - flip newStateServer () $ \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. -newStateServer :: (s -> a -> Process (s, b)) - -- ^ provide a new state and output by the specified - -- old state and input - -> s - -- ^ the initial state - -> Simulation (Server s a b) -newStateServer provide state = - 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, - serverTotalInputWaitTimeRef = r1, - serverTotalProcessingTimeRef = r2, - serverTotalOutputWaitTimeRef = r3, - serverInputWaitTimeRef = r4, - serverProcessingTimeRef = r5, - serverOutputWaitTimeRef = 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. Sometimes --- it can be indeed useful if you want to aggregate the statistics for different --- servers simultaneously, but it would be more preferable to avoid this. --- --- If you connect different server processors returned by this function in a chain --- with help of '>>>' or other category combinator then this chain will act as one --- whole, where the first server will take a new task only after the last server --- finishes its current task and requests for the next one from the previous processor --- in the chain. This is not always that thing you might need. --- --- To model a sequence of the server processors working independently, you --- should separate them with help of the 'prefetchProcessor' that plays a role --- of a small one-place buffer in that case. --- --- The queue processors usually have the prefetching capabilities per se, where --- the items are already stored in the queue. Therefore, the server processor --- should not be prefetched if it is connected directly with the queue processor. -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' (serverTotalOutputWaitTimeRef server) (+ (t0 - t')) - modifyIORef' (serverOutputWaitTimeRef server) $ - addSamplingStats (t0 - t') - triggerSignal (serverOutputProvidedSource server) (a', b') - -- get input - (a, xs') <- runStream xs - t1 <- liftDynamics time - liftEvent $ - do liftIO $ - do modifyIORef' (serverTotalInputWaitTimeRef server) (+ (t1 - t0)) - modifyIORef' (serverInputWaitTimeRef 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 when the server was locked while awaiting the input. --- --- The value returned changes discretely and it is usually delayed relative --- to the current simulation time. --- --- See also 'serverTotalInputWaitTimeChanged' and 'serverTotalInputWaitTimeChanged_'. -serverTotalInputWaitTime :: Server s a b -> Event Double -serverTotalInputWaitTime server = - Event $ \p -> readIORef (serverTotalInputWaitTimeRef server) - --- | Signal when the 'serverTotalInputWaitTime' property value has changed. -serverTotalInputWaitTimeChanged :: Server s a b -> Signal Double -serverTotalInputWaitTimeChanged server = - mapSignalM (const $ serverTotalInputWaitTime server) (serverTotalInputWaitTimeChanged_ server) - --- | Signal when the 'serverTotalInputWaitTime' property value has changed. -serverTotalInputWaitTimeChanged_ :: Server s a b -> Signal () -serverTotalInputWaitTimeChanged_ server = - mapSignal (const ()) (serverInputReceived server) - --- | Return the counted total time spent by the server while processing the 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 locked while trying --- to deliver the output. --- --- The value returned changes discretely and it is usually delayed relative --- to the current simulation time. --- --- See also 'serverTotalOutputWaitTimeChanged' and 'serverTotalOutputWaitTimeChanged_'. -serverTotalOutputWaitTime :: Server s a b -> Event Double -serverTotalOutputWaitTime server = - Event $ \p -> readIORef (serverTotalOutputWaitTimeRef server) - --- | Signal when the 'serverTotalOutputWaitTime' property value has changed. -serverTotalOutputWaitTimeChanged :: Server s a b -> Signal Double -serverTotalOutputWaitTimeChanged server = - mapSignalM (const $ serverTotalOutputWaitTime server) (serverTotalOutputWaitTimeChanged_ server) - --- | Signal when the 'serverTotalOutputWaitTime' property value has changed. -serverTotalOutputWaitTimeChanged_ :: Server s a b -> Signal () -serverTotalOutputWaitTimeChanged_ server = - mapSignal (const ()) (serverOutputProvided server) - --- | Return the statistics of the time when the server was locked while awaiting the input. --- --- The value returned changes discretely and it is usually delayed relative --- to the current simulation time. --- --- See also 'serverInputWaitTimeChanged' and 'serverInputWaitTimeChanged_'. -serverInputWaitTime :: Server s a b -> Event (SamplingStats Double) -serverInputWaitTime server = - Event $ \p -> readIORef (serverInputWaitTimeRef server) - --- | Signal when the 'serverInputWaitTime' property value has changed. -serverInputWaitTimeChanged :: Server s a b -> Signal (SamplingStats Double) -serverInputWaitTimeChanged server = - mapSignalM (const $ serverInputWaitTime server) (serverInputWaitTimeChanged_ server) - --- | Signal when the 'serverInputWaitTime' property value has changed. -serverInputWaitTimeChanged_ :: Server s a b -> Signal () -serverInputWaitTimeChanged_ server = - mapSignal (const ()) (serverInputReceived server) - --- | Return the statistics of the time spent by the server while processing 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 locked while trying --- to deliver the output. --- --- The value returned changes discretely and it is usually delayed relative --- to the current simulation time. --- --- See also 'serverOutputWaitTimeChanged' and 'serverOutputWaitTimeChanged_'. -serverOutputWaitTime :: Server s a b -> Event (SamplingStats Double) -serverOutputWaitTime server = - Event $ \p -> readIORef (serverOutputWaitTimeRef server) - --- | Signal when the 'serverOutputWaitTime' property value has changed. -serverOutputWaitTimeChanged :: Server s a b -> Signal (SamplingStats Double) -serverOutputWaitTimeChanged server = - mapSignalM (const $ serverOutputWaitTime server) (serverOutputWaitTimeChanged_ server) - --- | Signal when the 'serverOutputWaitTime' property value has changed. -serverOutputWaitTimeChanged_ :: Server s a b -> Signal () -serverOutputWaitTimeChanged_ 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 --- --- @ --- totalInputWaitTime \/ (totalInputWaitTime + totalProcessingTime + totalOutputWaitTime) --- @ --- --- As before in this module, the value returned changes discretely and --- it is usually delayed relative to the current simulation time. --- --- See also 'serverInputWaitFactorChanged' and 'serverInputWaitFactorChanged_'. -serverInputWaitFactor :: Server s a b -> Event Double -serverInputWaitFactor server = - Event $ \p -> - do x1 <- readIORef (serverTotalInputWaitTimeRef server) - x2 <- readIORef (serverTotalProcessingTimeRef server) - x3 <- readIORef (serverTotalOutputWaitTimeRef server) - return (x1 / (x1 + x2 + x3)) - --- | Signal when the 'serverInputWaitFactor' property value has changed. -serverInputWaitFactorChanged :: Server s a b -> Signal Double -serverInputWaitFactorChanged server = - mapSignalM (const $ serverInputWaitFactor server) (serverInputWaitFactorChanged_ server) - --- | Signal when the 'serverInputWaitFactor' property value has changed. -serverInputWaitFactorChanged_ :: Server s a b -> Signal () -serverInputWaitFactorChanged_ 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 \/ (totalInputWaitTime + totalProcessingTime + totalOutputWaitTime) --- @ --- --- As before in this module, the value returned changes discretely and --- it is usually delayed relative to the current simulation time. --- --- See also 'serverProcessingFactorChanged' and 'serverProcessingFactorChanged_'. -serverProcessingFactor :: Server s a b -> Event Double -serverProcessingFactor server = - Event $ \p -> - do x1 <- readIORef (serverTotalInputWaitTimeRef server) - x2 <- readIORef (serverTotalProcessingTimeRef server) - x3 <- readIORef (serverTotalOutputWaitTimeRef server) - return (x2 / (x1 + x2 + x3)) - --- | Signal when the 'serverProcessingFactor' property value has changed. -serverProcessingFactorChanged :: Server s a b -> Signal Double -serverProcessingFactorChanged server = - mapSignalM (const $ serverProcessingFactor server) (serverProcessingFactorChanged_ server) - --- | Signal when the 'serverProcessingFactor' property value has changed. -serverProcessingFactorChanged_ :: Server s a b -> Signal () -serverProcessingFactorChanged_ 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 --- --- @ --- totalOutputWaitTime \/ (totalInputWaitTime + totalProcessingTime + totalOutputWaitTime) --- @ --- --- As before in this module, the value returned changes discretely and --- it is usually delayed relative to the current simulation time. --- --- See also 'serverOutputWaitFactorChanged' and 'serverOutputWaitFactorChanged_'. -serverOutputWaitFactor :: Server s a b -> Event Double -serverOutputWaitFactor server = - Event $ \p -> - do x1 <- readIORef (serverTotalInputWaitTimeRef server) - x2 <- readIORef (serverTotalProcessingTimeRef server) - x3 <- readIORef (serverTotalOutputWaitTimeRef server) - return (x3 / (x1 + x2 + x3)) - --- | Signal when the 'serverOutputWaitFactor' property value has changed. -serverOutputWaitFactorChanged :: Server s a b -> Signal Double -serverOutputWaitFactorChanged server = - mapSignalM (const $ serverOutputWaitFactor server) (serverOutputWaitFactorChanged_ server) - --- | Signal when the 'serverOutputWaitFactor' property value has changed. -serverOutputWaitFactorChanged_ :: Server s a b -> Signal () -serverOutputWaitFactorChanged_ 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 (serverTotalInputWaitTimeRef server) - tx2 <- readIORef (serverTotalProcessingTimeRef server) - tx3 <- readIORef (serverTotalOutputWaitTimeRef server) - let xf1 = tx1 / (tx1 + tx2 + tx3) - xf2 = tx2 / (tx1 + tx2 + tx3) - xf3 = tx3 / (tx1 + tx2 + tx3) - xs1 <- readIORef (serverInputWaitTimeRef server) - xs2 <- readIORef (serverProcessingTimeRef server) - xs3 <- readIORef (serverOutputWaitTimeRef server) - let tab = replicate indent ' ' - return $ - showString tab . - showString "total input wait time (locked while awaiting the input) = " . shows tx1 . - showString "\n" . - showString tab . - showString "total processing time = " . shows tx2 . - showString "\n" . - showString tab . - showString "total output wait time (locked while delivering the output) = " . shows tx3 . - showString "\n\n" . - showString tab . - showString "input wait factor (from 0 to 1) = " . shows xf1 . - showString "\n" . - showString tab . - showString "processing factor (from 0 to 1) = " . shows xf2 . - showString "\n" . - showString tab . - showString "output wait factor (from 0 to 1) = " . shows xf3 . - showString "\n\n" . - showString tab . - showString "input wait time (locked while awaiting the input):\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 wait time (locked while delivering the output):\n\n" . - samplingStatsSummary xs3 (2 + indent) ++-- |+-- Module : Simulation.Aivika.Server+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- It models the server that prodives a service.+module Simulation.Aivika.Server+ (-- * Server+ Server,+ newServer,+ newStateServer,+ -- * Processing+ serverProcessor,+ -- * Server Properties and Activities+ serverInitState,+ serverState,+ serverTotalInputWaitTime,+ serverTotalProcessingTime,+ serverTotalOutputWaitTime,+ serverInputWaitTime,+ serverProcessingTime,+ serverOutputWaitTime,+ serverInputWaitFactor,+ serverProcessingFactor,+ serverOutputWaitFactor,+ -- * Summary+ serverSummary,+ -- * Derived Signals for Properties+ serverStateChanged,+ serverStateChanged_,+ serverTotalInputWaitTimeChanged,+ serverTotalInputWaitTimeChanged_,+ serverTotalProcessingTimeChanged,+ serverTotalProcessingTimeChanged_,+ serverTotalOutputWaitTimeChanged,+ serverTotalOutputWaitTimeChanged_,+ serverInputWaitTimeChanged,+ serverInputWaitTimeChanged_,+ serverProcessingTimeChanged,+ serverProcessingTimeChanged_,+ serverOutputWaitTimeChanged,+ serverOutputWaitTimeChanged_,+ serverInputWaitFactorChanged,+ serverInputWaitFactorChanged_,+ serverProcessingFactorChanged,+ serverProcessingFactorChanged_,+ serverOutputWaitFactorChanged,+ serverOutputWaitFactorChanged_,+ -- * Basic Signals+ serverInputReceived,+ serverTaskProcessed,+ serverOutputProvided,+ -- * Overall Signal+ serverChanged_) where++import Data.IORef+import Data.Monoid++import Control.Monad.Trans+import Control.Arrow++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@.+ serverTotalInputWaitTimeRef :: 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.+ serverTotalOutputWaitTimeRef :: IORef Double,+ -- ^ The counted total time spent for delivering the output.+ serverInputWaitTimeRef :: 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.+ serverOutputWaitTimeRef :: 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@.+newServer :: (a -> Process b)+ -- ^ provide an output by the specified input+ -> Simulation (Server () a b)+newServer provide =+ flip newStateServer () $ \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@.+newStateServer :: (s -> a -> Process (s, b))+ -- ^ provide a new state and output by the specified + -- old state and input+ -> s+ -- ^ the initial state+ -> Simulation (Server s a b)+newStateServer provide state =+ 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,+ serverTotalInputWaitTimeRef = r1,+ serverTotalProcessingTimeRef = r2,+ serverTotalOutputWaitTimeRef = r3,+ serverInputWaitTimeRef = r4,+ serverProcessingTimeRef = r5,+ serverOutputWaitTimeRef = 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. Sometimes +-- it can be indeed useful if you want to aggregate the statistics for different +-- servers simultaneously, but it would be more preferable to avoid this.+--+-- If you connect different server processors returned by this function in a chain +-- with help of '>>>' or other category combinator then this chain will act as one +-- whole, where the first server will take a new task only after the last server +-- finishes its current task and requests for the next one from the previous processor +-- in the chain. This is not always that thing you might need.+--+-- To model a sequence of the server processors working independently, you+-- should use the 'processorSeq' function which separates the processors with help of+-- the 'prefetchProcessor' that plays a role of a small one-place buffer in that case.+--+-- The queue processors usually have the prefetching capabilities per se, where+-- the items are already stored in the queue. Therefore, the server processor+-- should not be prefetched if it is connected directly with the queue processor.+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' (serverTotalOutputWaitTimeRef server) (+ (t0 - t'))+ modifyIORef' (serverOutputWaitTimeRef server) $+ addSamplingStats (t0 - t')+ triggerSignal (serverOutputProvidedSource server) (a', b')+ -- get input+ (a, xs') <- runStream xs+ t1 <- liftDynamics time+ liftEvent $+ do liftIO $+ do modifyIORef' (serverTotalInputWaitTimeRef server) (+ (t1 - t0))+ modifyIORef' (serverInputWaitTimeRef 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 when the server was locked while awaiting the input.+--+-- The value returned changes discretely and it is usually delayed relative+-- to the current simulation time.+--+-- See also 'serverTotalInputWaitTimeChanged' and 'serverTotalInputWaitTimeChanged_'.+serverTotalInputWaitTime :: Server s a b -> Event Double+serverTotalInputWaitTime server =+ Event $ \p -> readIORef (serverTotalInputWaitTimeRef server)+ +-- | Signal when the 'serverTotalInputWaitTime' property value has changed.+serverTotalInputWaitTimeChanged :: Server s a b -> Signal Double+serverTotalInputWaitTimeChanged server =+ mapSignalM (const $ serverTotalInputWaitTime server) (serverTotalInputWaitTimeChanged_ server)+ +-- | Signal when the 'serverTotalInputWaitTime' property value has changed.+serverTotalInputWaitTimeChanged_ :: Server s a b -> Signal ()+serverTotalInputWaitTimeChanged_ server =+ mapSignal (const ()) (serverInputReceived server)++-- | Return the counted total time spent by the server while processing the 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 locked while trying+-- to deliver the output.+--+-- The value returned changes discretely and it is usually delayed relative+-- to the current simulation time.+--+-- See also 'serverTotalOutputWaitTimeChanged' and 'serverTotalOutputWaitTimeChanged_'.+serverTotalOutputWaitTime :: Server s a b -> Event Double+serverTotalOutputWaitTime server =+ Event $ \p -> readIORef (serverTotalOutputWaitTimeRef server)+ +-- | Signal when the 'serverTotalOutputWaitTime' property value has changed.+serverTotalOutputWaitTimeChanged :: Server s a b -> Signal Double+serverTotalOutputWaitTimeChanged server =+ mapSignalM (const $ serverTotalOutputWaitTime server) (serverTotalOutputWaitTimeChanged_ server)+ +-- | Signal when the 'serverTotalOutputWaitTime' property value has changed.+serverTotalOutputWaitTimeChanged_ :: Server s a b -> Signal ()+serverTotalOutputWaitTimeChanged_ server =+ mapSignal (const ()) (serverOutputProvided server)++-- | Return the statistics of the time when the server was locked while awaiting the input.+--+-- The value returned changes discretely and it is usually delayed relative+-- to the current simulation time.+--+-- See also 'serverInputWaitTimeChanged' and 'serverInputWaitTimeChanged_'.+serverInputWaitTime :: Server s a b -> Event (SamplingStats Double)+serverInputWaitTime server =+ Event $ \p -> readIORef (serverInputWaitTimeRef server)+ +-- | Signal when the 'serverInputWaitTime' property value has changed.+serverInputWaitTimeChanged :: Server s a b -> Signal (SamplingStats Double)+serverInputWaitTimeChanged server =+ mapSignalM (const $ serverInputWaitTime server) (serverInputWaitTimeChanged_ server)+ +-- | Signal when the 'serverInputWaitTime' property value has changed.+serverInputWaitTimeChanged_ :: Server s a b -> Signal ()+serverInputWaitTimeChanged_ server =+ mapSignal (const ()) (serverInputReceived server)++-- | Return the statistics of the time spent by the server while processing 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 locked while trying+-- to deliver the output. +--+-- The value returned changes discretely and it is usually delayed relative+-- to the current simulation time.+--+-- See also 'serverOutputWaitTimeChanged' and 'serverOutputWaitTimeChanged_'.+serverOutputWaitTime :: Server s a b -> Event (SamplingStats Double)+serverOutputWaitTime server =+ Event $ \p -> readIORef (serverOutputWaitTimeRef server)+ +-- | Signal when the 'serverOutputWaitTime' property value has changed.+serverOutputWaitTimeChanged :: Server s a b -> Signal (SamplingStats Double)+serverOutputWaitTimeChanged server =+ mapSignalM (const $ serverOutputWaitTime server) (serverOutputWaitTimeChanged_ server)+ +-- | Signal when the 'serverOutputWaitTime' property value has changed.+serverOutputWaitTimeChanged_ :: Server s a b -> Signal ()+serverOutputWaitTimeChanged_ 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+--+-- @+-- totalInputWaitTime \/ (totalInputWaitTime + totalProcessingTime + totalOutputWaitTime)+-- @+--+-- As before in this module, the value returned changes discretely and+-- it is usually delayed relative to the current simulation time.+--+-- See also 'serverInputWaitFactorChanged' and 'serverInputWaitFactorChanged_'.+serverInputWaitFactor :: Server s a b -> Event Double+serverInputWaitFactor server =+ Event $ \p ->+ do x1 <- readIORef (serverTotalInputWaitTimeRef server)+ x2 <- readIORef (serverTotalProcessingTimeRef server)+ x3 <- readIORef (serverTotalOutputWaitTimeRef server)+ return (x1 / (x1 + x2 + x3))+ +-- | Signal when the 'serverInputWaitFactor' property value has changed.+serverInputWaitFactorChanged :: Server s a b -> Signal Double+serverInputWaitFactorChanged server =+ mapSignalM (const $ serverInputWaitFactor server) (serverInputWaitFactorChanged_ server)+ +-- | Signal when the 'serverInputWaitFactor' property value has changed.+serverInputWaitFactorChanged_ :: Server s a b -> Signal ()+serverInputWaitFactorChanged_ 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 \/ (totalInputWaitTime + totalProcessingTime + totalOutputWaitTime)+-- @+--+-- As before in this module, the value returned changes discretely and+-- it is usually delayed relative to the current simulation time.+--+-- See also 'serverProcessingFactorChanged' and 'serverProcessingFactorChanged_'.+serverProcessingFactor :: Server s a b -> Event Double+serverProcessingFactor server =+ Event $ \p ->+ do x1 <- readIORef (serverTotalInputWaitTimeRef server)+ x2 <- readIORef (serverTotalProcessingTimeRef server)+ x3 <- readIORef (serverTotalOutputWaitTimeRef server)+ return (x2 / (x1 + x2 + x3))+ +-- | Signal when the 'serverProcessingFactor' property value has changed.+serverProcessingFactorChanged :: Server s a b -> Signal Double+serverProcessingFactorChanged server =+ mapSignalM (const $ serverProcessingFactor server) (serverProcessingFactorChanged_ server)+ +-- | Signal when the 'serverProcessingFactor' property value has changed.+serverProcessingFactorChanged_ :: Server s a b -> Signal ()+serverProcessingFactorChanged_ 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+--+-- @+-- totalOutputWaitTime \/ (totalInputWaitTime + totalProcessingTime + totalOutputWaitTime)+-- @+--+-- As before in this module, the value returned changes discretely and+-- it is usually delayed relative to the current simulation time.+--+-- See also 'serverOutputWaitFactorChanged' and 'serverOutputWaitFactorChanged_'.+serverOutputWaitFactor :: Server s a b -> Event Double+serverOutputWaitFactor server =+ Event $ \p ->+ do x1 <- readIORef (serverTotalInputWaitTimeRef server)+ x2 <- readIORef (serverTotalProcessingTimeRef server)+ x3 <- readIORef (serverTotalOutputWaitTimeRef server)+ return (x3 / (x1 + x2 + x3))+ +-- | Signal when the 'serverOutputWaitFactor' property value has changed.+serverOutputWaitFactorChanged :: Server s a b -> Signal Double+serverOutputWaitFactorChanged server =+ mapSignalM (const $ serverOutputWaitFactor server) (serverOutputWaitFactorChanged_ server)+ +-- | Signal when the 'serverOutputWaitFactor' property value has changed.+serverOutputWaitFactorChanged_ :: Server s a b -> Signal ()+serverOutputWaitFactorChanged_ 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 (serverTotalInputWaitTimeRef server)+ tx2 <- readIORef (serverTotalProcessingTimeRef server)+ tx3 <- readIORef (serverTotalOutputWaitTimeRef server)+ let xf1 = tx1 / (tx1 + tx2 + tx3)+ xf2 = tx2 / (tx1 + tx2 + tx3)+ xf3 = tx3 / (tx1 + tx2 + tx3)+ xs1 <- readIORef (serverInputWaitTimeRef server)+ xs2 <- readIORef (serverProcessingTimeRef server)+ xs3 <- readIORef (serverOutputWaitTimeRef server)+ let tab = replicate indent ' '+ return $+ showString tab .+ showString "total input wait time (locked while awaiting the input) = " . shows tx1 .+ showString "\n" .+ showString tab .+ showString "total processing time = " . shows tx2 .+ showString "\n" .+ showString tab .+ showString "total output wait time (locked while delivering the output) = " . shows tx3 .+ showString "\n\n" .+ showString tab .+ showString "input wait factor (from 0 to 1) = " . shows xf1 .+ showString "\n" .+ showString tab .+ showString "processing factor (from 0 to 1) = " . shows xf2 .+ showString "\n" .+ showString tab .+ showString "output wait factor (from 0 to 1) = " . shows xf3 .+ showString "\n\n" .+ showString tab .+ showString "input wait time (locked while awaiting the input):\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 wait time (locked while delivering the output):\n\n" .+ samplingStatsSummary xs3 (2 + indent)
Simulation/Aivika/Signal.hs view
@@ -1,53 +1,53 @@- --- | --- Module : Simulation.Aivika.Signal --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- This module defines the signal which we can subscribe handlers to. --- These handlers can be disposed. The signal is triggered in the --- current time point actuating the corresponded computations from --- the handlers. --- -module Simulation.Aivika.Signal - (-- * Handling and Triggering Signal - Signal(..), - handleSignal_, - SignalSource, - newSignalSource, - publishSignal, - triggerSignal, - -- * Useful Combinators - mapSignal, - mapSignalM, - apSignal, - filterSignal, - filterSignalM, - emptySignal, - merge2Signals, - merge3Signals, - merge4Signals, - merge5Signals, - -- * Signal Arriving - arrivalSignal, - -- * Creating Signal in Time Points - newSignalInTimes, - newSignalInIntegTimes, - newSignalInStartTime, - newSignalInStopTime, - -- * Signal History - SignalHistory, - signalHistorySignal, - newSignalHistory, - newSignalHistoryStartingWith, - readSignalHistory, - -- * Signalable Computations - Signalable(..), - signalableChanged, - emptySignalable, - appendSignalable) where - -import Simulation.Aivika.Internal.Signal ++-- |+-- Module : Simulation.Aivika.Signal+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- This module defines the signal which we can subscribe handlers to. +-- These handlers can be disposed. The signal is triggered in the +-- current time point actuating the corresponded computations from +-- the handlers. +--+module Simulation.Aivika.Signal+ (-- * Handling and Triggering Signal+ Signal(..),+ handleSignal_,+ SignalSource,+ newSignalSource,+ publishSignal,+ triggerSignal,+ -- * Useful Combinators+ mapSignal,+ mapSignalM,+ apSignal,+ filterSignal,+ filterSignalM,+ emptySignal,+ merge2Signals,+ merge3Signals,+ merge4Signals,+ merge5Signals,+ -- * Signal Arriving+ arrivalSignal,+ -- * Creating Signal in Time Points+ newSignalInTimes,+ newSignalInIntegTimes,+ newSignalInStartTime,+ newSignalInStopTime,+ -- * Signal History+ SignalHistory,+ signalHistorySignal,+ newSignalHistory,+ newSignalHistoryStartingWith,+ readSignalHistory,+ -- * Signalable Computations+ Signalable(..),+ signalableChanged,+ emptySignalable,+ appendSignalable) where++import Simulation.Aivika.Internal.Signal
Simulation/Aivika/Simulation.hs view
@@ -1,25 +1,25 @@- --- | --- Module : Simulation.Aivika.Simulation --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- The module defines the 'Simulation' monad that represents a simulation run. --- -module Simulation.Aivika.Simulation - (-- * Simulation - Simulation, - SimulationLift(..), - runSimulation, - runSimulations, - -- * Error Handling - catchSimulation, - finallySimulation, - throwSimulation, - -- * Memoization - memoSimulation) where - -import Simulation.Aivika.Internal.Simulation ++-- |+-- Module : Simulation.Aivika.Simulation+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- The module defines the 'Simulation' monad that represents a simulation run.+-- +module Simulation.Aivika.Simulation+ (-- * Simulation+ Simulation,+ SimulationLift(..),+ runSimulation,+ runSimulations,+ -- * Error Handling+ catchSimulation,+ finallySimulation,+ throwSimulation,+ -- * Memoization+ memoSimulation) where++import Simulation.Aivika.Internal.Simulation
Simulation/Aivika/Specs.hs view
@@ -1,25 +1,25 @@- --- | --- Module : Simulation.Aivika.Specs --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- It defines the simulation specs and functions for this data type. -module Simulation.Aivika.Specs - (-- * Simulation Specs - Specs(..), - Method(..), - -- * Auxiliary Functions - basicTime, - integIterationBnds, - integIterationHiBnd, - integIterationLoBnd, - integPhaseBnds, - integPhaseHiBnd, - integPhaseLoBnd, - integTimes) where - -import Simulation.Aivika.Internal.Specs ++-- |+-- Module : Simulation.Aivika.Specs+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- It defines the simulation specs and functions for this data type.+module Simulation.Aivika.Specs+ (-- * Simulation Specs+ Specs(..),+ Method(..),+ -- * Auxiliary Functions+ basicTime,+ integIterationBnds,+ integIterationHiBnd,+ integIterationLoBnd,+ integPhaseBnds,+ integPhaseHiBnd,+ integPhaseLoBnd,+ integTimes) where++import Simulation.Aivika.Internal.Specs
Simulation/Aivika/Statistics.hs view
@@ -1,417 +1,417 @@- --- | --- Module : Simulation.Aivika.Statistics --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- Represents statistics. --- - -module Simulation.Aivika.Statistics - (-- * Simple Statistics - SamplingStats(..), - SamplingData(..), - combineSamplingStatsEither, - samplingStatsVariance, - samplingStatsDeviation, - samplingStatsSummary, - returnSamplingStats, - listSamplingStats, - fromIntSamplingStats, - -- * Timing Statistics - TimingStats(..), - TimingData(..), - timingStatsDeviation, - timingStatsSummary, - returnTimingStats, - fromIntTimingStats) where - -import Data.Monoid - --- | Defines data types that can be converted to 'Double'. -class Ord a => ConvertableToDouble a where - - -- | Convert the value to 'Double'. - convertToDouble :: a -> Double - -instance ConvertableToDouble Double where - convertToDouble = id - -instance ConvertableToDouble Int where - convertToDouble = fromIntegral - --- | Describes when the statistics consists of only samples --- not bound to the simulation time. -data SamplingStats a = - SamplingStats { samplingStatsCount :: !Int, - -- ^ The total number of samples. - samplingStatsMin :: !a, - -- ^ The minimum value among the samples. - samplingStatsMax :: !a, - -- ^ The maximum value among the samples. - samplingStatsMean :: !Double, - -- ^ The average value. - samplingStatsMean2 :: !Double - -- ^ The average square value. - } - deriving (Eq, Ord) - --- | Specifies data type from which values we can gather the statistics. -class SamplingData a where - - -- | An empty statistics that has no samples. - emptySamplingStats :: SamplingStats a - - -- | Add a new sample to the statistics. - addSamplingStats :: a -> SamplingStats a -> SamplingStats a - - -- | Combine two statistics. - combineSamplingStats :: SamplingStats a -> SamplingStats a -> SamplingStats a - -instance SamplingData a => Monoid (SamplingStats a) where - - mempty = emptySamplingStats - - mappend = combineSamplingStats - -instance SamplingData Double where - - emptySamplingStats = - SamplingStats { samplingStatsCount = 0, - samplingStatsMin = 1 / 0, - samplingStatsMax = (-1) / 0, - samplingStatsMean = 0 / 0, - samplingStatsMean2 = 0 / 0 } - - addSamplingStats = addSamplingStatsGeneric - - combineSamplingStats = combineSamplingStatsGeneric - -instance SamplingData Int where - - emptySamplingStats = - SamplingStats { samplingStatsCount = 0, - samplingStatsMin = maxBound, - samplingStatsMax = minBound, - samplingStatsMean = 0 / 0, - samplingStatsMean2 = 0 / 0 } - - addSamplingStats = addSamplingStatsGeneric - - combineSamplingStats = combineSamplingStatsGeneric - -addSamplingStatsGeneric :: ConvertableToDouble a => a -> SamplingStats a -> SamplingStats a -addSamplingStatsGeneric a stats - | isNaN x = stats - | count == 1 = SamplingStats { samplingStatsCount = 1, - samplingStatsMin = a, - samplingStatsMax = a, - samplingStatsMean = x, - samplingStatsMean2 = x * x } - | otherwise = SamplingStats { samplingStatsCount = count, - samplingStatsMin = minX, - samplingStatsMax = maxX, - samplingStatsMean = meanX, - samplingStatsMean2 = meanX2 } - where count = 1 + samplingStatsCount stats - minX = a `seq` min a (samplingStatsMin stats) - maxX = a `seq` max a (samplingStatsMax stats) - meanX = k1 * x + k2 * samplingStatsMean stats - meanX2 = k1 * x * x + k2 * samplingStatsMean2 stats - n = fromIntegral count - x = convertToDouble a - k1 = 1.0 / n - k2 = (n - 1.0) / n - -combineSamplingStatsGeneric :: ConvertableToDouble a => - SamplingStats a -> SamplingStats a -> SamplingStats a -combineSamplingStatsGeneric stats1 stats2 - | c1 == 0 = stats2 - | c2 == 0 = stats1 - | otherwise = SamplingStats { samplingStatsCount = c, - samplingStatsMin = minZ, - samplingStatsMax = maxZ, - samplingStatsMean = meanZ, - samplingStatsMean2 = meanZ2 } - where c1 = samplingStatsCount stats1 - c2 = samplingStatsCount stats2 - c = c1 + c2 - n1 = fromIntegral c1 - n2 = fromIntegral c2 - n = n1 + n2 - minX = samplingStatsMin stats1 - minY = samplingStatsMin stats2 - minZ = min minX minY - maxX = samplingStatsMax stats1 - maxY = samplingStatsMax stats2 - maxZ = max maxX maxY - meanX = samplingStatsMean stats1 - meanY = samplingStatsMean stats2 - meanZ = k1 * meanX + k2 * meanY - meanX2 = samplingStatsMean2 stats1 - meanY2 = samplingStatsMean2 stats2 - meanZ2 = k1 * meanX2 + k2 * meanY2 - k1 = n1 / n - k2 = n2 / n - --- | If allows combining statistics more efficiently if we know that the first argument can be a scalar. -combineSamplingStatsEither :: SamplingData a => Either a (SamplingStats a) -> SamplingStats a -> SamplingStats a -combineSamplingStatsEither (Left a) stats2 = addSamplingStats a stats2 -combineSamplingStatsEither (Right stats1) stats2 = combineSamplingStats stats1 stats2 - --- | Return the variance. -samplingStatsVariance :: SamplingStats a -> Double -samplingStatsVariance stats - | count == 1 = 0 - | otherwise = (meanX2 - meanX * meanX) * (n / (n - 1)) - where count = samplingStatsCount stats - meanX = samplingStatsMean stats - meanX2 = samplingStatsMean2 stats - n = fromIntegral count - --- | Return the deviation. -samplingStatsDeviation :: SamplingStats a -> Double -samplingStatsDeviation = sqrt . samplingStatsVariance - --- | Return the statistics by a single sample. -returnSamplingStats :: SamplingData a => a -> SamplingStats a -returnSamplingStats x = addSamplingStats x emptySamplingStats - --- | Create the statistics by the specified list of data. -listSamplingStats :: SamplingData a => [a] -> SamplingStats a -listSamplingStats = foldr addSamplingStats emptySamplingStats - --- | Convert the statistics from integer to double values. -fromIntSamplingStats :: SamplingStats Int -> SamplingStats Double -fromIntSamplingStats stats = - stats { samplingStatsMin = fromIntegral $ samplingStatsMin stats, - samplingStatsMax = fromIntegral $ samplingStatsMax stats } - --- | 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) . - showString " }" - -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 tab . - showString "mean = " . shows (samplingStatsMean stats) . - showString "\n" . - showString tab . - showString "std = " . shows (samplingStatsDeviation stats) . - showString "\n" . - showString tab . - showString "min = " . shows (samplingStatsMin stats) . - showString "\n" . - showString tab . - showString "max = " . shows (samplingStatsMax stats) - --- | This is the timing statistics where data are bound to the time. -data TimingStats a = - TimingStats { timingStatsCount :: !Int, - -- ^ Return the number of samples. - timingStatsMin :: !a, - -- ^ Return the minimum value. - timingStatsMax :: !a, - -- ^ Return the maximum value. - timingStatsMinTime :: !Double, - -- ^ Return the time at which the minimum is attained. - timingStatsMaxTime :: !Double, - -- ^ Return the time at which the maximum is attained. - timingStatsStartTime :: !Double, - -- ^ Return the start time of sampling. - timingStatsLastTime :: !Double, - -- ^ Return the last time of sampling. - timingStatsSum :: !Double, - -- ^ Return the sum of values. - timingStatsSum2 :: !Double - -- ^ Return the sum of square values. - } deriving (Eq, Ord) - --- | Defines the data type from which values we can gather the timing statistics. -class TimingData a where - - -- | An empty statistics that has no samples. - emptyTimingStats :: TimingStats a - - -- | Add a sample with the specified time to the statistics. - addTimingStats :: Double -> a -> TimingStats a -> TimingStats a - - -- | Return the average value. - timingStatsMean :: TimingStats a -> Double - - -- | Return the variance. - timingStatsVariance :: TimingStats a -> Double - -instance TimingData Double where - - emptyTimingStats = - TimingStats { timingStatsCount = 0, - timingStatsMin = 1 / 0, - timingStatsMax = (-1) / 0, - timingStatsMinTime = 1 / 0, - timingStatsMaxTime = (-1) / 0, - timingStatsStartTime = 1 / 0, - timingStatsLastTime = (-1) / 0, - timingStatsSum = 0 / 0, - timingStatsSum2 = 0 / 0 } - - addTimingStats = addTimingStatsGeneric - timingStatsMean = timingStatsMeanGeneric - timingStatsVariance = timingStatsVarianceGeneric - -instance TimingData Int where - - emptyTimingStats = - TimingStats { timingStatsCount = 0, - timingStatsMin = maxBound, - timingStatsMax = minBound, - timingStatsMinTime = 1 / 0, - timingStatsMaxTime = (-1) / 0, - timingStatsStartTime = 1 / 0, - timingStatsLastTime = (-1) / 0, - timingStatsSum = 0 / 0, - timingStatsSum2 = 0 / 0 } - - addTimingStats = addTimingStatsGeneric - timingStatsMean = timingStatsMeanGeneric - timingStatsVariance = timingStatsVarianceGeneric - -addTimingStatsGeneric :: ConvertableToDouble a => Double -> a -> TimingStats a -> TimingStats a -addTimingStatsGeneric t a stats - | t < t' = error "The current time cannot be less than the previous one: addTimingStats" - | isNaN x = stats - | count == 1 = TimingStats { timingStatsCount = 1, - timingStatsMin = a, - timingStatsMax = a, - timingStatsMinTime = t, - timingStatsMaxTime = t, - timingStatsStartTime = t, - timingStatsLastTime = t, - timingStatsSum = 0, - timingStatsSum2 = 0 } - | otherwise = TimingStats { timingStatsCount = count, - timingStatsMin = minX, - timingStatsMax = maxX, - timingStatsMinTime = minT, - timingStatsMaxTime = maxT, - timingStatsStartTime = t0, - timingStatsLastTime = t, - timingStatsSum = sumX, - timingStatsSum2 = sumX2 } - where count = 1 + timingStatsCount stats - minX' = timingStatsMin stats - maxX' = timingStatsMax stats - minX = a `seq` min a minX' - maxX = a `seq` max a maxX' - minT | a < minX' = t - | otherwise = timingStatsMinTime stats - maxT | a > maxX' = t - | otherwise = timingStatsMaxTime stats - t0 = timingStatsStartTime stats - t' = timingStatsLastTime stats - x = convertToDouble a - sumX' = timingStatsSum stats - sumX = sumX' + (t - t') * x - sumX2' = timingStatsSum2 stats - sumX2 = sumX2' + (t - t') * x * x - -timingStatsMeanGeneric :: ConvertableToDouble a => TimingStats a -> Double -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 - | 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 - where ex = timingStatsMeanGeneric stats - ex2 = timingStatsMean2Generic stats - --- | Return the deviation. -timingStatsDeviation :: TimingData a => TimingStats a -> Double -timingStatsDeviation = sqrt . timingStatsVariance - --- | Return the statistics by single timing data. -returnTimingStats :: TimingData a => Double -> a -> TimingStats a -returnTimingStats t a = addTimingStats t a emptyTimingStats - --- | Convert the statistics from integer to double values. -fromIntTimingStats :: TimingStats Int -> TimingStats Double -fromIntTimingStats stats = - stats { timingStatsMin = fromIntegral $ timingStatsMin stats, - timingStatsMax = fromIntegral $ timingStatsMax stats } - --- | 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 "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) . - showString "]" ++-- |+-- Module : Simulation.Aivika.Statistics+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- Represents statistics.+--++module Simulation.Aivika.Statistics+ (-- * Simple Statistics+ SamplingStats(..),+ SamplingData(..),+ combineSamplingStatsEither,+ samplingStatsVariance,+ samplingStatsDeviation,+ samplingStatsSummary,+ returnSamplingStats,+ listSamplingStats,+ fromIntSamplingStats,+ -- * Timing Statistics+ TimingStats(..),+ TimingData(..),+ timingStatsDeviation,+ timingStatsSummary,+ returnTimingStats,+ fromIntTimingStats) where ++import Data.Monoid++-- | Defines data types that can be converted to 'Double'.+class Ord a => ConvertableToDouble a where+ + -- | Convert the value to 'Double'.+ convertToDouble :: a -> Double+ +instance ConvertableToDouble Double where+ convertToDouble = id+ +instance ConvertableToDouble Int where+ convertToDouble = fromIntegral++-- | Describes when the statistics consists of only samples +-- not bound to the simulation time.+data SamplingStats a = + SamplingStats { samplingStatsCount :: !Int,+ -- ^ The total number of samples.+ samplingStatsMin :: !a,+ -- ^ The minimum value among the samples.+ samplingStatsMax :: !a,+ -- ^ The maximum value among the samples.+ samplingStatsMean :: !Double,+ -- ^ The average value.+ samplingStatsMean2 :: !Double + -- ^ The average square value.+ }+ deriving (Eq, Ord)+ +-- | Specifies data type from which values we can gather the statistics. +class SamplingData a where + + -- | An empty statistics that has no samples. + emptySamplingStats :: SamplingStats a+ + -- | Add a new sample to the statistics.+ addSamplingStats :: a -> SamplingStats a -> SamplingStats a++ -- | Combine two statistics.+ combineSamplingStats :: SamplingStats a -> SamplingStats a -> SamplingStats a++instance SamplingData a => Monoid (SamplingStats a) where + + mempty = emptySamplingStats+ + mappend = combineSamplingStats++instance SamplingData Double where++ emptySamplingStats =+ SamplingStats { samplingStatsCount = 0,+ samplingStatsMin = 1 / 0,+ samplingStatsMax = (-1) / 0,+ samplingStatsMean = 0 / 0,+ samplingStatsMean2 = 0 / 0 }+ + addSamplingStats = addSamplingStatsGeneric+ + combineSamplingStats = combineSamplingStatsGeneric+ +instance SamplingData Int where++ emptySamplingStats =+ SamplingStats { samplingStatsCount = 0,+ samplingStatsMin = maxBound,+ samplingStatsMax = minBound,+ samplingStatsMean = 0 / 0,+ samplingStatsMean2 = 0 / 0 }+ + addSamplingStats = addSamplingStatsGeneric++ combineSamplingStats = combineSamplingStatsGeneric+ +addSamplingStatsGeneric :: ConvertableToDouble a => a -> SamplingStats a -> SamplingStats a+addSamplingStatsGeneric a stats + | isNaN x = stats+ | count == 1 = SamplingStats { samplingStatsCount = 1,+ samplingStatsMin = a,+ samplingStatsMax = a,+ samplingStatsMean = x,+ samplingStatsMean2 = x * x }+ | otherwise = SamplingStats { samplingStatsCount = count,+ samplingStatsMin = minX,+ samplingStatsMax = maxX,+ samplingStatsMean = meanX,+ samplingStatsMean2 = meanX2 }+ where count = 1 + samplingStatsCount stats+ minX = a `seq` min a (samplingStatsMin stats)+ maxX = a `seq` max a (samplingStatsMax stats)+ meanX = k1 * x + k2 * samplingStatsMean stats+ meanX2 = k1 * x * x + k2 * samplingStatsMean2 stats+ n = fromIntegral count+ x = convertToDouble a+ k1 = 1.0 / n+ k2 = (n - 1.0) / n++combineSamplingStatsGeneric :: ConvertableToDouble a =>+ SamplingStats a -> SamplingStats a -> SamplingStats a+combineSamplingStatsGeneric stats1 stats2+ | c1 == 0 = stats2+ | c2 == 0 = stats1+ | otherwise = SamplingStats { samplingStatsCount = c,+ samplingStatsMin = minZ,+ samplingStatsMax = maxZ,+ samplingStatsMean = meanZ,+ samplingStatsMean2 = meanZ2 }+ where c1 = samplingStatsCount stats1+ c2 = samplingStatsCount stats2+ c = c1 + c2+ n1 = fromIntegral c1+ n2 = fromIntegral c2+ n = n1 + n2+ minX = samplingStatsMin stats1+ minY = samplingStatsMin stats2+ minZ = min minX minY+ maxX = samplingStatsMax stats1+ maxY = samplingStatsMax stats2+ maxZ = max maxX maxY+ meanX = samplingStatsMean stats1+ meanY = samplingStatsMean stats2+ meanZ = k1 * meanX + k2 * meanY+ meanX2 = samplingStatsMean2 stats1+ meanY2 = samplingStatsMean2 stats2+ meanZ2 = k1 * meanX2 + k2 * meanY2+ k1 = n1 / n+ k2 = n2 / n++-- | If allows combining statistics more efficiently if we know that the first argument can be a scalar.+combineSamplingStatsEither :: SamplingData a => Either a (SamplingStats a) -> SamplingStats a -> SamplingStats a+combineSamplingStatsEither (Left a) stats2 = addSamplingStats a stats2+combineSamplingStatsEither (Right stats1) stats2 = combineSamplingStats stats1 stats2++-- | Return the variance.+samplingStatsVariance :: SamplingStats a -> Double+samplingStatsVariance stats+ | count == 1 = 0+ | otherwise = (meanX2 - meanX * meanX) * (n / (n - 1))+ where count = samplingStatsCount stats+ meanX = samplingStatsMean stats+ meanX2 = samplingStatsMean2 stats+ n = fromIntegral count+ +-- | Return the deviation. +samplingStatsDeviation :: SamplingStats a -> Double+samplingStatsDeviation = sqrt . samplingStatsVariance++-- | Return the statistics by a single sample.+returnSamplingStats :: SamplingData a => a -> SamplingStats a+returnSamplingStats x = addSamplingStats x emptySamplingStats++-- | Create the statistics by the specified list of data.+listSamplingStats :: SamplingData a => [a] -> SamplingStats a+listSamplingStats = foldr addSamplingStats emptySamplingStats++-- | Convert the statistics from integer to double values.+fromIntSamplingStats :: SamplingStats Int -> SamplingStats Double+fromIntSamplingStats stats =+ stats { samplingStatsMin = fromIntegral $ samplingStatsMin stats,+ samplingStatsMax = fromIntegral $ samplingStatsMax stats }++-- | 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) .+ showString " }"++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 tab .+ showString "mean = " . shows (samplingStatsMean stats) . + showString "\n" .+ showString tab .+ showString "std = " . shows (samplingStatsDeviation stats) . + showString "\n" .+ showString tab .+ showString "min = " . shows (samplingStatsMin stats) . + showString "\n" .+ showString tab .+ showString "max = " . shows (samplingStatsMax stats)+ +-- | This is the timing statistics where data are bound to the time.+data TimingStats a =+ TimingStats { timingStatsCount :: !Int,+ -- ^ Return the number of samples.+ timingStatsMin :: !a,+ -- ^ Return the minimum value.+ timingStatsMax :: !a,+ -- ^ Return the maximum value.+ timingStatsMinTime :: !Double,+ -- ^ Return the time at which the minimum is attained.+ timingStatsMaxTime :: !Double,+ -- ^ Return the time at which the maximum is attained.+ timingStatsStartTime :: !Double,+ -- ^ Return the start time of sampling.+ timingStatsLastTime :: !Double,+ -- ^ Return the last time of sampling.+ timingStatsSum :: !Double,+ -- ^ Return the sum of values.+ timingStatsSum2 :: !Double + -- ^ Return the sum of square values.+ } deriving (Eq, Ord)+ +-- | Defines the data type from which values we can gather the timing statistics.+class TimingData a where + + -- | An empty statistics that has no samples.+ emptyTimingStats :: TimingStats a+ + -- | Add a sample with the specified time to the statistics.+ addTimingStats :: Double -> a -> TimingStats a -> TimingStats a+ + -- | Return the average value.+ timingStatsMean :: TimingStats a -> Double+ + -- | Return the variance.+ timingStatsVariance :: TimingStats a -> Double+ +instance TimingData Double where+ + emptyTimingStats = + TimingStats { timingStatsCount = 0,+ timingStatsMin = 1 / 0,+ timingStatsMax = (-1) / 0,+ timingStatsMinTime = 1 / 0,+ timingStatsMaxTime = (-1) / 0,+ timingStatsStartTime = 1 / 0,+ timingStatsLastTime = (-1) / 0,+ timingStatsSum = 0 / 0,+ timingStatsSum2 = 0 / 0 }+ + addTimingStats = addTimingStatsGeneric+ timingStatsMean = timingStatsMeanGeneric+ timingStatsVariance = timingStatsVarianceGeneric++instance TimingData Int where+ + emptyTimingStats = + TimingStats { timingStatsCount = 0,+ timingStatsMin = maxBound,+ timingStatsMax = minBound,+ timingStatsMinTime = 1 / 0,+ timingStatsMaxTime = (-1) / 0,+ timingStatsStartTime = 1 / 0,+ timingStatsLastTime = (-1) / 0,+ timingStatsSum = 0 / 0,+ timingStatsSum2 = 0 / 0 }+ + addTimingStats = addTimingStatsGeneric+ timingStatsMean = timingStatsMeanGeneric+ timingStatsVariance = timingStatsVarianceGeneric++addTimingStatsGeneric :: ConvertableToDouble a => Double -> a -> TimingStats a -> TimingStats a+addTimingStatsGeneric t a stats+ | t < t' = error "The current time cannot be less than the previous one: addTimingStats"+ | isNaN x = stats+ | count == 1 = TimingStats { timingStatsCount = 1,+ timingStatsMin = a,+ timingStatsMax = a,+ timingStatsMinTime = t,+ timingStatsMaxTime = t,+ timingStatsStartTime = t,+ timingStatsLastTime = t,+ timingStatsSum = 0,+ timingStatsSum2 = 0 }+ | otherwise = TimingStats { timingStatsCount = count,+ timingStatsMin = minX,+ timingStatsMax = maxX,+ timingStatsMinTime = minT,+ timingStatsMaxTime = maxT,+ timingStatsStartTime = t0,+ timingStatsLastTime = t,+ timingStatsSum = sumX,+ timingStatsSum2 = sumX2 }+ where count = 1 + timingStatsCount stats+ minX' = timingStatsMin stats+ maxX' = timingStatsMax stats+ minX = a `seq` min a minX'+ maxX = a `seq` max a maxX'+ minT | a < minX' = t+ | otherwise = timingStatsMinTime stats+ maxT | a > maxX' = t+ | otherwise = timingStatsMaxTime stats+ t0 = timingStatsStartTime stats+ t' = timingStatsLastTime stats+ x = convertToDouble a+ sumX' = timingStatsSum stats+ sumX = sumX' + (t - t') * x+ sumX2' = timingStatsSum2 stats+ sumX2 = sumX2' + (t - t') * x * x+ +timingStatsMeanGeneric :: ConvertableToDouble a => TimingStats a -> Double+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+ | 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+ where ex = timingStatsMeanGeneric stats+ ex2 = timingStatsMean2Generic stats+ +-- | Return the deviation. +timingStatsDeviation :: TimingData a => TimingStats a -> Double+timingStatsDeviation = sqrt . timingStatsVariance++-- | Return the statistics by single timing data.+returnTimingStats :: TimingData a => Double -> a -> TimingStats a+returnTimingStats t a = addTimingStats t a emptyTimingStats++-- | Convert the statistics from integer to double values.+fromIntTimingStats :: TimingStats Int -> TimingStats Double+fromIntTimingStats stats =+ stats { timingStatsMin = fromIntegral $ timingStatsMin stats,+ timingStatsMax = fromIntegral $ timingStatsMax stats }++-- | 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 "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) .+ showString "]"
Simulation/Aivika/Statistics/Accumulator.hs view
@@ -1,44 +1,44 @@- --- | --- Module : Simulation.Aivika.Statistics.Accumulator --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- This small utility module allows accumulating the timing statistics based on 'Signalable' data --- such as the queue size or the number of lost items in the queue. --- - -module Simulation.Aivika.Statistics.Accumulator - (-- * Timing Statistics Accumulator - TimingStatsAccumulator, - newTimingStatsAccumulator, - timingStatsAccumulated) where - -import Simulation.Aivika.Simulation -import Simulation.Aivika.Dynamics -import Simulation.Aivika.Event -import Simulation.Aivika.Ref -import Simulation.Aivika.Statistics -import Simulation.Aivika.Signal - --- | Represents an accumulator for the timing statistics. -newtype TimingStatsAccumulator a = - TimingStatsAccumulator { timingStatsAccumulatedRef :: Ref (TimingStats a) } - --- | Return the accumulated statistics. -timingStatsAccumulated :: TimingStatsAccumulator a -> Event (TimingStats a) -timingStatsAccumulated = readRef . timingStatsAccumulatedRef - --- | Start gathering the timing statistics from the current simulation time. -newTimingStatsAccumulator :: TimingData a => Signalable a -> Event (TimingStatsAccumulator a) -newTimingStatsAccumulator x = - do t0 <- liftDynamics time - a0 <- readSignalable x - r <- liftSimulation $ newRef (returnTimingStats t0 a0) - handleSignal_ (signalableChanged x) $ \a -> - do t <- liftDynamics time - modifyRef r $ addTimingStats t a - return TimingStatsAccumulator { timingStatsAccumulatedRef = r } ++-- |+-- Module : Simulation.Aivika.Statistics.Accumulator+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- This small utility module allows accumulating the timing statistics based on 'Signalable' data+-- such as the queue size or the number of lost items in the queue.+--++module Simulation.Aivika.Statistics.Accumulator+ (-- * Timing Statistics Accumulator+ TimingStatsAccumulator,+ newTimingStatsAccumulator,+ timingStatsAccumulated) where++import Simulation.Aivika.Simulation+import Simulation.Aivika.Dynamics+import Simulation.Aivika.Event+import Simulation.Aivika.Ref+import Simulation.Aivika.Statistics+import Simulation.Aivika.Signal++-- | Represents an accumulator for the timing statistics.+newtype TimingStatsAccumulator a =+ TimingStatsAccumulator { timingStatsAccumulatedRef :: Ref (TimingStats a) }++-- | Return the accumulated statistics.+timingStatsAccumulated :: TimingStatsAccumulator a -> Event (TimingStats a)+timingStatsAccumulated = readRef . timingStatsAccumulatedRef++-- | Start gathering the timing statistics from the current simulation time. +newTimingStatsAccumulator :: TimingData a => Signalable a -> Event (TimingStatsAccumulator a)+newTimingStatsAccumulator x =+ do t0 <- liftDynamics time+ a0 <- readSignalable x+ r <- liftSimulation $ newRef (returnTimingStats t0 a0)+ handleSignal_ (signalableChanged x) $ \a ->+ do t <- liftDynamics time+ modifyRef r $ addTimingStats t a+ return TimingStatsAccumulator { timingStatsAccumulatedRef = r }
Simulation/Aivika/Stream.hs view
@@ -1,534 +1,534 @@- --- | --- Module : Simulation.Aivika.Stream --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.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, - splitStreamQueueing, - splitStreamPrioritising, - -- * Specifying Identifier - streamUsingId, - -- * Prefetching and Delaying Stream - prefetchStream, - delayStream, - -- * Stream Arriving - arrivalStream, - -- * Memoizing, Zipping and Uzipping Stream - memoStream, - zipStreamSeq, - zipStreamParallel, - zip3StreamSeq, - zip3StreamParallel, - unzipStream, - streamSeq, - streamParallel, - -- * Consuming and Sinking Stream - consumeStream, - sinkStream, - -- * Useful Combinators - repeatProcess, - mapStream, - mapStreamM, - apStream, - apStreamM, - filterStream, - filterStreamM, - -- * Integrating with Signals - signalStream, - streamSignal, - -- * Utilities - leftStream, - rightStream, - replaceLeftStream, - replaceRightStream, - partitionEitherStream) where - -import Data.IORef -import Data.Maybe -import Data.Monoid - -import Control.Applicative -import Control.Monad -import Control.Monad.Trans - -import Simulation.Aivika.Simulation -import Simulation.Aivika.Dynamics -import Simulation.Aivika.Event -import Simulation.Aivika.Cont -import Simulation.Aivika.Process -import Simulation.Aivika.Signal -import Simulation.Aivika.Resource -import Simulation.Aivika.QueueStrategy -import Simulation.Aivika.Queue.Infinite -import Simulation.Aivika.Internal.Arrival - --- | 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 = mapStream - -instance Applicative Stream where - - pure a = let y = Cons (return (a, y)) in y - - (<*>) = apStream - -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 f (Cons s) = Cons y where - y = do (a, xs) <- s - return (f a, mapStream f xs) - --- | 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) - --- | Sequential application. -apStream :: Stream (a -> b) -> Stream a -> Stream b -apStream (Cons sf) (Cons sa) = Cons y where - y = do (f, sf') <- sf - (a, sa') <- sa - return (f a, apStream sf' sa') - --- | Sequential application. -apStreamM :: Stream (a -> Process b) -> Stream a -> Stream b -apStreamM (Cons sf) (Cons sa) = Cons y where - y = do (f, sf') <- sf - (a, sa') <- sa - x <- f a - return (x, apStreamM sf' sa') - --- | 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 = splitStreamQueueing 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. -splitStreamQueueing :: EnqueueStrategy s - => 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 -splitStreamQueueing 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 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 - => 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) - conting <- liftSimulation $ newResourceWithMaxCount FCFS 0 (Just 1) - ref <- liftIO $ newIORef Nothing - let writer p = - do (a, xs) <- runStream p - requestResource writing - liftIO $ writeIORef ref (Just a) - releaseResource reading - requestResource conting - writer xs - reader = - do requestResource reading - Just a <- liftIO $ readIORef ref - liftIO $ writeIORef ref Nothing - releaseResource writing - return a - forM_ streams $ spawnProcess CancelTogether . writer - a <- reader - let xs = repeatProcess (releaseResource conting >> reader) - return (a, xs) - --- | Concatenate the input priority streams producing one output stream. -concatPriorityStreams :: PriorityQueueStrategy s 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) - conting <- liftSimulation $ newResourceWithMaxCount FCFS 0 (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 - requestResource conting - writer xs - reader = - do requestResource reading - Just a <- liftIO $ readIORef ref - liftIO $ writeIORef ref Nothing - releaseResource writing - return a - forM_ streams $ spawnProcess CancelTogether . writer - a <- reader - let xs = repeatProcess (releaseResource conting >> reader) - return (a, xs) - --- | 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 - => 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 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 neverProcess - --- | 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 enqueueing data in the queue --- from the input stream. -consumeStream :: (a -> Process ()) -> Stream a -> Process () -consumeStream f = p 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 = p where - p (Cons s) = do (a, xs) <- s - p xs - --- | Prefetch the input stream requesting for one more data item in advance --- while the last received item is not yet fully processed in the chain of --- streams, usually by the processors. --- --- You can think of this as the prefetched stream could place its latest --- data item in some temporary space for later use, which is very useful --- for modeling a sequence of separate and independent work places. -prefetchStream :: Stream a -> Stream a -prefetchStream s = Cons z where - z = do reading <- liftSimulation $ newResourceWithMaxCount FCFS 0 (Just 1) - writing <- liftSimulation $ newResourceWithMaxCount FCFS 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 - spawnProcess CancelTogether $ writer s - runStream $ repeatProcess reader - --- | Return a stream of values triggered by the specified signal. --- --- Since the time at which the values of the stream are requested for may differ from --- the time at which the signal is triggered, it can be useful to apply the 'arrivalSignal' --- function to add the information about the time points at which the signal was --- actually received. --- --- The point is that the 'Stream' is requested outside, while the 'Signal' is triggered --- inside. They are different by nature. The former is passive, while the latter is active. --- --- The resulting stream may be a root of space leak as it uses an internal queue to store --- the values received from the signal. The oldest value is dequeued each time we request --- the stream and it is returned within the computation. --- --- Cancel the stream's process to unsubscribe from the specified signal. -signalStream :: Signal a -> Process (Stream a) -signalStream s = - do q <- liftEvent newFCFSQueue - h <- liftEvent $ - handleSignal s $ - enqueue q - whenCancellingProcess $ disposeEvent h - return $ repeatProcess $ dequeue q - --- | Return a computation of the signal that triggers values from the specified stream, --- each time the next value of the stream is received within the underlying 'Process' --- computation. --- --- Cancel the returned process to stop reading from the specified stream. -streamSignal :: Stream a -> Process (Signal a) -streamSignal z = - do s <- liftSimulation newSignalSource - spawnProcess CancelTogether $ - consumeStream (liftEvent . triggerSignal s) z - return $ publishSignal s - --- | Transform a stream so that the resulting stream returns a sequence of arrivals --- saving the information about the time points at which the original stream items --- were received by demand. -arrivalStream :: Stream a -> Stream (Arrival a) -arrivalStream s = Cons $ loop s Nothing where - loop s t0 = do (a, xs) <- runStream s - t <- liftDynamics time - let b = Arrival { arrivalValue = a, - arrivalTime = t, - arrivalDelay = - case t0 of - Nothing -> Nothing - Just t0 -> Just (t - t0) } - return (b, Cons $ loop xs (Just t)) - --- | Delay the stream by one step using the specified initial value. -delayStream :: a -> Stream a -> Stream a -delayStream a0 s = Cons $ return (a0, s) ++-- |+-- Module : Simulation.Aivika.Stream+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.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,+ splitStreamQueueing,+ splitStreamPrioritising,+ -- * Specifying Identifier+ streamUsingId,+ -- * Prefetching and Delaying Stream+ prefetchStream,+ delayStream,+ -- * Stream Arriving+ arrivalStream,+ -- * Memoizing, Zipping and Uzipping Stream+ memoStream,+ zipStreamSeq,+ zipStreamParallel,+ zip3StreamSeq,+ zip3StreamParallel,+ unzipStream,+ streamSeq,+ streamParallel,+ -- * Consuming and Sinking Stream+ consumeStream,+ sinkStream,+ -- * Useful Combinators+ repeatProcess,+ mapStream,+ mapStreamM,+ apStream,+ apStreamM,+ filterStream,+ filterStreamM,+ -- * Integrating with Signals+ signalStream,+ streamSignal,+ -- * Utilities+ leftStream,+ rightStream,+ replaceLeftStream,+ replaceRightStream,+ partitionEitherStream) where++import Data.IORef+import Data.Maybe+import Data.Monoid++import Control.Applicative+import Control.Monad+import Control.Monad.Trans++import Simulation.Aivika.Simulation+import Simulation.Aivika.Dynamics+import Simulation.Aivika.Event+import Simulation.Aivika.Cont+import Simulation.Aivika.Process+import Simulation.Aivika.Signal+import Simulation.Aivika.Resource+import Simulation.Aivika.QueueStrategy+import Simulation.Aivika.Queue.Infinite+import Simulation.Aivika.Internal.Arrival++-- | 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 = mapStream++instance Applicative Stream where++ pure a = let y = Cons (return (a, y)) in y+ + (<*>) = apStream++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 f (Cons s) = Cons y where+ y = do (a, xs) <- s+ return (f a, mapStream f xs)++-- | 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)++-- | Sequential application.+apStream :: Stream (a -> b) -> Stream a -> Stream b+apStream (Cons sf) (Cons sa) = Cons y where+ y = do (f, sf') <- sf+ (a, sa') <- sa+ return (f a, apStream sf' sa')++-- | Sequential application.+apStreamM :: Stream (a -> Process b) -> Stream a -> Stream b+apStreamM (Cons sf) (Cons sa) = Cons y where+ y = do (f, sf') <- sf+ (a, sa') <- sa+ x <- f a+ return (x, apStreamM sf' sa')++-- | 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 = splitStreamQueueing 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.+splitStreamQueueing :: EnqueueStrategy s+ => 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+splitStreamQueueing 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 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+ => 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)+ conting <- liftSimulation $ newResourceWithMaxCount FCFS 0 (Just 1)+ ref <- liftIO $ newIORef Nothing+ let writer p =+ do (a, xs) <- runStream p+ requestResource writing+ liftIO $ writeIORef ref (Just a)+ releaseResource reading+ requestResource conting+ writer xs+ reader =+ do requestResource reading+ Just a <- liftIO $ readIORef ref+ liftIO $ writeIORef ref Nothing+ releaseResource writing+ return a+ forM_ streams $ spawnProcess . writer+ a <- reader+ let xs = repeatProcess (releaseResource conting >> reader)+ return (a, xs)++-- | Concatenate the input priority streams producing one output stream.+concatPriorityStreams :: PriorityQueueStrategy s 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)+ conting <- liftSimulation $ newResourceWithMaxCount FCFS 0 (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+ requestResource conting+ writer xs+ reader =+ do requestResource reading+ Just a <- liftIO $ readIORef ref+ liftIO $ writeIORef ref Nothing+ releaseResource writing+ return a+ forM_ streams $ spawnProcess . writer+ a <- reader+ let xs = repeatProcess (releaseResource conting >> reader)+ return (a, xs)++-- | 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+ => 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 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 neverProcess++-- | 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 enqueueing data in the queue+-- from the input stream.+consumeStream :: (a -> Process ()) -> Stream a -> Process ()+consumeStream f = p 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 = p where+ p (Cons s) = do (a, xs) <- s+ p xs+ +-- | Prefetch the input stream requesting for one more data item in advance +-- while the last received item is not yet fully processed in the chain of +-- streams, usually by the processors.+--+-- You can think of this as the prefetched stream could place its latest +-- data item in some temporary space for later use, which is very useful +-- for modeling a sequence of separate and independent work places.+prefetchStream :: Stream a -> Stream a+prefetchStream s = Cons z where+ z = do reading <- liftSimulation $ newResourceWithMaxCount FCFS 0 (Just 1)+ writing <- liftSimulation $ newResourceWithMaxCount FCFS 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+ spawnProcess $ writer s+ runStream $ repeatProcess reader++-- | Return a stream of values triggered by the specified signal.+--+-- Since the time at which the values of the stream are requested for may differ from+-- the time at which the signal is triggered, it can be useful to apply the 'arrivalSignal'+-- function to add the information about the time points at which the signal was +-- actually received.+--+-- The point is that the 'Stream' is requested outside, while the 'Signal' is triggered+-- inside. They are different by nature. The former is passive, while the latter is active.+--+-- The resulting stream may be a root of space leak as it uses an internal queue to store+-- the values received from the signal. The oldest value is dequeued each time we request+-- the stream and it is returned within the computation.+--+-- Cancel the stream's process to unsubscribe from the specified signal.+signalStream :: Signal a -> Process (Stream a)+signalStream s =+ do q <- liftEvent newFCFSQueue+ h <- liftEvent $+ handleSignal s $ + enqueue q+ whenCancellingProcess $ disposeEvent h+ return $ repeatProcess $ dequeue q++-- | Return a computation of the signal that triggers values from the specified stream,+-- each time the next value of the stream is received within the underlying 'Process' +-- computation.+--+-- Cancel the returned process to stop reading from the specified stream. +streamSignal :: Stream a -> Process (Signal a)+streamSignal z =+ do s <- liftSimulation newSignalSource+ spawnProcess $+ consumeStream (liftEvent . triggerSignal s) z+ return $ publishSignal s++-- | Transform a stream so that the resulting stream returns a sequence of arrivals+-- saving the information about the time points at which the original stream items +-- were received by demand.+arrivalStream :: Stream a -> Stream (Arrival a)+arrivalStream s = Cons $ loop s Nothing where+ loop s t0 = do (a, xs) <- runStream s+ t <- liftDynamics time+ let b = Arrival { arrivalValue = a,+ arrivalTime = t,+ arrivalDelay =+ case t0 of+ Nothing -> Nothing+ Just t0 -> Just (t - t0) }+ return (b, Cons $ loop xs (Just t))++-- | Delay the stream by one step using the specified initial value.+delayStream :: a -> Stream a -> Stream a+delayStream a0 s = Cons $ return (a0, s)
Simulation/Aivika/Stream/Random.hs view
@@ -1,153 +1,153 @@- --- | --- Module : Simulation.Aivika.Stream.Random --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- This module defines random streams of events, which are useful --- for describing the input of the model. --- - -module Simulation.Aivika.Stream.Random - (-- * Stream of Random Events - randomStream, - randomUniformStream, - randomUniformIntStream, - randomNormalStream, - randomExponentialStream, - randomErlangStream, - randomPoissonStream, - randomBinomialStream) where - -import System.Random - -import Control.Monad -import Control.Monad.Trans - -import Simulation.Aivika.Parameter -import Simulation.Aivika.Parameter.Random -import Simulation.Aivika.Simulation -import Simulation.Aivika.Dynamics -import Simulation.Aivika.Event -import Simulation.Aivika.Process -import Simulation.Aivika.Processor -import Simulation.Aivika.Stream -import Simulation.Aivika.Statistics -import Simulation.Aivika.Ref -import Simulation.Aivika.Arrival - --- | Return a sream of random events that arrive with the specified delay. -randomStream :: Parameter (Double, a) - -- ^ compute a pair of the delay and event of type @a@ - -> Stream (Arrival a) - -- ^ a stream of delayed events -randomStream delay = Cons $ loop Nothing where - loop t0 = - do t1 <- liftDynamics time - case t0 of - Nothing -> return () - Just t0 -> - when (t1 /= t0) $ - error $ - "The time of requesting for a new random event is different from " ++ - "the time when the previous event has arrived. Probably, your model " ++ - "contains a logical error. The random events should be requested permanently. " ++ - "At least, they can be lost, for example, when trying to enqueue them, but " ++ - "the random stream itself must always work: randomStream." - (delay, a) <- liftParameter delay - holdProcess delay - t2 <- liftDynamics time - let arrival = Arrival { arrivalValue = a, - arrivalTime = t2, - arrivalDelay = - case t0 of - Nothing -> Nothing - Just t0 -> Just delay } - return (arrival, Cons $ loop (Just t2)) - --- | Create a new stream with delays distributed uniformly. -randomUniformStream :: Double - -- ^ the minimum delay - -> Double - -- ^ the maximum delay - -> Stream (Arrival Double) - -- ^ the stream of random events with the delays generated -randomUniformStream min max = - randomStream $ - randomUniform min max >>= \x -> - return (x, x) - --- | Create a new stream with integer delays distributed uniformly. -randomUniformIntStream :: Int - -- ^ the minimum delay - -> Int - -- ^ the maximum delay - -> Stream (Arrival Int) - -- ^ the stream of random events with the delays generated -randomUniformIntStream min max = - randomStream $ - randomUniformInt min max >>= \x -> - return (fromIntegral x, x) - --- | Create a new stream with delays distributed normally. -randomNormalStream :: Double - -- ^ the mean delay - -> Double - -- ^ the delay deviation - -> Stream (Arrival Double) - -- ^ the stream of random events with the delays generated -randomNormalStream mu nu = - randomStream $ - randomNormal mu nu >>= \x -> - return (x, x) - --- | 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 (Arrival Double) - -- ^ the stream of random events with the delays generated -randomExponentialStream mu = - randomStream $ - randomExponential mu >>= \x -> - return (x, x) - --- | 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 (Arrival Double) - -- ^ the stream of random events with the delays generated -randomErlangStream beta m = - randomStream $ - randomErlang beta m >>= \x -> - return (x, x) - --- | Return a new stream with delays having the Poisson distribution with --- the specified mean. -randomPoissonStream :: Double - -- ^ the mean delay - -> Stream (Arrival Int) - -- ^ the stream of random events with the delays generated -randomPoissonStream mu = - randomStream $ - randomPoisson mu >>= \x -> - return (fromIntegral x, x) - --- | 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 (Arrival Int) - -- ^ the stream of random events with the delays generated -randomBinomialStream prob trials = - randomStream $ - randomBinomial prob trials >>= \x -> - return (fromIntegral x, x) ++-- |+-- Module : Simulation.Aivika.Stream.Random+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- This module defines random streams of events, which are useful+-- for describing the input of the model.+--++module Simulation.Aivika.Stream.Random+ (-- * Stream of Random Events+ randomStream,+ randomUniformStream,+ randomUniformIntStream,+ randomNormalStream,+ randomExponentialStream,+ randomErlangStream,+ randomPoissonStream,+ randomBinomialStream) where++import System.Random++import Control.Monad+import Control.Monad.Trans++import Simulation.Aivika.Parameter+import Simulation.Aivika.Parameter.Random+import Simulation.Aivika.Simulation+import Simulation.Aivika.Dynamics+import Simulation.Aivika.Event+import Simulation.Aivika.Process+import Simulation.Aivika.Processor+import Simulation.Aivika.Stream+import Simulation.Aivika.Statistics+import Simulation.Aivika.Ref+import Simulation.Aivika.Arrival++-- | Return a sream of random events that arrive with the specified delay.+randomStream :: Parameter (Double, a)+ -- ^ compute a pair of the delay and event of type @a@+ -> Stream (Arrival a)+ -- ^ a stream of delayed events+randomStream delay = Cons $ loop Nothing where+ loop t0 =+ do t1 <- liftDynamics time+ case t0 of+ Nothing -> return ()+ Just t0 ->+ when (t1 /= t0) $+ error $+ "The time of requesting for a new random event is different from " +++ "the time when the previous event has arrived. Probably, your model " +++ "contains a logical error. The random events should be requested permanently. " +++ "At least, they can be lost, for example, when trying to enqueue them, but " +++ "the random stream itself must always work: randomStream."+ (delay, a) <- liftParameter delay+ holdProcess delay+ t2 <- liftDynamics time+ let arrival = Arrival { arrivalValue = a,+ arrivalTime = t2,+ arrivalDelay =+ case t0 of+ Nothing -> Nothing+ Just t0 -> Just delay }+ return (arrival, Cons $ loop (Just t2))++-- | Create a new stream with delays distributed uniformly.+randomUniformStream :: Double+ -- ^ the minimum delay+ -> Double+ -- ^ the maximum delay+ -> Stream (Arrival Double)+ -- ^ the stream of random events with the delays generated+randomUniformStream min max =+ randomStream $+ randomUniform min max >>= \x ->+ return (x, x)++-- | Create a new stream with integer delays distributed uniformly.+randomUniformIntStream :: Int+ -- ^ the minimum delay+ -> Int+ -- ^ the maximum delay+ -> Stream (Arrival Int)+ -- ^ the stream of random events with the delays generated+randomUniformIntStream min max =+ randomStream $+ randomUniformInt min max >>= \x ->+ return (fromIntegral x, x)++-- | Create a new stream with delays distributed normally.+randomNormalStream :: Double+ -- ^ the mean delay+ -> Double+ -- ^ the delay deviation+ -> Stream (Arrival Double)+ -- ^ the stream of random events with the delays generated+randomNormalStream mu nu =+ randomStream $+ randomNormal mu nu >>= \x ->+ return (x, x)+ +-- | 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 (Arrival Double)+ -- ^ the stream of random events with the delays generated+randomExponentialStream mu =+ randomStream $+ randomExponential mu >>= \x ->+ return (x, x)+ +-- | 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 (Arrival Double)+ -- ^ the stream of random events with the delays generated+randomErlangStream beta m =+ randomStream $+ randomErlang beta m >>= \x ->+ return (x, x)++-- | Return a new stream with delays having the Poisson distribution with+-- the specified mean.+randomPoissonStream :: Double+ -- ^ the mean delay+ -> Stream (Arrival Int)+ -- ^ the stream of random events with the delays generated+randomPoissonStream mu =+ randomStream $+ randomPoisson mu >>= \x ->+ return (fromIntegral x, x)++-- | 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 (Arrival Int)+ -- ^ the stream of random events with the delays generated+randomBinomialStream prob trials =+ randomStream $+ randomBinomial prob trials >>= \x ->+ return (fromIntegral x, x)
Simulation/Aivika/SystemDynamics.hs view
@@ -1,708 +1,777 @@- -{-# LANGUAGE BangPatterns, RecursiveDo #-} - --- | --- Module : Simulation.Aivika.SystemDynamics --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- This module defines integrals and other functions of System Dynamics. --- - -module Simulation.Aivika.SystemDynamics - (-- * Equality and Ordering - (.==.), - (./=.), - (.<.), - (.>=.), - (.>.), - (.<=.), - maxDynamics, - minDynamics, - ifDynamics, - -- * Ordinary Differential Equations - integ, - smoothI, - smooth, - smooth3I, - smooth3, - smoothNI, - smoothN, - delay1I, - delay1, - delay3I, - delay3, - delayNI, - delayN, - forecast, - trend, - -- * Difference Equations - diffsum, - -- * Table Functions - lookupDynamics, - lookupStepwiseDynamics, - -- * Discrete Functions - delay, - delayI, - step, - pulse, - pulseP, - ramp, - -- * Financial Functions - npv, - npve) where - -import Data.Array -import Data.Array.IO.Safe -import Data.IORef -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.Dynamics -import Simulation.Aivika.Dynamics.Extra -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 --- - --- | Compare for equality. -(.==.) :: (Eq a) => Dynamics a -> Dynamics a -> Dynamics Bool -(.==.) = liftM2 (==) - --- | Compare for inequality. -(./=.) :: (Eq a) => Dynamics a -> Dynamics a -> Dynamics Bool -(./=.) = liftM2 (/=) - --- | Compare for ordering. -(.<.) :: (Ord a) => Dynamics a -> Dynamics a -> Dynamics Bool -(.<.) = liftM2 (<) - --- | Compare for ordering. -(.>=.) :: (Ord a) => Dynamics a -> Dynamics a -> Dynamics Bool -(.>=.) = liftM2 (>=) - --- | Compare for ordering. -(.>.) :: (Ord a) => Dynamics a -> Dynamics a -> Dynamics Bool -(.>.) = liftM2 (>) - --- | Compare for ordering. -(.<=.) :: (Ord a) => Dynamics a -> Dynamics a -> Dynamics Bool -(.<=.) = liftM2 (<=) - --- | Return the maximum. -maxDynamics :: (Ord a) => Dynamics a -> Dynamics a -> Dynamics a -maxDynamics = liftM2 max - --- | Return the minimum. -minDynamics :: (Ord a) => Dynamics a -> Dynamics a -> Dynamics a -minDynamics = liftM2 min - --- | Implement the if-then-else operator. -ifDynamics :: Dynamics Bool -> Dynamics a -> Dynamics a -> Dynamics a -ifDynamics cond x y = - do a <- cond - if a then x else y - --- --- Ordinary Differential Equations --- - -integEuler :: Dynamics Double - -> Dynamics Double - -> Dynamics Double - -> Point -> IO Double -integEuler (Dynamics f) (Dynamics i) (Dynamics y) p = - case pointIteration p of - 0 -> - i p - n -> do - let sc = pointSpecs p - ty = basicTime sc (n - 1) 0 - py = p { pointTime = ty, pointIteration = n - 1, pointPhase = 0 } - a <- y py - b <- f py - let !v = a + spcDT (pointSpecs p) * b - return v - -integRK2 :: Dynamics Double - -> Dynamics Double - -> Dynamics Double - -> Point -> IO Double -integRK2 (Dynamics f) (Dynamics i) (Dynamics y) p = - case pointPhase p of - 0 -> case pointIteration p of - 0 -> - i p - n -> do - let sc = pointSpecs p - ty = basicTime sc (n - 1) 0 - t1 = ty - t2 = basicTime sc (n - 1) 1 - py = p { pointTime = ty, pointIteration = n - 1, pointPhase = 0 } - p1 = py - p2 = p { pointTime = t2, pointIteration = n - 1, pointPhase = 1 } - vy <- y py - k1 <- f p1 - k2 <- f p2 - let !v = vy + spcDT sc / 2.0 * (k1 + k2) - return v - 1 -> do - let sc = pointSpecs p - n = pointIteration p - ty = basicTime sc n 0 - t1 = ty - py = p { pointTime = ty, pointIteration = n, pointPhase = 0 } - p1 = py - vy <- y py - k1 <- f p1 - let !v = vy + spcDT sc * k1 - return v - _ -> - error "Incorrect phase: integRK2" - -integRK4 :: Dynamics Double - -> Dynamics Double - -> Dynamics Double - -> Point -> IO Double -integRK4 (Dynamics f) (Dynamics i) (Dynamics y) p = - case pointPhase p of - 0 -> case pointIteration p of - 0 -> - i p - n -> do - let sc = pointSpecs p - ty = basicTime sc (n - 1) 0 - t1 = ty - t2 = basicTime sc (n - 1) 1 - t3 = basicTime sc (n - 1) 2 - t4 = basicTime sc (n - 1) 3 - py = p { pointTime = ty, pointIteration = n - 1, pointPhase = 0 } - p1 = py - p2 = p { pointTime = t2, pointIteration = n - 1, pointPhase = 1 } - p3 = p { pointTime = t3, pointIteration = n - 1, pointPhase = 2 } - p4 = p { pointTime = t4, pointIteration = n - 1, pointPhase = 3 } - vy <- y py - k1 <- f p1 - k2 <- f p2 - k3 <- f p3 - k4 <- f p4 - let !v = vy + spcDT sc / 6.0 * (k1 + 2.0 * k2 + 2.0 * k3 + k4) - return v - 1 -> do - let sc = pointSpecs p - n = pointIteration p - ty = basicTime sc n 0 - t1 = ty - py = p { pointTime = ty, pointIteration = n, pointPhase = 0 } - p1 = py - vy <- y py - k1 <- f p1 - let !v = vy + spcDT sc / 2.0 * k1 - return v - 2 -> do - let sc = pointSpecs p - n = pointIteration p - ty = basicTime sc n 0 - t2 = basicTime sc n 1 - py = p { pointTime = ty, pointIteration = n, pointPhase = 0 } - p2 = p { pointTime = t2, pointIteration = n, pointPhase = 1 } - vy <- y py - k2 <- f p2 - let !v = vy + spcDT sc / 2.0 * k2 - return v - 3 -> do - let sc = pointSpecs p - n = pointIteration p - ty = basicTime sc n 0 - t3 = basicTime sc n 2 - py = p { pointTime = ty, pointIteration = n, pointPhase = 0 } - p3 = p { pointTime = t3, pointIteration = n, pointPhase = 2 } - vy <- y py - k3 <- f p3 - let !v = vy + spcDT sc * k3 - return v - _ -> - error "Incorrect phase: integRK4" - --- | Return an integral with the specified derivative and initial value. --- --- To create a loopback, you should use the recursive do-notation. --- It allows defining the differential equations unordered as --- in mathematics: --- --- @ --- model :: Simulation [Double] --- model = --- mdo a <- integ (- ka * a) 100 --- b <- integ (ka * a - kb * b) 0 --- c <- integ (kb * b) 0 --- let ka = 1 --- kb = 1 --- runDynamicsInStopTime $ sequence [a, b, c] --- @ -integ :: Dynamics Double -- ^ the derivative - -> Dynamics Double -- ^ the initial value - -> Simulation (Dynamics Double) -- ^ the integral -integ diff i = - mdo y <- MU.memoDynamics z - z <- Simulation $ \r -> - case spcMethod (runSpecs r) of - Euler -> return $ Dynamics $ integEuler diff i y - RungeKutta2 -> return $ Dynamics $ integRK2 diff i y - RungeKutta4 -> return $ Dynamics $ integRK4 diff i y - return y - --- | Return the first order exponential smooth. --- --- To create a loopback, you should use the recursive do-notation --- with help of which the function itself is defined: --- --- @ --- smoothI x t i = --- mdo y <- integ ((x - y) \/ t) i --- return y --- @ -smoothI :: Dynamics Double -- ^ the value to smooth over time - -> Dynamics Double -- ^ time - -> Dynamics Double -- ^ the initial value - -> Simulation (Dynamics Double) -- ^ the first order exponential smooth -smoothI x t i = - mdo y <- integ ((x - y) / t) i - return y - --- | Return the first order exponential smooth. --- --- This is a simplified version of the 'smoothI' function --- without specifing the initial value. -smooth :: Dynamics Double -- ^ the value to smooth over time - -> Dynamics Double -- ^ time - -> Simulation (Dynamics Double) -- ^ the first order exponential smooth -smooth x t = smoothI x t x - --- | Return the third order exponential smooth. --- --- To create a loopback, you should use the recursive do-notation --- with help of which the function itself is defined: --- --- @ --- smooth3I x t i = --- mdo y <- integ ((s2 - y) \/ t') i --- s2 <- integ ((s1 - s2) \/ t') i --- s1 <- integ ((x - s1) \/ t') i --- let t' = t \/ 3.0 --- return y --- @ -smooth3I :: Dynamics Double -- ^ the value to smooth over time - -> Dynamics Double -- ^ time - -> Dynamics Double -- ^ the initial value - -> Simulation (Dynamics Double) -- ^ the third order exponential smooth -smooth3I x t i = - mdo y <- integ ((s2 - y) / t') i - s2 <- integ ((s1 - s2) / t') i - s1 <- integ ((x - s1) / t') i - let t' = t / 3.0 - return y - --- | Return the third order exponential smooth. --- --- This is a simplified version of the 'smooth3I' function --- without specifying the initial value. -smooth3 :: Dynamics Double -- ^ the value to smooth over time - -> Dynamics Double -- ^ time - -> Simulation (Dynamics Double) -- ^ the third order exponential smooth -smooth3 x t = smooth3I x t x - --- | Return the n'th order exponential smooth. --- --- The result is not discrete in that sense that it may change within the integration time --- interval depending on the integration method used. Probably, you should apply --- the 'discreteDynamics' function to the result if you want to achieve an effect when --- the value is not changed within the time interval, which is used sometimes. -smoothNI :: Dynamics Double -- ^ the value to smooth over time - -> Dynamics Double -- ^ time - -> Int -- ^ the order - -> Dynamics Double -- ^ the initial value - -> Simulation (Dynamics Double) -- ^ the n'th order exponential smooth -smoothNI x t n i = - mdo s <- forM [1 .. n] $ \k -> - if k == 1 - then integ ((x - a ! 1) / t') i - else integ ((a ! (k - 1) - a ! k) / t') i - let a = listArray (1, n) s - t' = t / fromIntegral n - return $ a ! n - --- | Return the n'th order exponential smooth. --- --- This is a simplified version of the 'smoothNI' function --- without specifying the initial value. -smoothN :: Dynamics Double -- ^ the value to smooth over time - -> Dynamics Double -- ^ time - -> Int -- ^ the order - -> Simulation (Dynamics Double) -- ^ the n'th order exponential smooth -smoothN x t n = smoothNI x t n x - --- | Return the first order exponential delay. --- --- To create a loopback, you should use the recursive do-notation --- with help of which the function itself is defined: --- --- @ --- delay1I x t i = --- mdo y <- integ (x - y \/ t) (i * t) --- return $ y \/ t --- @ -delay1I :: Dynamics Double -- ^ the value to conserve - -> Dynamics Double -- ^ time - -> Dynamics Double -- ^ the initial value - -> Simulation (Dynamics Double) -- ^ the first order exponential delay -delay1I x t i = - mdo y <- integ (x - y / t) (i * t) - return $ y / t - --- | Return the first order exponential delay. --- --- This is a simplified version of the 'delay1I' function --- without specifying the initial value. -delay1 :: Dynamics Double -- ^ the value to conserve - -> Dynamics Double -- ^ time - -> Simulation (Dynamics Double) -- ^ the first order exponential delay -delay1 x t = delay1I x t x - --- | Return the third order exponential delay. -delay3I :: Dynamics Double -- ^ the value to conserve - -> Dynamics Double -- ^ time - -> Dynamics Double -- ^ the initial value - -> Simulation (Dynamics Double) -- ^ the third order exponential delay -delay3I x t i = - mdo y <- integ (s2 / t' - y / t') (i * t') - s2 <- integ (s1 / t' - s2 / t') (i * t') - s1 <- integ (x - s1 / t') (i * t') - let t' = t / 3.0 - return $ y / t' - --- | Return the third order exponential delay. --- --- This is a simplified version of the 'delay3I' function --- without specifying the initial value. -delay3 :: Dynamics Double -- ^ the value to conserve - -> Dynamics Double -- ^ time - -> Simulation (Dynamics Double) -- ^ the third order exponential delay -delay3 x t = delay3I x t x - --- | Return the n'th order exponential delay. -delayNI :: Dynamics Double -- ^ the value to conserve - -> Dynamics Double -- ^ time - -> Int -- ^ the order - -> Dynamics Double -- ^ the initial value - -> Simulation (Dynamics Double) -- ^ the n'th order exponential delay -delayNI x t n i = - mdo s <- forM [1 .. n] $ \k -> - if k == 1 - then integ (x - (a ! 1) / t') (i * t') - else integ ((a ! (k - 1)) / t' - (a ! k) / t') (i * t') - let a = listArray (1, n) s - t' = t / fromIntegral n - return $ (a ! n) / t' - --- | Return the n'th order exponential delay. --- --- This is a simplified version of the 'delayNI' function --- without specifying the initial value. -delayN :: Dynamics Double -- ^ the value to conserve - -> Dynamics Double -- ^ time - -> Int -- ^ the order - -> Simulation (Dynamics Double) -- ^ the n'th order exponential delay -delayN x t n = delayNI x t n x - --- | Return the forecast. --- --- The function has the following definition: --- --- @ --- forecast x at hz = --- do y <- smooth x at --- return $ x * (1.0 + (x \/ y - 1.0) \/ at * hz) --- @ -forecast :: Dynamics Double -- ^ the value to forecast - -> Dynamics Double -- ^ the average time - -> Dynamics Double -- ^ the time horizon - -> Simulation (Dynamics Double) -- ^ the forecast -forecast x at hz = - do y <- smooth x at - return $ x * (1.0 + (x / y - 1.0) / at * hz) - --- | Return the trend. --- --- The function has the following definition: --- --- @ --- trend x at i = --- do y <- smoothI x at (x \/ (1.0 + i * at)) --- return $ (x \/ y - 1.0) \/ at --- @ -trend :: Dynamics Double -- ^ the value for which the trend is calculated - -> Dynamics Double -- ^ the average time - -> Dynamics Double -- ^ the initial value - -> Simulation (Dynamics Double) -- ^ the fractional change rate -trend x at i = - do y <- smoothI x at (x / (1.0 + i * at)) - return $ (x / y - 1.0) / at - --- --- Difference Equations --- - --- | Retun the sum for the difference equation. --- It is like an integral returned by the 'integ' function, only now --- the difference is used instead of derivative. --- --- As usual, to create a loopback, you should use the recursive do-notation. -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 - let Dynamics m = y - sc = pointSpecs p - ty = basicTime sc (n - 1) 0 - py = p { pointTime = ty, - pointIteration = n - 1, - pointPhase = 0 } - a <- m py - b <- diff py - let !v = a + b - return v - return y - --- --- Table Functions --- - --- | 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 $ 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 m) tbl = - Dynamics $ \p -> - do a <- m p - return $ tableLookupStepwise a tbl - --- --- Discrete Functions --- - --- | Return the delayed value using the specified lag time. -delay :: Dynamics a -- ^ the value to delay - -> Dynamics Double -- ^ the lag time - -> Dynamics a -- ^ the delayed value -delay (Dynamics x) (Dynamics d) = discreteDynamics $ 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 = 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 } - | 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 - --- --- Financial Functions --- - --- | Return the Net Present Value (NPV) of the stream computed using the specified --- discount rate, the initial value and some factor (usually 1). --- --- It is defined in the following way: --- --- @ --- npv stream rate init factor = --- mdo let dt' = liftParameter dt --- df <- integ (- df * rate) 1 --- accum <- integ (stream * df) init --- return $ (accum + dt' * stream * df) * factor --- @ -npv :: Dynamics Double -- ^ the stream - -> Dynamics Double -- ^ the discount rate - -> Dynamics Double -- ^ the initial value - -> Dynamics Double -- ^ factor - -> Simulation (Dynamics Double) -- ^ the Net Present Value (NPV) -npv stream rate init factor = - mdo let dt' = liftParameter dt - df <- integ (- df * rate) 1 - accum <- integ (stream * df) init - 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. --- --- It is defined in the following way: --- --- @ --- npve stream rate init factor = --- 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 --- @ -npve :: Dynamics Double -- ^ the stream - -> Dynamics Double -- ^ the discount rate - -> Dynamics Double -- ^ the initial value - -> Dynamics Double -- ^ factor - -> Simulation (Dynamics Double) -- ^ the Net Present Value End (NPVE) -npve stream rate init factor = - 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 - --- | 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 - ++{-# LANGUAGE BangPatterns, RecursiveDo #-}++-- |+-- Module : Simulation.Aivika.SystemDynamics+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- This module defines integrals and other functions of System Dynamics.+--++module Simulation.Aivika.SystemDynamics+ (-- * Equality and Ordering+ (.==.),+ (./=.),+ (.<.),+ (.>=.),+ (.>.),+ (.<=.),+ maxDynamics,+ minDynamics,+ ifDynamics,+ -- * Ordinary Differential Equations+ integ,+ integEither,+ smoothI,+ smooth,+ smooth3I,+ smooth3,+ smoothNI,+ smoothN,+ delay1I,+ delay1,+ delay3I,+ delay3,+ delayNI,+ delayN,+ forecast,+ trend,+ -- * Difference Equations+ diffsum,+ diffsumEither,+ -- * Table Functions+ lookupDynamics,+ lookupStepwiseDynamics,+ -- * Discrete Functions+ delay,+ delayI,+ step,+ pulse,+ pulseP,+ ramp,+ -- * Financial Functions+ npv,+ npve) where++import Data.Array+import Data.Array.IO.Safe+import Data.IORef+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.Dynamics+import Simulation.Aivika.Dynamics.Extra+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+--++-- | Compare for equality.+(.==.) :: (Eq a) => Dynamics a -> Dynamics a -> Dynamics Bool+(.==.) = liftM2 (==)++-- | Compare for inequality.+(./=.) :: (Eq a) => Dynamics a -> Dynamics a -> Dynamics Bool+(./=.) = liftM2 (/=)++-- | Compare for ordering.+(.<.) :: (Ord a) => Dynamics a -> Dynamics a -> Dynamics Bool+(.<.) = liftM2 (<)++-- | Compare for ordering.+(.>=.) :: (Ord a) => Dynamics a -> Dynamics a -> Dynamics Bool+(.>=.) = liftM2 (>=)++-- | Compare for ordering.+(.>.) :: (Ord a) => Dynamics a -> Dynamics a -> Dynamics Bool+(.>.) = liftM2 (>)++-- | Compare for ordering.+(.<=.) :: (Ord a) => Dynamics a -> Dynamics a -> Dynamics Bool+(.<=.) = liftM2 (<=)++-- | Return the maximum.+maxDynamics :: (Ord a) => Dynamics a -> Dynamics a -> Dynamics a+maxDynamics = liftM2 max++-- | Return the minimum.+minDynamics :: (Ord a) => Dynamics a -> Dynamics a -> Dynamics a+minDynamics = liftM2 min++-- | Implement the if-then-else operator.+ifDynamics :: Dynamics Bool -> Dynamics a -> Dynamics a -> Dynamics a+ifDynamics cond x y =+ do a <- cond+ if a then x else y++--+-- Ordinary Differential Equations+--++integEuler :: Dynamics Double+ -> Dynamics Double + -> Dynamics Double + -> Point -> IO Double+integEuler (Dynamics f) (Dynamics i) (Dynamics y) p = + case pointIteration p of+ 0 -> + i p+ n -> do + let sc = pointSpecs p+ ty = basicTime sc (n - 1) 0+ py = p { pointTime = ty, pointIteration = n - 1, pointPhase = 0 }+ a <- y py+ b <- f py+ let !v = a + spcDT (pointSpecs p) * b+ return v++integRK2 :: Dynamics Double+ -> Dynamics Double+ -> Dynamics Double+ -> Point -> IO Double+integRK2 (Dynamics f) (Dynamics i) (Dynamics y) p =+ case pointPhase p of+ 0 -> case pointIteration p of+ 0 ->+ i p+ n -> do+ let sc = pointSpecs p+ ty = basicTime sc (n - 1) 0+ t1 = ty+ t2 = basicTime sc (n - 1) 1+ py = p { pointTime = ty, pointIteration = n - 1, pointPhase = 0 }+ p1 = py+ p2 = p { pointTime = t2, pointIteration = n - 1, pointPhase = 1 }+ vy <- y py+ k1 <- f p1+ k2 <- f p2+ let !v = vy + spcDT sc / 2.0 * (k1 + k2)+ return v+ 1 -> do+ let sc = pointSpecs p+ n = pointIteration p+ ty = basicTime sc n 0+ t1 = ty+ py = p { pointTime = ty, pointIteration = n, pointPhase = 0 }+ p1 = py+ vy <- y py+ k1 <- f p1+ let !v = vy + spcDT sc * k1+ return v+ _ -> + error "Incorrect phase: integRK2"++integRK4 :: Dynamics Double+ -> Dynamics Double+ -> Dynamics Double+ -> Point -> IO Double+integRK4 (Dynamics f) (Dynamics i) (Dynamics y) p =+ case pointPhase p of+ 0 -> case pointIteration p of+ 0 -> + i p+ n -> do+ let sc = pointSpecs p+ ty = basicTime sc (n - 1) 0+ t1 = ty+ t2 = basicTime sc (n - 1) 1+ t3 = basicTime sc (n - 1) 2+ t4 = basicTime sc (n - 1) 3+ py = p { pointTime = ty, pointIteration = n - 1, pointPhase = 0 }+ p1 = py+ p2 = p { pointTime = t2, pointIteration = n - 1, pointPhase = 1 }+ p3 = p { pointTime = t3, pointIteration = n - 1, pointPhase = 2 }+ p4 = p { pointTime = t4, pointIteration = n - 1, pointPhase = 3 }+ vy <- y py+ k1 <- f p1+ k2 <- f p2+ k3 <- f p3+ k4 <- f p4+ let !v = vy + spcDT sc / 6.0 * (k1 + 2.0 * k2 + 2.0 * k3 + k4)+ return v+ 1 -> do+ let sc = pointSpecs p+ n = pointIteration p+ ty = basicTime sc n 0+ t1 = ty+ py = p { pointTime = ty, pointIteration = n, pointPhase = 0 }+ p1 = py+ vy <- y py+ k1 <- f p1+ let !v = vy + spcDT sc / 2.0 * k1+ return v+ 2 -> do+ let sc = pointSpecs p+ n = pointIteration p+ ty = basicTime sc n 0+ t2 = basicTime sc n 1+ py = p { pointTime = ty, pointIteration = n, pointPhase = 0 }+ p2 = p { pointTime = t2, pointIteration = n, pointPhase = 1 }+ vy <- y py+ k2 <- f p2+ let !v = vy + spcDT sc / 2.0 * k2+ return v+ 3 -> do+ let sc = pointSpecs p+ n = pointIteration p+ ty = basicTime sc n 0+ t3 = basicTime sc n 2+ py = p { pointTime = ty, pointIteration = n, pointPhase = 0 }+ p3 = p { pointTime = t3, pointIteration = n, pointPhase = 2 }+ vy <- y py+ k3 <- f p3+ let !v = vy + spcDT sc * k3+ return v+ _ -> + error "Incorrect phase: integRK4"++-- | Return an integral with the specified derivative and initial value.+--+-- To create a loopback, you should use the recursive do-notation.+-- It allows defining the differential equations unordered as+-- in mathematics:+--+-- @+-- model :: Simulation [Double]+-- model = +-- mdo a <- integ (- ka * a) 100+-- b <- integ (ka * a - kb * b) 0+-- c <- integ (kb * b) 0+-- let ka = 1+-- kb = 1+-- runDynamicsInStopTime $ sequence [a, b, c]+-- @+integ :: Dynamics Double -- ^ the derivative+ -> Dynamics Double -- ^ the initial value+ -> Simulation (Dynamics Double) -- ^ the integral+integ diff i =+ mdo y <- MU.memoDynamics z+ z <- Simulation $ \r ->+ case spcMethod (runSpecs r) of+ Euler -> return $ Dynamics $ integEuler diff i y+ RungeKutta2 -> return $ Dynamics $ integRK2 diff i y+ RungeKutta4 -> return $ Dynamics $ integRK4 diff i y+ return y++integEulerEither :: Dynamics (Either Double Double)+ -> Dynamics Double + -> Dynamics Double + -> Point -> IO Double+integEulerEither (Dynamics f) (Dynamics i) (Dynamics y) p = + case pointIteration p of+ 0 -> + i p+ n -> do + let sc = pointSpecs p+ ty = basicTime sc (n - 1) 0+ py = p { pointTime = ty, pointIteration = n - 1, pointPhase = 0 }+ b <- f py+ case b of+ Left v ->+ return v+ Right b -> do+ a <- y py+ let !v = a + spcDT (pointSpecs p) * b+ return v++-- | Like 'integ' but allows either setting a new 'Left' integral value,+-- or integrating using the 'Right' derivative directly within computation.+--+-- This function always uses Euler's method.+integEither :: Dynamics (Either Double Double)+ -- ^ either set a new 'Left' integral value, or use a 'Right' derivative+ -> Dynamics Double+ -- ^ the initial value+ -> Simulation (Dynamics Double)+integEither diff i =+ mdo y <- MU.memoDynamics z+ z <- Simulation $ \r ->+ return $ Dynamics $ integEulerEither diff i y+ return y++-- | Return the first order exponential smooth.+--+-- To create a loopback, you should use the recursive do-notation+-- with help of which the function itself is defined:+--+-- @+-- smoothI x t i =+-- mdo y <- integ ((x - y) \/ t) i+-- return y+-- @ +smoothI :: Dynamics Double -- ^ the value to smooth over time+ -> Dynamics Double -- ^ time+ -> Dynamics Double -- ^ the initial value+ -> Simulation (Dynamics Double) -- ^ the first order exponential smooth+smoothI x t i =+ mdo y <- integ ((x - y) / t) i+ return y++-- | Return the first order exponential smooth.+--+-- This is a simplified version of the 'smoothI' function+-- without specifing the initial value.+smooth :: Dynamics Double -- ^ the value to smooth over time+ -> Dynamics Double -- ^ time+ -> Simulation (Dynamics Double) -- ^ the first order exponential smooth+smooth x t = smoothI x t x++-- | Return the third order exponential smooth.+--+-- To create a loopback, you should use the recursive do-notation+-- with help of which the function itself is defined:+--+-- @+-- smooth3I x t i =+-- mdo y <- integ ((s2 - y) \/ t') i+-- s2 <- integ ((s1 - s2) \/ t') i+-- s1 <- integ ((x - s1) \/ t') i+-- let t' = t \/ 3.0+-- return y+-- @ +smooth3I :: Dynamics Double -- ^ the value to smooth over time+ -> Dynamics Double -- ^ time+ -> Dynamics Double -- ^ the initial value+ -> Simulation (Dynamics Double) -- ^ the third order exponential smooth+smooth3I x t i =+ mdo y <- integ ((s2 - y) / t') i+ s2 <- integ ((s1 - s2) / t') i+ s1 <- integ ((x - s1) / t') i+ let t' = t / 3.0+ return y++-- | Return the third order exponential smooth.+-- +-- This is a simplified version of the 'smooth3I' function+-- without specifying the initial value.+smooth3 :: Dynamics Double -- ^ the value to smooth over time+ -> Dynamics Double -- ^ time+ -> Simulation (Dynamics Double) -- ^ the third order exponential smooth+smooth3 x t = smooth3I x t x++-- | Return the n'th order exponential smooth.+--+-- The result is not discrete in that sense that it may change within the integration time+-- interval depending on the integration method used. Probably, you should apply+-- the 'discreteDynamics' function to the result if you want to achieve an effect when+-- the value is not changed within the time interval, which is used sometimes.+smoothNI :: Dynamics Double -- ^ the value to smooth over time+ -> Dynamics Double -- ^ time+ -> Int -- ^ the order+ -> Dynamics Double -- ^ the initial value+ -> Simulation (Dynamics Double) -- ^ the n'th order exponential smooth+smoothNI x t n i =+ mdo s <- forM [1 .. n] $ \k ->+ if k == 1+ then integ ((x - a ! 1) / t') i+ else integ ((a ! (k - 1) - a ! k) / t') i+ let a = listArray (1, n) s + t' = t / fromIntegral n+ return $ a ! n++-- | Return the n'th order exponential smooth.+--+-- This is a simplified version of the 'smoothNI' function+-- without specifying the initial value.+smoothN :: Dynamics Double -- ^ the value to smooth over time+ -> Dynamics Double -- ^ time+ -> Int -- ^ the order+ -> Simulation (Dynamics Double) -- ^ the n'th order exponential smooth+smoothN x t n = smoothNI x t n x++-- | Return the first order exponential delay.+--+-- To create a loopback, you should use the recursive do-notation+-- with help of which the function itself is defined:+--+-- @+-- delay1I x t i =+-- mdo y <- integ (x - y \/ t) (i * t)+-- return $ y \/ t+-- @ +delay1I :: Dynamics Double -- ^ the value to conserve+ -> Dynamics Double -- ^ time+ -> Dynamics Double -- ^ the initial value+ -> Simulation (Dynamics Double) -- ^ the first order exponential delay+delay1I x t i =+ mdo y <- integ (x - y / t) (i * t)+ return $ y / t++-- | Return the first order exponential delay.+--+-- This is a simplified version of the 'delay1I' function+-- without specifying the initial value.+delay1 :: Dynamics Double -- ^ the value to conserve+ -> Dynamics Double -- ^ time+ -> Simulation (Dynamics Double) -- ^ the first order exponential delay+delay1 x t = delay1I x t x++-- | Return the third order exponential delay.+delay3I :: Dynamics Double -- ^ the value to conserve+ -> Dynamics Double -- ^ time+ -> Dynamics Double -- ^ the initial value+ -> Simulation (Dynamics Double) -- ^ the third order exponential delay+delay3I x t i =+ mdo y <- integ (s2 / t' - y / t') (i * t')+ s2 <- integ (s1 / t' - s2 / t') (i * t')+ s1 <- integ (x - s1 / t') (i * t')+ let t' = t / 3.0+ return $ y / t' ++-- | Return the third order exponential delay.+--+-- This is a simplified version of the 'delay3I' function+-- without specifying the initial value.+delay3 :: Dynamics Double -- ^ the value to conserve+ -> Dynamics Double -- ^ time+ -> Simulation (Dynamics Double) -- ^ the third order exponential delay+delay3 x t = delay3I x t x++-- | Return the n'th order exponential delay.+delayNI :: Dynamics Double -- ^ the value to conserve+ -> Dynamics Double -- ^ time+ -> Int -- ^ the order+ -> Dynamics Double -- ^ the initial value+ -> Simulation (Dynamics Double) -- ^ the n'th order exponential delay+delayNI x t n i =+ mdo s <- forM [1 .. n] $ \k ->+ if k == 1+ then integ (x - (a ! 1) / t') (i * t')+ else integ ((a ! (k - 1)) / t' - (a ! k) / t') (i * t')+ let a = listArray (1, n) s+ t' = t / fromIntegral n+ return $ (a ! n) / t'++-- | Return the n'th order exponential delay.+--+-- This is a simplified version of the 'delayNI' function+-- without specifying the initial value.+delayN :: Dynamics Double -- ^ the value to conserve+ -> Dynamics Double -- ^ time+ -> Int -- ^ the order+ -> Simulation (Dynamics Double) -- ^ the n'th order exponential delay+delayN x t n = delayNI x t n x++-- | Return the forecast.+--+-- The function has the following definition:+--+-- @+-- forecast x at hz =+-- do y <- smooth x at+-- return $ x * (1.0 + (x \/ y - 1.0) \/ at * hz)+-- @+forecast :: Dynamics Double -- ^ the value to forecast+ -> Dynamics Double -- ^ the average time+ -> Dynamics Double -- ^ the time horizon+ -> Simulation (Dynamics Double) -- ^ the forecast+forecast x at hz =+ do y <- smooth x at+ return $ x * (1.0 + (x / y - 1.0) / at * hz)++-- | Return the trend.+--+-- The function has the following definition:+--+-- @+-- trend x at i =+-- do y <- smoothI x at (x \/ (1.0 + i * at))+-- return $ (x \/ y - 1.0) \/ at+-- @+trend :: Dynamics Double -- ^ the value for which the trend is calculated+ -> Dynamics Double -- ^ the average time+ -> Dynamics Double -- ^ the initial value+ -> Simulation (Dynamics Double) -- ^ the fractional change rate+trend x at i =+ do y <- smoothI x at (x / (1.0 + i * at))+ return $ (x / y - 1.0) / at++--+-- Difference Equations+--++-- | Retun the sum for the difference equation.+-- It is like an integral returned by the 'integ' function, only now+-- the difference is used instead of derivative.+--+-- As usual, to create a loopback, you should use the recursive do-notation.+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 + let Dynamics m = y+ sc = pointSpecs p+ ty = basicTime sc (n - 1) 0+ py = p { pointTime = ty, + pointIteration = n - 1, + pointPhase = 0 }+ a <- m py+ b <- diff py+ let !v = a + b+ return v+ return y++-- | Like 'diffsum' but allows either setting a new 'Left' sum value, or adding the 'Right' difference.+diffsumEither :: (Num a, Unboxed a)+ => Dynamics (Either a a)+ -- ^ either set the 'Left' value for the sum, or add the 'Right' difference to the sum+ -> Dynamics a+ -- ^ the initial value+ -> Simulation (Dynamics a)+ -- ^ the sum+diffsumEither (Dynamics diff) (Dynamics i) =+ mdo y <-+ MU.memo0Dynamics $+ Dynamics $ \p ->+ case pointIteration p of+ 0 -> i p+ n -> do + let Dynamics m = y+ sc = pointSpecs p+ ty = basicTime sc (n - 1) 0+ py = p { pointTime = ty, + pointIteration = n - 1, + pointPhase = 0 }+ b <- diff py+ case b of+ Left v ->+ return v+ Right b -> do+ a <- m py+ let !v = a + b+ return v+ return y++--+-- Table Functions+--++-- | 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 $ 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 m) tbl =+ Dynamics $ \p ->+ do a <- m p+ return $ tableLookupStepwise a tbl++--+-- Discrete Functions+--++-- | Return the delayed value using the specified lag time.+delay :: Dynamics a -- ^ the value to delay+ -> Dynamics Double -- ^ the lag time+ -> Dynamics a -- ^ the delayed value+delay (Dynamics x) (Dynamics d) = discreteDynamics $ 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 = 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 }+ | 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++--+-- Financial Functions+--++-- | Return the Net Present Value (NPV) of the stream computed using the specified+-- discount rate, the initial value and some factor (usually 1).+--+-- It is defined in the following way:+--+-- @+-- npv stream rate init factor =+-- mdo let dt' = liftParameter dt+-- df <- integ (- df * rate) 1+-- accum <- integ (stream * df) init+-- return $ (accum + dt' * stream * df) * factor+-- @+npv :: Dynamics Double -- ^ the stream+ -> Dynamics Double -- ^ the discount rate+ -> Dynamics Double -- ^ the initial value+ -> Dynamics Double -- ^ factor+ -> Simulation (Dynamics Double) -- ^ the Net Present Value (NPV)+npv stream rate init factor =+ mdo let dt' = liftParameter dt+ df <- integ (- df * rate) 1+ accum <- integ (stream * df) init+ 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.+--+-- It is defined in the following way:+--+-- @+-- npve stream rate init factor =+-- 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+-- @+npve :: Dynamics Double -- ^ the stream+ -> Dynamics Double -- ^ the discount rate+ -> Dynamics Double -- ^ the initial value+ -> Dynamics Double -- ^ factor+ -> Simulation (Dynamics Double) -- ^ the Net Present Value End (NPVE)+npve stream rate init factor =+ 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++-- | 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
@@ -1,64 +1,64 @@- --- | --- Module : Simulation.Aivika.Table --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.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 ++-- |+-- Module : Simulation.Aivika.Table+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.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
@@ -1,168 +1,179 @@- --- | --- Module : Simulation.Aivika.Task --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.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 -> processAwait (taskResultReceived t) - --- | Cancel the task. -cancelTask :: Task a -> Event () -cancelTask t = - cancelProcessWithId (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 ++-- |+-- Module : Simulation.Aivika.Task+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.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,+ spawnTaskWith,+ spawnTaskUsingIdWith,+ -- * 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 -> processAwait (taskResultReceived t)++-- | Cancel the task.+cancelTask :: Task a -> Event ()+cancelTask t =+ cancelProcessWithId (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 :: ProcessId -> Process a -> Process (Task a)+spawnTaskUsingId = spawnTaskUsingIdWith CancelTogether++-- | Run a child process in background and return immediately the corresponded task.+spawnTask :: Process a -> Process (Task a)+spawnTask = spawnTaskWith CancelTogether++-- | Run using the specified identifier a child process in background and return+-- immediately the corresponded task.+spawnTaskUsingIdWith :: ContCancellation -> ProcessId -> Process a -> Process (Task a)+spawnTaskUsingIdWith cancellation pid p =+ do (t, m) <- liftEvent $ newTaskUsingId pid p+ spawnProcessUsingIdWith cancellation pid m+ return t++-- | Run a child process in background and return immediately the corresponded task.+spawnTaskWith :: ContCancellation -> Process a -> Process (Task a)+spawnTaskWith cancellation p =+ do pid <- liftSimulation newProcessId+ spawnTaskUsingIdWith 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/Transform.hs view
@@ -1,126 +1,145 @@- -{-# LANGUAGE RecursiveDo #-} - --- | --- Module : Simulation.Aivika.Transform --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- The module defines something which is most close to the notion of --- analogous circuit as an opposite to the digital one. --- -module Simulation.Aivika.Transform - (-- * The Transform Arrow - Transform(..), - -- * Delaying the Transform - delayTransform, - -- * The Time Transform - timeTransform, - -- * Differential and Difference Equations - integTransform, - sumTransform) where - -import qualified Control.Category as C -import Control.Arrow -import Control.Monad - -import Simulation.Aivika.Simulation -import Simulation.Aivika.Dynamics -import Simulation.Aivika.Dynamics.Memo -import Simulation.Aivika.Unboxed -import Simulation.Aivika.SystemDynamics - --- | It allows representing an analogous circuit as an opposite to --- the digital one. --- --- This is a transform of one time varying function to another usually --- specified in the integration time points and then interpolated in --- other time points with help of one of the memoization functions --- like 'memo0Dynamics'. --- -newtype Transform a b = - Transform { runTransform :: Dynamics a -> Simulation (Dynamics b) - -- ^ Run the transform. - } - -instance C.Category Transform where - - id = Transform return - - (Transform g) . (Transform f) = - Transform $ \a -> f a >>= g - -instance Arrow Transform where - - arr f = Transform $ return . fmap f - - first (Transform f) = - Transform $ \bd -> - do (b, d) <- unzip0Dynamics bd - c <- f b - return $ liftM2 (,) c d - - second (Transform f) = - Transform $ \db -> - do (d, b) <- unzip0Dynamics db - c <- f b - return $ liftM2 (,) d c - - (Transform f) *** (Transform g) = - Transform $ \bb' -> - do (b, b') <- unzip0Dynamics bb' - c <- f b - c' <- g b' - return $ liftM2 (,) c c' - - (Transform f) &&& (Transform g) = - Transform $ \b -> - do c <- f b - c' <- g b - return $ liftM2 (,) c c' - -instance ArrowLoop Transform where - - loop (Transform f) = - Transform $ \b -> - mdo let bd = liftM2 (,) b d - cd <- f bd - (c, d) <- unzip0Dynamics cd - return c - --- | A transform that returns the current modeling time. -timeTransform :: Transform a Double -timeTransform = Transform $ const $ return time - --- | Return a delayed transform by the specified lag time and initial value. --- --- This is actually the 'delayI' function wrapped in the 'Transform' type. -delayTransform :: Dynamics Double -- ^ the lag time - -> Dynamics a -- ^ the initial value - -> Transform a a -- ^ the delayed transform -delayTransform lagTime init = - Transform $ \a -> delayI a lagTime init - --- | Return a transform that maps the derivative to an integral --- by the specified initial value. --- --- This is actually the 'integ' function wrapped in the 'Transform' type. -integTransform :: Dynamics Double - -- ^ the initial value - -> Transform Double Double - -- ^ map the derivative to an integral -integTransform = Transform . integ - --- | Return a transform that maps the difference to a sum --- by the specified initial value. --- --- This is actually the 'diffsum' function wrapped in the 'Transform' type. -sumTransform :: (Num a, Unboxed a) => - Dynamics a - -- ^ the initial value - -> Transform a a - -- ^ map the difference to a sum -sumTransform = Transform . diffsum ++{-# LANGUAGE RecursiveDo #-}++-- |+-- Module : Simulation.Aivika.Transform+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- The module defines something which is most close to the notion of+-- analogous circuit as an opposite to the digital one.+--+module Simulation.Aivika.Transform+ (-- * The Transform Arrow+ Transform(..),+ -- * Delaying the Transform+ delayTransform,+ -- * The Time Transform+ timeTransform,+ -- * Differential and Difference Equations+ integTransform,+ integTransformEither,+ sumTransform,+ sumTransformEither) where++import qualified Control.Category as C+import Control.Arrow+import Control.Monad++import Simulation.Aivika.Simulation+import Simulation.Aivika.Dynamics+import Simulation.Aivika.Dynamics.Memo+import Simulation.Aivika.Unboxed+import Simulation.Aivika.SystemDynamics++-- | It allows representing an analogous circuit as an opposite to+-- the digital one.+--+-- This is a transform of one time varying function to another usually+-- specified in the integration time points and then interpolated in+-- other time points with help of one of the memoization functions+-- like 'memo0Dynamics'.+--+newtype Transform a b =+ Transform { runTransform :: Dynamics a -> Simulation (Dynamics b)+ -- ^ Run the transform.+ }++instance C.Category Transform where++ id = Transform return+ + (Transform g) . (Transform f) =+ Transform $ \a -> f a >>= g++instance Arrow Transform where++ arr f = Transform $ return . fmap f++ first (Transform f) =+ Transform $ \bd ->+ do (b, d) <- unzip0Dynamics bd+ c <- f b+ return $ liftM2 (,) c d ++ second (Transform f) =+ Transform $ \db ->+ do (d, b) <- unzip0Dynamics db+ c <- f b+ return $ liftM2 (,) d c++ (Transform f) *** (Transform g) =+ Transform $ \bb' ->+ do (b, b') <- unzip0Dynamics bb'+ c <- f b+ c' <- g b'+ return $ liftM2 (,) c c'++ (Transform f) &&& (Transform g) =+ Transform $ \b ->+ do c <- f b+ c' <- g b+ return $ liftM2 (,) c c'++instance ArrowLoop Transform where++ loop (Transform f) =+ Transform $ \b ->+ mdo let bd = liftM2 (,) b d+ cd <- f bd+ (c, d) <- unzip0Dynamics cd+ return c++-- | A transform that returns the current modeling time.+timeTransform :: Transform a Double+timeTransform = Transform $ const $ return time++-- | Return a delayed transform by the specified lag time and initial value.+--+-- This is actually the 'delayI' function wrapped in the 'Transform' type. +delayTransform :: Dynamics Double -- ^ the lag time+ -> Dynamics a -- ^ the initial value+ -> Transform a a -- ^ the delayed transform+delayTransform lagTime init =+ Transform $ \a -> delayI a lagTime init+ +-- | Return a transform that maps the derivative to an integral+-- by the specified initial value.+--+-- This is actually the 'integ' function wrapped in the 'Transform' type. +integTransform :: Dynamics Double+ -- ^ the initial value+ -> Transform Double Double+ -- ^ map the derivative to an integral+integTransform init = Transform $ \diff -> integ diff init+ +-- | Like 'integTransform' but allows either setting a new 'Left' value of the integral,+-- or updating it by the specified 'Right' derivative.+integTransformEither :: Dynamics Double+ -- ^ the initial value+ -> Transform (Either Double Double) Double+ -- ^ map either a new 'Left' value or the 'Right' derivative to an integral+integTransformEither init = Transform $ \diff -> integEither diff init++-- | Return a transform that maps the difference to a sum+-- by the specified initial value.+--+-- This is actually the 'diffsum' function wrapped in the 'Transform' type. +sumTransform :: (Num a, Unboxed a)+ => Dynamics a+ -- ^ the initial value+ -> Transform a a+ -- ^ map the difference to a sum+sumTransform init = Transform $ \diff -> diffsum diff init++-- | Like 'sumTransform' but allows either setting a new 'Left' value of the sum,+-- or updating it by the specified 'Right' difference.+sumTransformEither :: (Num a, Unboxed a)+ => Dynamics a+ -- ^ the initial value+ -> Transform (Either a a) a+ -- ^ map either a new 'Left' value or the 'Right' difference to a sum+sumTransformEither init = Transform $ \diff -> diffsumEither diff init
Simulation/Aivika/Transform/Extra.hs view
@@ -1,54 +1,54 @@- --- | --- Module : Simulation.Aivika.Transform.Extra --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- This module defines auxiliary computations such as interpolation ones --- that complement the memoization, for example. There are scan computations too. --- - -module Simulation.Aivika.Transform.Extra - (-- * Interpolation - initTransform, - discreteTransform, - interpolatingTransform, - -- * Scans - scanTransform, - scan1Transform) where - -import Simulation.Aivika.Dynamics -import Simulation.Aivika.Dynamics.Extra -import Simulation.Aivika.Transform -import Simulation.Aivika.Transform.Memo - --- | A transform that returns the initial value. -initTransform :: Transform a a -initTransform = Transform $ return . initDynamics - --- | A transform that discretizes the computation in the integration time points. -discreteTransform :: Transform a a -discreteTransform = Transform $ return . discreteDynamics - --- | A tranform that interpolates the computation based on the integration time points only. --- Unlike the 'discreteTransform' computation it knows about the intermediate --- time points that are used in the Runge-Kutta method. -interpolatingTransform :: Transform a a -interpolatingTransform = Transform $ return . interpolateDynamics - --- | Like the standard 'scanl1' function but applied to values in --- the integration time points. The accumulator values are transformed --- according to the second argument, which should be either --- 'memo0Transform' or its unboxed version. -scan1Transform :: (a -> a -> a) -> Transform a a -> Transform a a -scan1Transform f (Transform tr) = Transform $ scan1Dynamics f tr - --- | Like the standard 'scanl' function but applied to values in --- the integration time points. The accumulator values are transformed --- according to the third argument, which should be either --- 'memo0Transform' or its unboxed version. -scanTransform :: (a -> b -> a) -> a -> Transform a a -> Transform b a -scanTransform f acc (Transform tr) = Transform $ scanDynamics f acc tr ++-- |+-- Module : Simulation.Aivika.Transform.Extra+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- This module defines auxiliary computations such as interpolation ones+-- that complement the memoization, for example. There are scan computations too.+--++module Simulation.Aivika.Transform.Extra+ (-- * Interpolation+ initTransform,+ discreteTransform,+ interpolatingTransform,+ -- * Scans+ scanTransform,+ scan1Transform) where++import Simulation.Aivika.Dynamics+import Simulation.Aivika.Dynamics.Extra+import Simulation.Aivika.Transform+import Simulation.Aivika.Transform.Memo++-- | A transform that returns the initial value.+initTransform :: Transform a a+initTransform = Transform $ return . initDynamics++-- | A transform that discretizes the computation in the integration time points.+discreteTransform :: Transform a a+discreteTransform = Transform $ return . discreteDynamics++-- | A tranform that interpolates the computation based on the integration time points only.+-- Unlike the 'discreteTransform' computation it knows about the intermediate +-- time points that are used in the Runge-Kutta method.+interpolatingTransform :: Transform a a+interpolatingTransform = Transform $ return . interpolateDynamics ++-- | Like the standard 'scanl1' function but applied to values in +-- the integration time points. The accumulator values are transformed+-- according to the second argument, which should be either +-- 'memo0Transform' or its unboxed version.+scan1Transform :: (a -> a -> a) -> Transform a a -> Transform a a+scan1Transform f (Transform tr) = Transform $ scan1Dynamics f tr++-- | Like the standard 'scanl' function but applied to values in +-- the integration time points. The accumulator values are transformed+-- according to the third argument, which should be either+-- 'memo0Transform' or its unboxed version.+scanTransform :: (a -> b -> a) -> a -> Transform a a -> Transform b a+scanTransform f acc (Transform tr) = Transform $ scanDynamics f acc tr
Simulation/Aivika/Transform/Memo.hs view
@@ -1,45 +1,45 @@- --- | --- Module : Simulation.Aivika.Transform.Memo --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- This module defines memoization transforms. The memoization creates such 'Dynamics' --- computations, which values are cached in the integration time points. Then --- these values are interpolated in all other time points. --- - -module Simulation.Aivika.Transform.Memo - (memoTransform, - memo0Transform, - iteratingTransform) where - -import Simulation.Aivika.Parameter -import Simulation.Aivika.Dynamics -import Simulation.Aivika.Dynamics.Extra -import Simulation.Aivika.Dynamics.Memo -import Simulation.Aivika.Transform - --- | A transform that memoizes and order the computation in the integration time points --- using the interpolation that knows of the Runge-Kutta method. The values are --- calculated sequentially starting from 'starttime'. -memoTransform :: Transform e e -memoTransform = Transform memoDynamics - --- | A transform that memoizes and order the computation in the integration time points using --- the 'discreteDynamics' interpolation. It consumes less memory than the 'memoTransform' --- computation but it is not aware of the Runge-Kutta method. There is a subtle --- difference when we request for values in the intermediate time points --- that are used by this method to integrate. In general case you should --- prefer the 'memo0Transform' computation above 'memoTransform'. -memo0Transform :: Transform e e -memo0Transform = Transform memo0Dynamics - --- | A transform that iterates sequentially the dynamic process with side effects in --- the integration time points. It is equivalent to the 'memo0Transform' computation --- but significantly more efficient, for the internal array is not created. -iteratingTransform :: Transform () () -iteratingTransform = Transform iterateDynamics ++-- |+-- Module : Simulation.Aivika.Transform.Memo+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- This module defines memoization transforms. The memoization creates such 'Dynamics'+-- computations, which values are cached in the integration time points. Then+-- these values are interpolated in all other time points.+--++module Simulation.Aivika.Transform.Memo+ (memoTransform,+ memo0Transform,+ iteratingTransform) where++import Simulation.Aivika.Parameter+import Simulation.Aivika.Dynamics+import Simulation.Aivika.Dynamics.Extra+import Simulation.Aivika.Dynamics.Memo+import Simulation.Aivika.Transform++-- | A transform that memoizes and order the computation in the integration time points+-- using the interpolation that knows of the Runge-Kutta method. The values are+-- calculated sequentially starting from 'starttime'.+memoTransform :: Transform e e+memoTransform = Transform memoDynamics ++-- | A transform that memoizes and order the computation in the integration time points using +-- the 'discreteDynamics' interpolation. It consumes less memory than the 'memoTransform'+-- computation but it is not aware of the Runge-Kutta method. There is a subtle+-- difference when we request for values in the intermediate time points+-- that are used by this method to integrate. In general case you should +-- prefer the 'memo0Transform' computation above 'memoTransform'.+memo0Transform :: Transform e e+memo0Transform = Transform memo0Dynamics++-- | A transform that iterates sequentially the dynamic process with side effects in +-- the integration time points. It is equivalent to the 'memo0Transform' computation+-- but significantly more efficient, for the internal array is not created.+iteratingTransform :: Transform () ()+iteratingTransform = Transform iterateDynamics
Simulation/Aivika/Transform/Memo/Unboxed.hs view
@@ -1,41 +1,41 @@- -{-# LANGUAGE FlexibleContexts #-} - --- | --- Module : Simulation.Aivika.Transform.Memo.Unboxed --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- This module defines the unboxed memoization transforms. The memoization creates such 'Dynamics' --- computations, which values are cached in the integration time points. Then --- these values are interpolated in all other time points. --- - -module Simulation.Aivika.Transform.Memo.Unboxed - (memoTransform, - memo0Transform) where - -import Simulation.Aivika.Parameter -import Simulation.Aivika.Dynamics -import Simulation.Aivika.Dynamics.Extra -import Simulation.Aivika.Dynamics.Memo.Unboxed -import Simulation.Aivika.Transform -import Simulation.Aivika.Unboxed - --- | A transform that memoizes and order the computation in the integration time points --- using the interpolation that knows of the Runge-Kutta method. The values are --- calculated sequentially starting from 'starttime'. -memoTransform :: Unboxed e => Transform e e -memoTransform = Transform memoDynamics - --- | A transform that memoizes and order the computation in the integration time points using --- the 'discreteDynamics' interpolation. It consumes less memory than the 'memoTransform' --- computation but it is not aware of the Runge-Kutta method. There is a subtle --- difference when we request for values in the intermediate time points --- that are used by this method to integrate. In general case you should --- prefer the 'memo0Transform' computation above 'memoTransform'. -memo0Transform :: Unboxed e => Transform e e -memo0Transform = Transform memo0Dynamics ++{-# LANGUAGE FlexibleContexts #-}++-- |+-- Module : Simulation.Aivika.Transform.Memo.Unboxed+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- This module defines the unboxed memoization transforms. The memoization creates such 'Dynamics'+-- computations, which values are cached in the integration time points. Then+-- these values are interpolated in all other time points.+--++module Simulation.Aivika.Transform.Memo.Unboxed+ (memoTransform,+ memo0Transform) where++import Simulation.Aivika.Parameter+import Simulation.Aivika.Dynamics+import Simulation.Aivika.Dynamics.Extra+import Simulation.Aivika.Dynamics.Memo.Unboxed+import Simulation.Aivika.Transform+import Simulation.Aivika.Unboxed++-- | A transform that memoizes and order the computation in the integration time points+-- using the interpolation that knows of the Runge-Kutta method. The values are+-- calculated sequentially starting from 'starttime'.+memoTransform :: Unboxed e => Transform e e+memoTransform = Transform memoDynamics ++-- | A transform that memoizes and order the computation in the integration time points using +-- the 'discreteDynamics' interpolation. It consumes less memory than the 'memoTransform'+-- computation but it is not aware of the Runge-Kutta method. There is a subtle+-- difference when we request for values in the intermediate time points+-- that are used by this method to integrate. In general case you should +-- prefer the 'memo0Transform' computation above 'memoTransform'.+memo0Transform :: Unboxed e => Transform e e+memo0Transform = Transform memo0Dynamics
Simulation/Aivika/Unboxed.hs view
@@ -1,48 +1,48 @@- -{-# LANGUAGE CPP, FlexibleContexts #-} - --- | --- Module : Simulation.Aivika.Unboxed --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- The 'Unboxed' class allows creating unboxed arrays in monad 'IO'. --- - -module Simulation.Aivika.Unboxed - (Unboxed(..)) where - -import Data.Array -import Data.Array.IO.Safe -import Data.Int -import Data.Word - --- | The type which values can be contained in an unboxed array. -class MArray IOUArray e IO => Unboxed e where - - -- | Create an unboxed array with default values. - newUnboxedArray_ :: Ix i => (i, i) -> IO (IOUArray i e) - newUnboxedArray_ = newArray_ - -instance Unboxed Bool -instance Unboxed Char -instance Unboxed Double -instance Unboxed Float -instance Unboxed Int -instance Unboxed Int8 -instance Unboxed Int16 -instance Unboxed Int32 -instance Unboxed Word -instance Unboxed Word8 -instance Unboxed Word16 -instance Unboxed Word32 - -#ifndef __HASTE__ - -instance Unboxed Int64 -instance Unboxed Word64 - -#endif ++{-# LANGUAGE CPP, FlexibleContexts #-}++-- |+-- Module : Simulation.Aivika.Unboxed+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- The 'Unboxed' class allows creating unboxed arrays in monad 'IO'.+--++module Simulation.Aivika.Unboxed+ (Unboxed(..)) where++import Data.Array+import Data.Array.IO.Safe+import Data.Int +import Data.Word ++-- | The type which values can be contained in an unboxed array.+class MArray IOUArray e IO => Unboxed e where++ -- | Create an unboxed array with default values.+ newUnboxedArray_ :: Ix i => (i, i) -> IO (IOUArray i e)+ newUnboxedArray_ = newArray_++instance Unboxed Bool +instance Unboxed Char +instance Unboxed Double +instance Unboxed Float +instance Unboxed Int +instance Unboxed Int8 +instance Unboxed Int16 +instance Unboxed Int32 +instance Unboxed Word +instance Unboxed Word8 +instance Unboxed Word16 +instance Unboxed Word32 ++#ifndef __HASTE__++instance Unboxed Int64+instance Unboxed Word64++#endif
Simulation/Aivika/Var.hs view
@@ -1,188 +1,188 @@- --- | --- Module : Simulation.Aivika.Var --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- This module defines a variable that is bound up with the event queue and --- that keeps the history of changes storing the values in arrays, which --- allows using the variable in differential and difference equations of --- System Dynamics within hybrid discrete-continuous simulation. --- -module Simulation.Aivika.Var - (Var, - varChanged, - varChanged_, - newVar, - readVar, - varMemo, - writeVar, - modifyVar, - freezeVar) where - -import Data.Array -import Data.Array.IO.Safe - -import Simulation.Aivika.Internal.Specs -import Simulation.Aivika.Internal.Simulation -import Simulation.Aivika.Internal.Dynamics -import Simulation.Aivika.Internal.Event -import Simulation.Aivika.Internal.Signal -import Simulation.Aivika.Ref -import Simulation.Aivika.Signal - -import qualified Simulation.Aivika.Vector as V -import qualified Simulation.Aivika.Vector.Unboxed as UV - --- | Like the 'Ref' reference but keeps the history of changes in --- different time points. The 'Var' variable is safe to be used in --- the hybrid discrete-continuous simulation. --- --- For example, the memoised values of a variable can be used in --- the differential or difference equations of System Dynamics, while --- the variable iself can be updated wihin the discrete event simulation. --- --- Only this variable is much slower than the reference. -data Var a = - Var { varXS :: UV.Vector Double, - varMS :: V.Vector a, - varYS :: V.Vector a, - varChangedSource :: SignalSource a } - --- | Create a new variable. -newVar :: a -> Simulation (Var a) -newVar a = - Simulation $ \r -> - do xs <- UV.newVector - ms <- V.newVector - ys <- V.newVector - UV.appendVector xs $ spcStartTime $ runSpecs r - V.appendVector ms a - V.appendVector ys a - s <- invokeSimulation r newSignalSource - return Var { varXS = xs, - varMS = ms, - varYS = ms, - varChangedSource = s } - --- | Read the first actual, i.e. memoised, value of a variable for the requested time --- actuating the current events from the queue if needed. --- --- This computation can be used in the ordinary differential and --- difference equations of System Dynamics. -varMemo :: Var a -> Dynamics a -varMemo v = - runEventWith CurrentEventsOrFromPast $ - Event $ \p -> - do let xs = varXS v - ms = varMS v - ys = varYS v - t = pointTime p - count <- UV.vectorCount xs - let i = count - 1 - x <- UV.readVector xs i - if x < t - then do a <- V.readVector ys i - UV.appendVector xs t - V.appendVector ms a - V.appendVector ys a - return a - else if x == t - then V.readVector ms i - else do i <- UV.vectorBinarySearch xs t - if i >= 0 - then V.readVector ms i - else V.readVector ms $ - (i + 1) - 1 - --- | Read the recent actual value of a variable for the requested time. --- --- This computation is destined for using within discrete event simulation. -readVar :: Var a -> Event a -readVar v = - Event $ \p -> - do let xs = varXS v - ys = varYS v - t = pointTime p - count <- UV.vectorCount xs - let i = count - 1 - x <- UV.readVector xs i - if x <= t - then V.readVector ys i - else do i <- UV.vectorBinarySearch xs t - if i >= 0 - then V.readVector ys i - else V.readVector ys $ - (i + 1) - 1 - --- | Write a new value into the variable. -writeVar :: Var a -> a -> Event () -writeVar v a = - Event $ \p -> - do let xs = varXS v - ms = varMS v - ys = varYS v - t = pointTime p - s = varChangedSource v - count <- UV.vectorCount xs - let i = count - 1 - x <- UV.readVector xs i - if t < x - then error "Cannot update the past data: writeVar." - else if t == x - then V.writeVector ys i $! a - else do UV.appendVector xs t - V.appendVector ms $! a - V.appendVector ys $! a - invokeEvent p $ triggerSignal s a - --- | Mutate the contents of the variable. -modifyVar :: Var a -> (a -> a) -> Event () -modifyVar v f = - Event $ \p -> - do let xs = varXS v - ms = varMS v - ys = varYS v - t = pointTime p - s = varChangedSource v - count <- UV.vectorCount xs - let i = count - 1 - x <- UV.readVector xs i - if t < x - then error "Cannot update the past data: modifyVar." - else if t == x - then do a <- V.readVector ys i - let b = f a - V.writeVector ys i $! b - invokeEvent p $ triggerSignal s b - else do a <- V.readVector ys i - let b = f a - UV.appendVector xs t - V.appendVector ms $! b - V.appendVector ys $! b - invokeEvent p $ triggerSignal s b - --- | Freeze the variable and return in arrays the time points and corresponded --- first and last values when the variable had changed or had been memoised in --- different time points: (1) the time points are sorted in ascending order; --- (2) the first and last actual values per each time point are provided. --- --- If you need to get all changes including those ones that correspond to the same --- simulation time points then you can use the 'newSignalHistory' function passing --- in the 'varChanged' signal to it and then call function 'readSignalHistory'. -freezeVar :: Var a -> Event (Array Int Double, Array Int a, Array Int a) -freezeVar v = - Event $ \p -> - do xs <- UV.freezeVector (varXS v) - ms <- V.freezeVector (varMS v) - ys <- V.freezeVector (varYS v) - return (xs, ms, ys) - --- | Return a signal that notifies about every change of the variable state. -varChanged :: Var a -> Signal a -varChanged v = publishSignal (varChangedSource v) - --- | Return a signal that notifies about every change of the variable state. -varChanged_ :: Var a -> Signal () -varChanged_ v = mapSignal (const ()) $ varChanged v ++-- |+-- Module : Simulation.Aivika.Var+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- This module defines a variable that is bound up with the event queue and +-- that keeps the history of changes storing the values in arrays, which+-- allows using the variable in differential and difference equations of+-- System Dynamics within hybrid discrete-continuous simulation.+--+module Simulation.Aivika.Var+ (Var,+ varChanged,+ varChanged_,+ newVar,+ readVar,+ varMemo,+ writeVar,+ modifyVar,+ freezeVar) where++import Data.Array+import Data.Array.IO.Safe++import Simulation.Aivika.Internal.Specs+import Simulation.Aivika.Internal.Simulation+import Simulation.Aivika.Internal.Dynamics+import Simulation.Aivika.Internal.Event+import Simulation.Aivika.Internal.Signal+import Simulation.Aivika.Ref+import Simulation.Aivika.Signal++import qualified Simulation.Aivika.Vector as V+import qualified Simulation.Aivika.Vector.Unboxed as UV++-- | Like the 'Ref' reference but keeps the history of changes in +-- different time points. The 'Var' variable is safe to be used in+-- the hybrid discrete-continuous simulation.+--+-- For example, the memoised values of a variable can be used in+-- the differential or difference equations of System Dynamics, while+-- the variable iself can be updated wihin the discrete event simulation.+--+-- Only this variable is much slower than the reference.+data Var a = + Var { varXS :: UV.Vector Double,+ varMS :: V.Vector a,+ varYS :: V.Vector a,+ varChangedSource :: SignalSource a }+ +-- | Create a new variable.+newVar :: a -> Simulation (Var a)+newVar a =+ Simulation $ \r ->+ do xs <- UV.newVector+ ms <- V.newVector+ ys <- V.newVector+ UV.appendVector xs $ spcStartTime $ runSpecs r+ V.appendVector ms a+ V.appendVector ys a+ s <- invokeSimulation r newSignalSource+ return Var { varXS = xs,+ varMS = ms,+ varYS = ms,+ varChangedSource = s }++-- | Read the first actual, i.e. memoised, value of a variable for the requested time+-- actuating the current events from the queue if needed.+--+-- This computation can be used in the ordinary differential and+-- difference equations of System Dynamics.+varMemo :: Var a -> Dynamics a+varMemo v =+ runEventWith CurrentEventsOrFromPast $+ Event $ \p ->+ do let xs = varXS v+ ms = varMS v+ ys = varYS v+ t = pointTime p+ count <- UV.vectorCount xs+ let i = count - 1+ x <- UV.readVector xs i+ if x < t+ then do a <- V.readVector ys i+ UV.appendVector xs t+ V.appendVector ms a+ V.appendVector ys a+ return a+ else if x == t+ then V.readVector ms i+ else do i <- UV.vectorBinarySearch xs t+ if i >= 0+ then V.readVector ms i+ else V.readVector ms $ - (i + 1) - 1++-- | Read the recent actual value of a variable for the requested time.+--+-- This computation is destined for using within discrete event simulation.+readVar :: Var a -> Event a+readVar v = + Event $ \p ->+ do let xs = varXS v+ ys = varYS v+ t = pointTime p+ count <- UV.vectorCount xs+ let i = count - 1+ x <- UV.readVector xs i+ if x <= t + then V.readVector ys i+ else do i <- UV.vectorBinarySearch xs t+ if i >= 0+ then V.readVector ys i+ else V.readVector ys $ - (i + 1) - 1++-- | Write a new value into the variable.+writeVar :: Var a -> a -> Event ()+writeVar v a =+ Event $ \p ->+ do let xs = varXS v+ ms = varMS v+ ys = varYS v+ t = pointTime p+ s = varChangedSource v+ count <- UV.vectorCount xs+ let i = count - 1+ x <- UV.readVector xs i+ if t < x + then error "Cannot update the past data: writeVar."+ else if t == x+ then V.writeVector ys i $! a+ else do UV.appendVector xs t+ V.appendVector ms $! a+ V.appendVector ys $! a+ invokeEvent p $ triggerSignal s a++-- | Mutate the contents of the variable.+modifyVar :: Var a -> (a -> a) -> Event ()+modifyVar v f =+ Event $ \p ->+ do let xs = varXS v+ ms = varMS v+ ys = varYS v+ t = pointTime p+ s = varChangedSource v+ count <- UV.vectorCount xs+ let i = count - 1+ x <- UV.readVector xs i+ if t < x+ then error "Cannot update the past data: modifyVar."+ else if t == x+ then do a <- V.readVector ys i+ let b = f a+ V.writeVector ys i $! b+ invokeEvent p $ triggerSignal s b+ else do a <- V.readVector ys i+ let b = f a+ UV.appendVector xs t+ V.appendVector ms $! b+ V.appendVector ys $! b+ invokeEvent p $ triggerSignal s b++-- | Freeze the variable and return in arrays the time points and corresponded +-- first and last values when the variable had changed or had been memoised in+-- different time points: (1) the time points are sorted in ascending order;+-- (2) the first and last actual values per each time point are provided.+--+-- If you need to get all changes including those ones that correspond to the same+-- simulation time points then you can use the 'newSignalHistory' function passing+-- in the 'varChanged' signal to it and then call function 'readSignalHistory'.+freezeVar :: Var a -> Event (Array Int Double, Array Int a, Array Int a)+freezeVar v =+ Event $ \p ->+ do xs <- UV.freezeVector (varXS v)+ ms <- V.freezeVector (varMS v)+ ys <- V.freezeVector (varYS v)+ return (xs, ms, ys)+ +-- | Return a signal that notifies about every change of the variable state.+varChanged :: Var a -> Signal a+varChanged v = publishSignal (varChangedSource v)++-- | Return a signal that notifies about every change of the variable state.+varChanged_ :: Var a -> Signal ()+varChanged_ v = mapSignal (const ()) $ varChanged v
Simulation/Aivika/Var/Unboxed.hs view
@@ -1,188 +1,188 @@- --- | --- Module : Simulation.Aivika.Var.Unboxed --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- This module defines an unboxed variable that is bound up with the event queue and --- that keeps the history of changes storing the values in unboxed arrays, which --- allows using the variable in differential and difference equations of --- System Dynamics within hybrid discrete-continuous simulation. --- -module Simulation.Aivika.Var.Unboxed - (Var, - varChanged, - varChanged_, - newVar, - readVar, - varMemo, - writeVar, - modifyVar, - freezeVar) where - -import Data.Array -import Data.Array.IO.Safe - -import Simulation.Aivika.Internal.Specs -import Simulation.Aivika.Internal.Simulation -import Simulation.Aivika.Internal.Dynamics -import Simulation.Aivika.Internal.Event -import Simulation.Aivika.Internal.Signal -import Simulation.Aivika.Ref -import Simulation.Aivika.Signal -import Simulation.Aivika.Unboxed - -import qualified Simulation.Aivika.Vector.Unboxed as UV - --- | Like the 'Ref' reference but keeps the history of changes in --- different time points. The 'Var' variable is safe to be used in --- the hybrid discrete-continuous simulation. --- --- For example, the memoised values of a variable can be used in --- the differential or difference equations of System Dynamics, while --- the variable iself can be updated wihin the discrete event simulation. --- --- Only this variable is much slower than the reference. -data Var a = - Var { varXS :: UV.Vector Double, - varMS :: UV.Vector a, - varYS :: UV.Vector a, - varChangedSource :: SignalSource a } - --- | Create a new variable. -newVar :: Unboxed a => a -> Simulation (Var a) -newVar a = - Simulation $ \r -> - do xs <- UV.newVector - ms <- UV.newVector - ys <- UV.newVector - UV.appendVector xs $ spcStartTime $ runSpecs r - UV.appendVector ms a - UV.appendVector ys a - s <- invokeSimulation r newSignalSource - return Var { varXS = xs, - varMS = ms, - varYS = ms, - varChangedSource = s } - --- | Read the first actual, i.e. memoised, value of a variable for the requested time --- actuating the current events from the queue if needed. --- --- This computation can be used in the ordinary differential and --- difference equations of System Dynamics. -varMemo :: Unboxed a => Var a -> Dynamics a -varMemo v = - runEventWith CurrentEventsOrFromPast $ - Event $ \p -> - do let xs = varXS v - ms = varMS v - ys = varYS v - t = pointTime p - count <- UV.vectorCount xs - let i = count - 1 - x <- UV.readVector xs i - if x < t - then do a <- UV.readVector ys i - UV.appendVector xs t - UV.appendVector ms a - UV.appendVector ys a - return a - else if x == t - then UV.readVector ms i - else do i <- UV.vectorBinarySearch xs t - if i >= 0 - then UV.readVector ms i - else UV.readVector ms $ - (i + 1) - 1 - --- | Read the recent actual value of a variable for the requested time. --- --- This computation is destined for using within discrete event simulation. -readVar :: Unboxed a => Var a -> Event a -readVar v = - Event $ \p -> - do let xs = varXS v - ys = varYS v - t = pointTime p - count <- UV.vectorCount xs - let i = count - 1 - x <- UV.readVector xs i - if x <= t - then UV.readVector ys i - else do i <- UV.vectorBinarySearch xs t - if i >= 0 - then UV.readVector ys i - else UV.readVector ys $ - (i + 1) - 1 - --- | Write a new value into the variable. -writeVar :: Unboxed a => Var a -> a -> Event () -writeVar v a = - Event $ \p -> - do let xs = varXS v - ms = varMS v - ys = varYS v - t = pointTime p - s = varChangedSource v - count <- UV.vectorCount xs - let i = count - 1 - x <- UV.readVector xs i - if t < x - then error "Cannot update the past data: writeVar." - else if t == x - then UV.writeVector ys i $! a - else do UV.appendVector xs t - UV.appendVector ms $! a - UV.appendVector ys $! a - invokeEvent p $ triggerSignal s a - --- | Mutate the contents of the variable. -modifyVar :: Unboxed a => Var a -> (a -> a) -> Event () -modifyVar v f = - Event $ \p -> - do let xs = varXS v - ms = varMS v - ys = varYS v - t = pointTime p - s = varChangedSource v - count <- UV.vectorCount xs - let i = count - 1 - x <- UV.readVector xs i - if t < x - then error "Cannot update the past data: modifyVar." - else if t == x - then do a <- UV.readVector ys i - let b = f a - UV.writeVector ys i $! b - invokeEvent p $ triggerSignal s b - else do a <- UV.readVector ys i - let b = f a - UV.appendVector xs t - UV.appendVector ms $! b - UV.appendVector ys $! b - invokeEvent p $ triggerSignal s b - --- | Freeze the variable and return in arrays the time points and corresponded --- first and last values when the variable had changed or had been memoised in --- different time points: (1) the time points are sorted in ascending order; --- (2) the first and last actual values per each time point are provided. --- --- If you need to get all changes including those ones that correspond to the same --- simulation time points then you can use the 'newSignalHistory' function passing --- in the 'varChanged' signal to it and then call function 'readSignalHistory'. -freezeVar :: Unboxed a => Var a -> Event (Array Int Double, Array Int a, Array Int a) -freezeVar v = - Event $ \p -> - do xs <- UV.freezeVector (varXS v) - ms <- UV.freezeVector (varMS v) - ys <- UV.freezeVector (varYS v) - return (xs, ms, ys) - --- | Return a signal that notifies about every change of the variable state. -varChanged :: Var a -> Signal a -varChanged v = publishSignal (varChangedSource v) - --- | Return a signal that notifies about every change of the variable state. -varChanged_ :: Var a -> Signal () -varChanged_ v = mapSignal (const ()) $ varChanged v ++-- |+-- Module : Simulation.Aivika.Var.Unboxed+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- This module defines an unboxed variable that is bound up with the event queue and +-- that keeps the history of changes storing the values in unboxed arrays, which+-- allows using the variable in differential and difference equations of+-- System Dynamics within hybrid discrete-continuous simulation.+--+module Simulation.Aivika.Var.Unboxed+ (Var,+ varChanged,+ varChanged_,+ newVar,+ readVar,+ varMemo,+ writeVar,+ modifyVar,+ freezeVar) where++import Data.Array+import Data.Array.IO.Safe++import Simulation.Aivika.Internal.Specs+import Simulation.Aivika.Internal.Simulation+import Simulation.Aivika.Internal.Dynamics+import Simulation.Aivika.Internal.Event+import Simulation.Aivika.Internal.Signal+import Simulation.Aivika.Ref+import Simulation.Aivika.Signal+import Simulation.Aivika.Unboxed++import qualified Simulation.Aivika.Vector.Unboxed as UV++-- | Like the 'Ref' reference but keeps the history of changes in +-- different time points. The 'Var' variable is safe to be used in+-- the hybrid discrete-continuous simulation.+--+-- For example, the memoised values of a variable can be used in+-- the differential or difference equations of System Dynamics, while+-- the variable iself can be updated wihin the discrete event simulation.+--+-- Only this variable is much slower than the reference.+data Var a = + Var { varXS :: UV.Vector Double,+ varMS :: UV.Vector a,+ varYS :: UV.Vector a,+ varChangedSource :: SignalSource a }++-- | Create a new variable.+newVar :: Unboxed a => a -> Simulation (Var a)+newVar a =+ Simulation $ \r ->+ do xs <- UV.newVector+ ms <- UV.newVector+ ys <- UV.newVector+ UV.appendVector xs $ spcStartTime $ runSpecs r+ UV.appendVector ms a+ UV.appendVector ys a+ s <- invokeSimulation r newSignalSource+ return Var { varXS = xs,+ varMS = ms,+ varYS = ms,+ varChangedSource = s }++-- | Read the first actual, i.e. memoised, value of a variable for the requested time+-- actuating the current events from the queue if needed.+--+-- This computation can be used in the ordinary differential and+-- difference equations of System Dynamics.+varMemo :: Unboxed a => Var a -> Dynamics a+varMemo v =+ runEventWith CurrentEventsOrFromPast $+ Event $ \p ->+ do let xs = varXS v+ ms = varMS v+ ys = varYS v+ t = pointTime p+ count <- UV.vectorCount xs+ let i = count - 1+ x <- UV.readVector xs i+ if x < t+ then do a <- UV.readVector ys i+ UV.appendVector xs t+ UV.appendVector ms a+ UV.appendVector ys a+ return a+ else if x == t+ then UV.readVector ms i+ else do i <- UV.vectorBinarySearch xs t+ if i >= 0+ then UV.readVector ms i+ else UV.readVector ms $ - (i + 1) - 1++-- | Read the recent actual value of a variable for the requested time.+--+-- This computation is destined for using within discrete event simulation.+readVar :: Unboxed a => Var a -> Event a+readVar v = + Event $ \p ->+ do let xs = varXS v+ ys = varYS v+ t = pointTime p+ count <- UV.vectorCount xs+ let i = count - 1+ x <- UV.readVector xs i+ if x <= t + then UV.readVector ys i+ else do i <- UV.vectorBinarySearch xs t+ if i >= 0+ then UV.readVector ys i+ else UV.readVector ys $ - (i + 1) - 1++-- | Write a new value into the variable.+writeVar :: Unboxed a => Var a -> a -> Event ()+writeVar v a =+ Event $ \p ->+ do let xs = varXS v+ ms = varMS v+ ys = varYS v+ t = pointTime p+ s = varChangedSource v+ count <- UV.vectorCount xs+ let i = count - 1+ x <- UV.readVector xs i+ if t < x + then error "Cannot update the past data: writeVar."+ else if t == x+ then UV.writeVector ys i $! a+ else do UV.appendVector xs t+ UV.appendVector ms $! a+ UV.appendVector ys $! a+ invokeEvent p $ triggerSignal s a++-- | Mutate the contents of the variable.+modifyVar :: Unboxed a => Var a -> (a -> a) -> Event ()+modifyVar v f =+ Event $ \p ->+ do let xs = varXS v+ ms = varMS v+ ys = varYS v+ t = pointTime p+ s = varChangedSource v+ count <- UV.vectorCount xs+ let i = count - 1+ x <- UV.readVector xs i+ if t < x+ then error "Cannot update the past data: modifyVar."+ else if t == x+ then do a <- UV.readVector ys i+ let b = f a+ UV.writeVector ys i $! b+ invokeEvent p $ triggerSignal s b+ else do a <- UV.readVector ys i+ let b = f a+ UV.appendVector xs t+ UV.appendVector ms $! b+ UV.appendVector ys $! b+ invokeEvent p $ triggerSignal s b++-- | Freeze the variable and return in arrays the time points and corresponded +-- first and last values when the variable had changed or had been memoised in+-- different time points: (1) the time points are sorted in ascending order;+-- (2) the first and last actual values per each time point are provided.+--+-- If you need to get all changes including those ones that correspond to the same+-- simulation time points then you can use the 'newSignalHistory' function passing+-- in the 'varChanged' signal to it and then call function 'readSignalHistory'.+freezeVar :: Unboxed a => Var a -> Event (Array Int Double, Array Int a, Array Int a)+freezeVar v =+ Event $ \p ->+ do xs <- UV.freezeVector (varXS v)+ ms <- UV.freezeVector (varMS v)+ ys <- UV.freezeVector (varYS v)+ return (xs, ms, ys)+ +-- | Return a signal that notifies about every change of the variable state.+varChanged :: Var a -> Signal a+varChanged v = publishSignal (varChangedSource v)++-- | Return a signal that notifies about every change of the variable state.+varChanged_ :: Var a -> Signal ()+varChanged_ v = mapSignal (const ()) $ varChanged v
Simulation/Aivika/Vector.hs view
@@ -1,183 +1,183 @@- --- | --- Module : Simulation.Aivika.Vector --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- An imperative vector. --- -module Simulation.Aivika.Vector - (Vector, - newVector, - copyVector, - vectorCount, - appendVector, - readVector, - writeVector, - vectorBinarySearch, - vectorInsert, - vectorDeleteAt, - vectorIndex, - freezeVector) where - -import Data.Array -import Data.Array.MArray.Safe -import Data.Array.IO.Safe -import Data.IORef -import Control.Monad - --- | Represents a resizable vector. -data Vector a = Vector { vectorArrayRef :: IORef (IOArray Int a), - vectorCountRef :: IORef Int, - vectorCapacityRef :: IORef Int } - --- | Create a new vector. -newVector :: IO (Vector a) -newVector = - do array <- newArray_ (0, 4 - 1) - arrayRef <- newIORef array - countRef <- newIORef 0 - capacityRef <- newIORef 4 - return Vector { vectorArrayRef = arrayRef, - vectorCountRef = countRef, - vectorCapacityRef = capacityRef } - --- | Copy the vector. -copyVector :: Vector a -> IO (Vector a) -copyVector vector = - do array <- readIORef (vectorArrayRef vector) - count <- readIORef (vectorCountRef vector) - array' <- newArray_ (0, count - 1) - arrayRef' <- newIORef array' - countRef' <- newIORef count - capacityRef' <- newIORef count - forM_ [0 .. count - 1] $ \i -> - do x <- readArray array i - writeArray array' i x - return Vector { vectorArrayRef = arrayRef', - vectorCountRef = countRef', - vectorCapacityRef = capacityRef' } - --- | Ensure that the vector has the specified capacity. -vectorEnsureCapacity :: Vector a -> Int -> IO () -vectorEnsureCapacity vector capacity = - do capacity' <- readIORef (vectorCapacityRef vector) - when (capacity' < capacity) $ - do array' <- readIORef (vectorArrayRef vector) - count' <- readIORef (vectorCountRef vector) - let capacity'' = max (2 * capacity') capacity - array'' <- newArray_ (0, capacity'' - 1) - forM_ [0 .. count' - 1] $ \i -> - do x <- readArray array' i - writeArray array'' i x - writeIORef (vectorArrayRef vector) array'' - writeIORef (vectorCapacityRef vector) capacity'' - --- | Return the element count. -vectorCount :: Vector a -> IO Int -vectorCount vector = readIORef (vectorCountRef vector) - --- | Add the specified element to the end of the vector. -appendVector :: Vector a -> a -> IO () -appendVector vector item = - do count <- readIORef (vectorCountRef vector) - vectorEnsureCapacity vector (count + 1) - array <- readIORef (vectorArrayRef vector) - writeArray array count item - writeIORef (vectorCountRef vector) (count + 1) - --- | Read a value from the vector, where indices are started from 0. -readVector :: Vector a -> Int -> IO a -readVector vector index = - do array <- readIORef (vectorArrayRef vector) - readArray array index - --- | Set an array item at the specified index which is started from 0. -writeVector :: Vector a -> Int -> a -> IO () -writeVector vector index item = - do array <- readIORef (vectorArrayRef vector) - writeArray array index item - -vectorBinarySearch' :: Ord a => IOArray Int a -> a -> Int -> Int -> IO Int -vectorBinarySearch' array item left right = - if left > right - then return $ - (right + 1) - 1 - else - do let index = (left + right) `div` 2 - curr <- readArray array index - if item < curr - then vectorBinarySearch' array item left (index - 1) - else if item == curr - then return index - else vectorBinarySearch' array item (index + 1) right - --- | Return the index of the specified element using binary search; otherwise, --- a negated insertion index minus one: 0 -> -0 - 1, ..., i -> -i - 1, .... -vectorBinarySearch :: Ord a => Vector a -> a -> IO Int -vectorBinarySearch vector item = - do array <- readIORef (vectorArrayRef vector) - count <- readIORef (vectorCountRef vector) - vectorBinarySearch' array item 0 (count - 1) - --- | Return the elements of the vector in an immutable array. -freezeVector :: Vector a -> IO (Array Int a) -freezeVector vector = - do vector' <- copyVector vector - array <- readIORef (vectorArrayRef vector') - freeze array - --- | Insert the element in the vector at the specified index. -vectorInsert :: Vector a -> Int -> a -> IO () -vectorInsert vector index item = - do count <- readIORef (vectorCountRef vector) - when (index < 0) $ - error $ - "Index cannot be " ++ - "negative: vectorInsert." - when (index > count) $ - error $ - "Index cannot be greater " ++ - "than the count: vectorInsert." - vectorEnsureCapacity vector (count + 1) - array <- readIORef (vectorArrayRef vector) - forM_ [count, count - 1 .. index + 1] $ \i -> - do x <- readArray array (i - 1) - writeArray array i x - writeArray array index item - writeIORef (vectorCountRef vector) (count + 1) - --- | Delete the element at the specified index. -vectorDeleteAt :: Vector a -> Int -> IO () -vectorDeleteAt vector index = - do count <- readIORef (vectorCountRef vector) - when (index < 0) $ - error $ - "Index cannot be " ++ - "negative: vectorDeleteAt." - when (index >= count) $ - error $ - "Index must be less " ++ - "than the count: vectorDeleteAt." - array <- readIORef (vectorArrayRef vector) - forM_ [index, index + 1 .. count - 2] $ \i -> - do x <- readArray array (i + 1) - writeArray array i x - writeArray array (count - 1) undefined - writeIORef (vectorCountRef vector) (count - 1) - --- | Return the index of the item or -1. -vectorIndex :: Eq a => Vector a -> a -> IO Int -vectorIndex vector item = - do count <- readIORef (vectorCountRef vector) - array <- readIORef (vectorArrayRef vector) - let loop index = - if index >= count - then return $ -1 - else do x <- readArray array index - if item == x - then return index - else loop $ index + 1 - loop 0 ++-- |+-- Module : Simulation.Aivika.Vector+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- An imperative vector.+--+module Simulation.Aivika.Vector+ (Vector, + newVector, + copyVector,+ vectorCount, + appendVector, + readVector, + writeVector,+ vectorBinarySearch,+ vectorInsert,+ vectorDeleteAt,+ vectorIndex,+ freezeVector) where ++import Data.Array+import Data.Array.MArray.Safe+import Data.Array.IO.Safe+import Data.IORef+import Control.Monad++-- | Represents a resizable vector.+data Vector a = Vector { vectorArrayRef :: IORef (IOArray Int a),+ vectorCountRef :: IORef Int, + vectorCapacityRef :: IORef Int }++-- | Create a new vector.+newVector :: IO (Vector a)+newVector = + do array <- newArray_ (0, 4 - 1)+ arrayRef <- newIORef array+ countRef <- newIORef 0+ capacityRef <- newIORef 4+ return Vector { vectorArrayRef = arrayRef,+ vectorCountRef = countRef,+ vectorCapacityRef = capacityRef }++-- | Copy the vector.+copyVector :: Vector a -> IO (Vector a)+copyVector vector =+ do array <- readIORef (vectorArrayRef vector)+ count <- readIORef (vectorCountRef vector)+ array' <- newArray_ (0, count - 1)+ arrayRef' <- newIORef array'+ countRef' <- newIORef count+ capacityRef' <- newIORef count+ forM_ [0 .. count - 1] $ \i ->+ do x <- readArray array i+ writeArray array' i x+ return Vector { vectorArrayRef = arrayRef',+ vectorCountRef = countRef',+ vectorCapacityRef = capacityRef' }++-- | Ensure that the vector has the specified capacity.+vectorEnsureCapacity :: Vector a -> Int -> IO ()+vectorEnsureCapacity vector capacity =+ do capacity' <- readIORef (vectorCapacityRef vector)+ when (capacity' < capacity) $+ do array' <- readIORef (vectorArrayRef vector)+ count' <- readIORef (vectorCountRef vector)+ let capacity'' = max (2 * capacity') capacity+ array'' <- newArray_ (0, capacity'' - 1)+ forM_ [0 .. count' - 1] $ \i ->+ do x <- readArray array' i+ writeArray array'' i x+ writeIORef (vectorArrayRef vector) array''+ writeIORef (vectorCapacityRef vector) capacity''+ +-- | Return the element count.+vectorCount :: Vector a -> IO Int+vectorCount vector = readIORef (vectorCountRef vector)+ +-- | Add the specified element to the end of the vector.+appendVector :: Vector a -> a -> IO () +appendVector vector item =+ do count <- readIORef (vectorCountRef vector)+ vectorEnsureCapacity vector (count + 1)+ array <- readIORef (vectorArrayRef vector)+ writeArray array count item+ writeIORef (vectorCountRef vector) (count + 1)+ +-- | Read a value from the vector, where indices are started from 0.+readVector :: Vector a -> Int -> IO a+readVector vector index =+ do array <- readIORef (vectorArrayRef vector)+ readArray array index+ +-- | Set an array item at the specified index which is started from 0.+writeVector :: Vector a -> Int -> a -> IO ()+writeVector vector index item =+ do array <- readIORef (vectorArrayRef vector)+ writeArray array index item++vectorBinarySearch' :: Ord a => IOArray Int a -> a -> Int -> Int -> IO Int+vectorBinarySearch' array item left right =+ if left > right + then return $ - (right + 1) - 1+ else+ do let index = (left + right) `div` 2+ curr <- readArray array index+ if item < curr + then vectorBinarySearch' array item left (index - 1)+ else if item == curr+ then return index+ else vectorBinarySearch' array item (index + 1) right+ +-- | Return the index of the specified element using binary search; otherwise, +-- a negated insertion index minus one: 0 -> -0 - 1, ..., i -> -i - 1, ....+vectorBinarySearch :: Ord a => Vector a -> a -> IO Int+vectorBinarySearch vector item =+ do array <- readIORef (vectorArrayRef vector)+ count <- readIORef (vectorCountRef vector)+ vectorBinarySearch' array item 0 (count - 1)++-- | Return the elements of the vector in an immutable array.+freezeVector :: Vector a -> IO (Array Int a)+freezeVector vector = + do vector' <- copyVector vector+ array <- readIORef (vectorArrayRef vector')+ freeze array+ +-- | Insert the element in the vector at the specified index.+vectorInsert :: Vector a -> Int -> a -> IO () +vectorInsert vector index item =+ do count <- readIORef (vectorCountRef vector)+ when (index < 0) $+ error $+ "Index cannot be " +++ "negative: vectorInsert."+ when (index > count) $+ error $+ "Index cannot be greater " +++ "than the count: vectorInsert."+ vectorEnsureCapacity vector (count + 1)+ array <- readIORef (vectorArrayRef vector)+ forM_ [count, count - 1 .. index + 1] $ \i ->+ do x <- readArray array (i - 1)+ writeArray array i x+ writeArray array index item+ writeIORef (vectorCountRef vector) (count + 1)+ +-- | Delete the element at the specified index.+vectorDeleteAt :: Vector a -> Int -> IO ()+vectorDeleteAt vector index =+ do count <- readIORef (vectorCountRef vector)+ when (index < 0) $+ error $+ "Index cannot be " +++ "negative: vectorDeleteAt."+ when (index >= count) $+ error $+ "Index must be less " +++ "than the count: vectorDeleteAt."+ array <- readIORef (vectorArrayRef vector)+ forM_ [index, index + 1 .. count - 2] $ \i ->+ do x <- readArray array (i + 1)+ writeArray array i x+ writeArray array (count - 1) undefined+ writeIORef (vectorCountRef vector) (count - 1)+ +-- | Return the index of the item or -1. +vectorIndex :: Eq a => Vector a -> a -> IO Int+vectorIndex vector item =+ do count <- readIORef (vectorCountRef vector)+ array <- readIORef (vectorArrayRef vector)+ let loop index =+ if index >= count+ then return $ -1+ else do x <- readArray array index+ if item == x+ then return index+ else loop $ index + 1+ loop 0
Simulation/Aivika/Vector/Unboxed.hs view
@@ -1,186 +1,186 @@- --- | --- Module : Simulation.Aivika.Vector.Unboxed --- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com> --- License : BSD3 --- Maintainer : David Sorokin <david.sorokin@gmail.com> --- Stability : experimental --- Tested with: GHC 7.8.3 --- --- An imperative unboxed vector. --- -module Simulation.Aivika.Vector.Unboxed - (Vector, - newVector, - copyVector, - vectorCount, - appendVector, - readVector, - writeVector, - vectorBinarySearch, - vectorInsert, - vectorDeleteAt, - vectorIndex, - freezeVector) where - -import Data.Array -import Data.Array.MArray.Safe -import Data.Array.IO.Safe -import Data.IORef -import Control.Monad - -import Simulation.Aivika.Unboxed - --- | Represents an unboxed resizable vector. -data Vector a = Vector { vectorArrayRef :: IORef (IOUArray Int a), - vectorCountRef :: IORef Int, - vectorCapacityRef :: IORef Int } - --- | Create a new vector. -newVector :: Unboxed a => IO (Vector a) -newVector = - do array <- newUnboxedArray_ (0, 4 - 1) - arrayRef <- newIORef array - countRef <- newIORef 0 - capacityRef <- newIORef 4 - return Vector { vectorArrayRef = arrayRef, - vectorCountRef = countRef, - vectorCapacityRef = capacityRef } - --- | Copy the vector. -copyVector :: Unboxed a => Vector a -> IO (Vector a) -copyVector vector = - do array <- readIORef (vectorArrayRef vector) - count <- readIORef (vectorCountRef vector) - array' <- newUnboxedArray_ (0, count - 1) - arrayRef' <- newIORef array' - countRef' <- newIORef count - capacityRef' <- newIORef count - forM_ [0 .. count - 1] $ \i -> - do x <- readArray array i - writeArray array' i x - return Vector { vectorArrayRef = arrayRef', - vectorCountRef = countRef', - vectorCapacityRef = capacityRef' } - --- | Ensure that the vector has the specified capacity. -vectorEnsureCapacity :: Unboxed a => Vector a -> Int -> IO () -vectorEnsureCapacity vector capacity = - do capacity' <- readIORef (vectorCapacityRef vector) - when (capacity' < capacity) $ - do array' <- readIORef (vectorArrayRef vector) - count' <- readIORef (vectorCountRef vector) - let capacity'' = max (2 * capacity') capacity - array'' <- newUnboxedArray_ (0, capacity'' - 1) - forM_ [0 .. count' - 1] $ \i -> - do x <- readArray array' i - writeArray array'' i x - writeIORef (vectorArrayRef vector) array'' - writeIORef (vectorCapacityRef vector) capacity'' - --- | Return the element count. -vectorCount :: Unboxed a => Vector a -> IO Int -vectorCount vector = readIORef (vectorCountRef vector) - --- | Add the specified element to the end of the vector. -appendVector :: Unboxed a => Vector a -> a -> IO () -appendVector vector item = - do count <- readIORef (vectorCountRef vector) - vectorEnsureCapacity vector (count + 1) - array <- readIORef (vectorArrayRef vector) - writeArray array count item - writeIORef (vectorCountRef vector) (count + 1) - --- | Read a value from the vector, where indices are started from 0. -readVector :: Unboxed a => Vector a -> Int -> IO a -readVector vector index = - do array <- readIORef (vectorArrayRef vector) - readArray array index - --- | Set an array item at the specified index which is started from 0. -writeVector :: Unboxed a => Vector a -> Int -> a -> IO () -writeVector vector index item = - do array <- readIORef (vectorArrayRef vector) - writeArray array index item - -vectorBinarySearch' :: (Unboxed a, Ord a) => IOUArray Int a -> a -> Int -> Int -> IO Int -vectorBinarySearch' array item left right = - if left > right - then return $ - (right + 1) - 1 - else - do let index = (left + right) `div` 2 - curr <- readArray array index - if item < curr - then vectorBinarySearch' array item left (index - 1) - else if item == curr - then return index - else vectorBinarySearch' array item (index + 1) right - --- | Return the index of the specified element using binary search; otherwise, --- a negated insertion index minus one: 0 -> -0 - 1, ..., i -> -i - 1, .... -vectorBinarySearch :: (Unboxed a, Ord a) => Vector a -> a -> IO Int -vectorBinarySearch vector item = - do array <- readIORef (vectorArrayRef vector) - count <- readIORef (vectorCountRef vector) - vectorBinarySearch' array item 0 (count - 1) - --- | Return the elements of the vector in an immutable array. -freezeVector :: Unboxed a => Vector a -> IO (Array Int a) -freezeVector vector = - do vector' <- copyVector vector - array <- readIORef (vectorArrayRef vector') - freeze array - --- | Insert the element in the vector at the specified index. -vectorInsert :: Unboxed a => Vector a -> Int -> a -> IO () -vectorInsert vector index item = - do count <- readIORef (vectorCountRef vector) - when (index < 0) $ - error $ - "Index cannot be " ++ - "negative: vectorInsert." - when (index > count) $ - error $ - "Index cannot be greater " ++ - "than the count: vectorInsert." - vectorEnsureCapacity vector (count + 1) - array <- readIORef (vectorArrayRef vector) - forM_ [count, count - 1 .. index + 1] $ \i -> - do x <- readArray array (i - 1) - writeArray array i x - writeArray array index item - writeIORef (vectorCountRef vector) (count + 1) - --- | Delete the element at the specified index. -vectorDeleteAt :: Unboxed a => Vector a -> Int -> IO () -vectorDeleteAt vector index = - do count <- readIORef (vectorCountRef vector) - when (index < 0) $ - error $ - "Index cannot be " ++ - "negative: vectorDeleteAt." - when (index >= count) $ - error $ - "Index must be less " ++ - "than the count: vectorDeleteAt." - array <- readIORef (vectorArrayRef vector) - forM_ [index, index + 1 .. count - 2] $ \i -> - do x <- readArray array (i + 1) - writeArray array i x - writeArray array (count - 1) undefined - writeIORef (vectorCountRef vector) (count - 1) - --- | Return the index of the item or -1. -vectorIndex :: (Unboxed a, Eq a) => Vector a -> a -> IO Int -vectorIndex vector item = - do count <- readIORef (vectorCountRef vector) - array <- readIORef (vectorArrayRef vector) - let loop index = - if index >= count - then return $ -1 - else do x <- readArray array index - if item == x - then return index - else loop $ index + 1 - loop 0 - ++-- |+-- Module : Simulation.Aivika.Vector.Unboxed+-- Copyright : Copyright (c) 2009-2014, David Sorokin <david.sorokin@gmail.com>+-- License : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability : experimental+-- Tested with: GHC 7.8.3+--+-- An imperative unboxed vector.+--+module Simulation.Aivika.Vector.Unboxed+ (Vector, + newVector, + copyVector, + vectorCount, + appendVector, + readVector, + writeVector, + vectorBinarySearch,+ vectorInsert,+ vectorDeleteAt,+ vectorIndex,+ freezeVector) where ++import Data.Array+import Data.Array.MArray.Safe+import Data.Array.IO.Safe+import Data.IORef+import Control.Monad++import Simulation.Aivika.Unboxed++-- | Represents an unboxed resizable vector.+data Vector a = Vector { vectorArrayRef :: IORef (IOUArray Int a),+ vectorCountRef :: IORef Int, + vectorCapacityRef :: IORef Int }++-- | Create a new vector.+newVector :: Unboxed a => IO (Vector a)+newVector = + do array <- newUnboxedArray_ (0, 4 - 1)+ arrayRef <- newIORef array+ countRef <- newIORef 0+ capacityRef <- newIORef 4+ return Vector { vectorArrayRef = arrayRef,+ vectorCountRef = countRef,+ vectorCapacityRef = capacityRef }++-- | Copy the vector.+copyVector :: Unboxed a => Vector a -> IO (Vector a)+copyVector vector =+ do array <- readIORef (vectorArrayRef vector)+ count <- readIORef (vectorCountRef vector)+ array' <- newUnboxedArray_ (0, count - 1)+ arrayRef' <- newIORef array'+ countRef' <- newIORef count+ capacityRef' <- newIORef count+ forM_ [0 .. count - 1] $ \i ->+ do x <- readArray array i+ writeArray array' i x+ return Vector { vectorArrayRef = arrayRef',+ vectorCountRef = countRef',+ vectorCapacityRef = capacityRef' }++-- | Ensure that the vector has the specified capacity.+vectorEnsureCapacity :: Unboxed a => Vector a -> Int -> IO ()+vectorEnsureCapacity vector capacity =+ do capacity' <- readIORef (vectorCapacityRef vector)+ when (capacity' < capacity) $+ do array' <- readIORef (vectorArrayRef vector)+ count' <- readIORef (vectorCountRef vector)+ let capacity'' = max (2 * capacity') capacity+ array'' <- newUnboxedArray_ (0, capacity'' - 1)+ forM_ [0 .. count' - 1] $ \i ->+ do x <- readArray array' i+ writeArray array'' i x+ writeIORef (vectorArrayRef vector) array''+ writeIORef (vectorCapacityRef vector) capacity''+ +-- | Return the element count.+vectorCount :: Unboxed a => Vector a -> IO Int+vectorCount vector = readIORef (vectorCountRef vector)+ +-- | Add the specified element to the end of the vector.+appendVector :: Unboxed a => Vector a -> a -> IO () +appendVector vector item =+ do count <- readIORef (vectorCountRef vector)+ vectorEnsureCapacity vector (count + 1)+ array <- readIORef (vectorArrayRef vector)+ writeArray array count item+ writeIORef (vectorCountRef vector) (count + 1)+ +-- | Read a value from the vector, where indices are started from 0.+readVector :: Unboxed a => Vector a -> Int -> IO a+readVector vector index =+ do array <- readIORef (vectorArrayRef vector)+ readArray array index+ +-- | Set an array item at the specified index which is started from 0.+writeVector :: Unboxed a => Vector a -> Int -> a -> IO ()+writeVector vector index item =+ do array <- readIORef (vectorArrayRef vector)+ writeArray array index item+ +vectorBinarySearch' :: (Unboxed a, Ord a) => IOUArray Int a -> a -> Int -> Int -> IO Int+vectorBinarySearch' array item left right =+ if left > right + then return $ - (right + 1) - 1+ else+ do let index = (left + right) `div` 2+ curr <- readArray array index+ if item < curr + then vectorBinarySearch' array item left (index - 1)+ else if item == curr+ then return index+ else vectorBinarySearch' array item (index + 1) right+ +-- | Return the index of the specified element using binary search; otherwise, +-- a negated insertion index minus one: 0 -> -0 - 1, ..., i -> -i - 1, ....+vectorBinarySearch :: (Unboxed a, Ord a) => Vector a -> a -> IO Int+vectorBinarySearch vector item =+ do array <- readIORef (vectorArrayRef vector)+ count <- readIORef (vectorCountRef vector)+ vectorBinarySearch' array item 0 (count - 1)++-- | Return the elements of the vector in an immutable array.+freezeVector :: Unboxed a => Vector a -> IO (Array Int a)+freezeVector vector = + do vector' <- copyVector vector+ array <- readIORef (vectorArrayRef vector')+ freeze array+ +-- | Insert the element in the vector at the specified index.+vectorInsert :: Unboxed a => Vector a -> Int -> a -> IO () +vectorInsert vector index item =+ do count <- readIORef (vectorCountRef vector)+ when (index < 0) $+ error $+ "Index cannot be " +++ "negative: vectorInsert."+ when (index > count) $+ error $+ "Index cannot be greater " +++ "than the count: vectorInsert."+ vectorEnsureCapacity vector (count + 1)+ array <- readIORef (vectorArrayRef vector)+ forM_ [count, count - 1 .. index + 1] $ \i ->+ do x <- readArray array (i - 1)+ writeArray array i x+ writeArray array index item+ writeIORef (vectorCountRef vector) (count + 1)+ +-- | Delete the element at the specified index.+vectorDeleteAt :: Unboxed a => Vector a -> Int -> IO ()+vectorDeleteAt vector index =+ do count <- readIORef (vectorCountRef vector)+ when (index < 0) $+ error $+ "Index cannot be " +++ "negative: vectorDeleteAt."+ when (index >= count) $+ error $+ "Index must be less " +++ "than the count: vectorDeleteAt."+ array <- readIORef (vectorArrayRef vector)+ forM_ [index, index + 1 .. count - 2] $ \i ->+ do x <- readArray array (i + 1)+ writeArray array i x+ writeArray array (count - 1) undefined+ writeIORef (vectorCountRef vector) (count - 1)+ +-- | Return the index of the item or -1. +vectorIndex :: (Unboxed a, Eq a) => Vector a -> a -> IO Int+vectorIndex vector item =+ do count <- readIORef (vectorCountRef vector)+ array <- readIORef (vectorArrayRef vector)+ let loop index =+ if index >= count+ then return $ -1+ else do x <- readArray array index+ if item == x+ then return index+ else loop $ index + 1+ loop 0+
aivika.cabal view
@@ -1,203 +1,205 @@-name: aivika -version: 2.0 -synopsis: A multi-paradigm simulation library -description: - Aivika is a multi-paradigm simulation library with a strong emphasis - on Discrete Event Simulation (DES) and System Dynamics (SD). - . - The library has the following features: - . - * allows defining recursive stochastic differential equations of - System Dynamics (unordered as in maths via the recursive do-notation); - . - * supports the event-driven paradigm of DES as a basic core for - implementing other paradigms; - . - * supports extensively the process-oriented paradigm of DES - with an ability to resume, suspend and cancel - the discontinuous processes; - . - * allows working with the resources based on specified queue strategies - (FCFS\/FIFO, LCFS\/LIFO, SIRO, static priorities and so on); - . - * allows customizing the infinite and finite queues based on strategies too; - . - * allows defining a queue network based on infinite streams of data - and their processors, where we can define a complex enough - behaviour just in a few lines of code; - . - * allows simulating circuits with recursive links and delays; - . - * supports the activity-oriented paradigm of DES; - . - * supports the basic constructs for the agent-based modeling; - . - * allows creating combined discrete-continuous models as all parts - of the library are well integrated and this is reflected directly - in the type system; - . - * the arrays of simulation variables are inherently supported; - . - * supports the Monte-Carlo simulation; - . - * the simulation model can depend on external parameters; - . - * uses extensively signals for notification; - . - * allows gathering statistics in time points; - . - * hides technical details in high-level simulation computations - (monads and arrows). - . - Aivika itself is a light-weight engine with minimal dependencies. - However, it has additional packages Aivika Experiment [1] and - Aivika Experiment Chart [2] that offer the following features: - . - * automating the simulation experiments; - . - * saving the results in CSV files; - . - * plotting the deviation chart by rule 3-sigma, histogram, - time series, XY chart; - . - * collecting the summary of statistical data; - . - * parallel execution of the Monte-Carlo simulation; - . - * have an extensible architecture. - . - All three libraries were tested on Linux, Windows and OS X. - . - The PDF documentation is available on the Aivika Wiki [3] website. - . - \[1] <http://hackage.haskell.org/package/aivika-experiment> - . - \[2] <http://hackage.haskell.org/package/aivika-experiment-chart> - . - \[3] <https://github.com/dsorokin/aivika/wiki> - . - P.S. Aivika is actually a genuine female Mari name which is pronounced - with stress on the last syllable. -category: Simulation -license: BSD3 -license-file: LICENSE -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 -cabal-version: >= 1.10 -build-type: Simple -tested-with: GHC == 7.8.3 - -extra-source-files: examples/BassDiffusion.hs - examples/ChemicalReaction.hs - examples/ChemicalReactionCircuit.hs - examples/FishBank.hs - examples/MachRep1.hs - examples/MachRep1EventDriven.hs - examples/MachRep1TimeDriven.hs - examples/MachRep2.hs - examples/MachRep3.hs - examples/Furnace.hs - examples/InspectionAdjustmentStations.hs - examples/WorkStationsInSeries.hs - examples/TimeOut.hs - examples/TimeOutInt.hs - examples/TimeOutWait.hs - -flag haste-inst - - description: The package is built using haste-inst - default: False - -library - - exposed-modules: Simulation.Aivika - Simulation.Aivika.Agent - Simulation.Aivika.Arrival - Simulation.Aivika.Circuit - Simulation.Aivika.Cont - Simulation.Aivika.DoubleLinkedList - Simulation.Aivika.Dynamics - Simulation.Aivika.Dynamics.Extra - Simulation.Aivika.Dynamics.Memo - Simulation.Aivika.Dynamics.Memo.Unboxed - Simulation.Aivika.Dynamics.Random - Simulation.Aivika.Event - Simulation.Aivika.Generator - Simulation.Aivika.Net - 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.Ref - Simulation.Aivika.Ref.Plain - Simulation.Aivika.Resource - Simulation.Aivika.Results.Locale - Simulation.Aivika.Results - Simulation.Aivika.Results.IO - Simulation.Aivika.Server - Simulation.Aivika.Signal - Simulation.Aivika.Simulation - Simulation.Aivika.Specs - Simulation.Aivika.Statistics - Simulation.Aivika.Statistics.Accumulator - Simulation.Aivika.Stream - Simulation.Aivika.Stream.Random - Simulation.Aivika.SystemDynamics - Simulation.Aivika.Table - Simulation.Aivika.Task - Simulation.Aivika.Transform - Simulation.Aivika.Transform.Extra - Simulation.Aivika.Transform.Memo - Simulation.Aivika.Transform.Memo.Unboxed - Simulation.Aivika.Unboxed - Simulation.Aivika.Var - Simulation.Aivika.Var.Unboxed - Simulation.Aivika.Vector - Simulation.Aivika.Vector.Unboxed - - 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 - Simulation.Aivika.Internal.Specs - Simulation.Aivika.Internal.Arrival - - build-depends: base >= 4.5.0.0 && < 6, - mtl >= 2.1.1, - array >= 0.3.0.0, - containers >= 0.4.0.0, - random >= 1.0.0.3 - - if !flag(haste-inst) - build-depends: vector >= 0.10.0.1 - - other-extensions: FlexibleContexts, - FlexibleInstances, - UndecidableInstances, - BangPatterns, - RecursiveDo, - Arrows, - MultiParamTypeClasses, - FunctionalDependencies, - ExistentialQuantification, - TypeFamilies, - CPP - - ghc-options: -O2 - - default-language: Haskell2010 - -source-repository head - - type: git - location: https://github.com/dsorokin/aivika +name: aivika+version: 2.1+synopsis: A multi-paradigm simulation library+description:+ Aivika is a multi-paradigm simulation library with a strong emphasis+ on Discrete Event Simulation (DES) and System Dynamics (SD).+ .+ The library has the following features:+ .+ * allows defining recursive stochastic differential equations of + System Dynamics (unordered as in maths via the recursive do-notation);+ .+ * supports the event-driven paradigm of DES as a basic core for + implementing other paradigms;+ .+ * supports extensively the process-oriented paradigm of DES+ with an ability to resume, suspend and cancel + the discontinuous processes;+ .+ * allows working with the resources based on specified queue strategies + (FCFS\/FIFO, LCFS\/LIFO, SIRO, static priorities and so on);+ .+ * allows customizing the infinite and finite queues based on strategies too;+ .+ * allows defining a queue network based on infinite streams of data+ and their processors, where we can define a complex enough+ behaviour just in a few lines of code;+ .+ * allows simulating circuits with recursive links and delays;+ .+ * supports the activity-oriented paradigm of DES;+ .+ * supports the basic constructs for the agent-based modeling;+ .+ * allows creating combined discrete-continuous models as all parts+ of the library are well integrated and this is reflected directly + in the type system;+ .+ * the arrays of simulation variables are inherently supported;+ .+ * supports the Monte-Carlo simulation;+ .+ * the simulation model can depend on external parameters;+ .+ * uses extensively signals for notification;+ .+ * allows gathering statistics in time points;+ .+ * hides technical details in high-level simulation computations+ (monads and arrows).+ .+ Aivika itself is a light-weight engine with minimal dependencies. + However, it has additional packages Aivika Experiment [1] and + Aivika Experiment Chart [2] that offer the following features:+ .+ * automating the simulation experiments;+ .+ * saving the results in CSV files;+ .+ * plotting the deviation chart by rule 3-sigma, histogram, + time series, XY chart;+ .+ * collecting the summary of statistical data;+ .+ * parallel execution of the Monte-Carlo simulation;+ .+ * have an extensible architecture.+ .+ All three libraries were tested on Linux, Windows and OS X.+ .+ The PDF documentation is available on the Aivika Wiki [3] website.+ .+ \[1] <http://hackage.haskell.org/package/aivika-experiment>+ .+ \[2] <http://hackage.haskell.org/package/aivika-experiment-chart>+ .+ \[3] <https://github.com/dsorokin/aivika/wiki>+ .+ P.S. Aivika is actually a genuine female Mari name which is pronounced + with stress on the last syllable.+category: Simulation+license: BSD3+license-file: LICENSE+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+cabal-version: >= 1.10+build-type: Simple+tested-with: GHC == 7.8.3++extra-source-files: examples/BassDiffusion.hs+ examples/ChemicalReaction.hs+ examples/ChemicalReactionCircuit.hs+ examples/FishBank.hs+ examples/MachRep1.hs+ examples/MachRep1EventDriven.hs+ examples/MachRep1TimeDriven.hs+ examples/MachRep2.hs+ examples/MachRep3.hs+ examples/Furnace.hs+ examples/InspectionAdjustmentStations.hs+ examples/WorkStationsInSeries.hs+ examples/QuarryOperations.hs+ examples/TimeOut.hs+ examples/TimeOutInt.hs+ examples/TimeOutWait.hs++flag haste-inst+ + description: The package is built using haste-inst+ default: False++library++ exposed-modules: Simulation.Aivika+ Simulation.Aivika.Activity+ Simulation.Aivika.Agent+ Simulation.Aivika.Arrival+ Simulation.Aivika.Circuit+ Simulation.Aivika.Cont+ Simulation.Aivika.DoubleLinkedList+ Simulation.Aivika.Dynamics+ Simulation.Aivika.Dynamics.Extra+ Simulation.Aivika.Dynamics.Memo+ Simulation.Aivika.Dynamics.Memo.Unboxed+ Simulation.Aivika.Dynamics.Random+ Simulation.Aivika.Event+ Simulation.Aivika.Generator+ Simulation.Aivika.Net+ 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.Ref+ Simulation.Aivika.Ref.Plain+ Simulation.Aivika.Resource+ Simulation.Aivika.Results.Locale+ Simulation.Aivika.Results+ Simulation.Aivika.Results.IO+ Simulation.Aivika.Server+ Simulation.Aivika.Signal+ Simulation.Aivika.Simulation+ Simulation.Aivika.Specs+ Simulation.Aivika.Statistics+ Simulation.Aivika.Statistics.Accumulator+ Simulation.Aivika.Stream+ Simulation.Aivika.Stream.Random+ Simulation.Aivika.SystemDynamics+ Simulation.Aivika.Table+ Simulation.Aivika.Task+ Simulation.Aivika.Transform+ Simulation.Aivika.Transform.Extra+ Simulation.Aivika.Transform.Memo+ Simulation.Aivika.Transform.Memo.Unboxed+ Simulation.Aivika.Unboxed+ Simulation.Aivika.Var+ Simulation.Aivika.Var.Unboxed+ Simulation.Aivika.Vector+ Simulation.Aivika.Vector.Unboxed++ 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+ Simulation.Aivika.Internal.Specs+ Simulation.Aivika.Internal.Arrival+ + build-depends: base >= 4.5.0.0 && < 6,+ mtl >= 2.1.1,+ array >= 0.3.0.0,+ containers >= 0.4.0.0,+ random >= 1.0.0.3++ if !flag(haste-inst)+ build-depends: vector >= 0.10.0.1++ other-extensions: FlexibleContexts,+ FlexibleInstances,+ UndecidableInstances,+ BangPatterns,+ RecursiveDo,+ Arrows,+ MultiParamTypeClasses,+ FunctionalDependencies,+ ExistentialQuantification,+ TypeFamilies,+ CPP+ + ghc-options: -O2++ default-language: Haskell2010++source-repository head++ type: git+ location: https://github.com/dsorokin/aivika
examples/BassDiffusion.hs view
@@ -1,104 +1,104 @@- --- This is the Bass Diffusion model solved with help of --- the Agent-based Modeling as described in the AnyLogic --- documentation. - -import Data.Array - -import Control.Monad -import Control.Monad.Trans - -import Simulation.Aivika - -n = 500 -- the number of agents - -advertisingEffectiveness = 0.011 -contactRate = 100.0 -adoptionFraction = 0.015 - -specs = Specs { spcStartTime = 0.0, - spcStopTime = 8.0, - spcDT = 0.1, - spcMethod = RungeKutta4, - spcGeneratorType = SimpleGenerator } - -data Person = Person { personAgent :: Agent, - personPotentialAdopter :: AgentState, - personAdopter :: AgentState } - -createPerson :: Simulation Person -createPerson = - do agent <- newAgent - potentialAdopter <- newState agent - adopter <- newState agent - return Person { personAgent = agent, - personPotentialAdopter = potentialAdopter, - personAdopter = adopter } - -createPersons :: Simulation (Array Int Person) -createPersons = - do list <- forM [1 .. n] $ \i -> - do p <- createPerson - return (i, p) - return $ array (1, n) list - -definePerson :: Person -> Array Int Person -> Ref Int -> Ref Int -> Simulation () -definePerson p ps potentialAdopters adopters = - do setStateActivation (personPotentialAdopter p) $ - do modifyRef potentialAdopters $ \a -> a + 1 - -- add a timeout - t <- liftParameter $ - randomExponential (1 / advertisingEffectiveness) - let st = personPotentialAdopter p - st' = personAdopter p - 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 = liftParameter $ - randomExponential (1 / contactRate) -- many times! - addTimer (personAdopter p) t $ - do i <- liftParameter $ - randomUniformInt 1 n - let p' = ps ! i - st <- selectedState (personAgent p') - when (st == Just (personPotentialAdopter p')) $ - do b <- liftParameter $ - randomTrue adoptionFraction - when b $ selectState (personAdopter p') - setStateDeactivation (personPotentialAdopter p) $ - modifyRef potentialAdopters $ \a -> a - 1 - setStateDeactivation (personAdopter p) $ - modifyRef adopters $ \a -> a - 1 - -definePersons :: Array Int Person -> Ref Int -> Ref Int -> Simulation () -definePersons ps potentialAdopters adopters = - forM_ (elems ps) $ \p -> - definePerson p ps potentialAdopters adopters - -activatePerson :: Person -> Event () -activatePerson p = selectState (personPotentialAdopter p) - -activatePersons :: Array Int Person -> Event () -activatePersons ps = - forM_ (elems ps) $ \p -> activatePerson p - -model :: Simulation Results -model = - do potentialAdopters <- newRef 0 - adopters <- newRef 0 - ps <- createPersons - definePersons ps potentialAdopters adopters - runEventInStartTime $ - activatePersons ps - return $ - results - [resultSource - "potentialAdopter" "potential adopters" potentialAdopters, - resultSource - "adopters" "adopters" adopters] - -main = - printSimulationResultsInIntegTimes - printResultSourceInEnglish - model specs ++-- This is the Bass Diffusion model solved with help of +-- the Agent-based Modeling as described in the AnyLogic +-- documentation.++import Data.Array++import Control.Monad+import Control.Monad.Trans++import Simulation.Aivika++n = 500 -- the number of agents++advertisingEffectiveness = 0.011+contactRate = 100.0+adoptionFraction = 0.015++specs = Specs { spcStartTime = 0.0, + spcStopTime = 8.0,+ spcDT = 0.1,+ spcMethod = RungeKutta4,+ spcGeneratorType = SimpleGenerator }++data Person = Person { personAgent :: Agent,+ personPotentialAdopter :: AgentState,+ personAdopter :: AgentState }+ +createPerson :: Simulation Person +createPerson = + do agent <- newAgent+ potentialAdopter <- newState agent+ adopter <- newState agent+ return Person { personAgent = agent,+ personPotentialAdopter = potentialAdopter,+ personAdopter = adopter }+ +createPersons :: Simulation (Array Int Person)+createPersons =+ do list <- forM [1 .. n] $ \i ->+ do p <- createPerson+ return (i, p)+ return $ array (1, n) list+ +definePerson :: Person -> Array Int Person -> Ref Int -> Ref Int -> Simulation ()+definePerson p ps potentialAdopters adopters =+ do setStateActivation (personPotentialAdopter p) $+ do modifyRef potentialAdopters $ \a -> a + 1+ -- add a timeout+ t <- liftParameter $+ randomExponential (1 / advertisingEffectiveness) + let st = personPotentialAdopter p+ st' = personAdopter p+ 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 = liftParameter $+ randomExponential (1 / contactRate) -- many times!+ addTimer (personAdopter p) t $+ do i <- liftParameter $+ randomUniformInt 1 n+ let p' = ps ! i+ st <- selectedState (personAgent p')+ when (st == Just (personPotentialAdopter p')) $+ do b <- liftParameter $+ randomTrue adoptionFraction+ when b $ selectState (personAdopter p')+ setStateDeactivation (personPotentialAdopter p) $+ modifyRef potentialAdopters $ \a -> a - 1+ setStateDeactivation (personAdopter p) $+ modifyRef adopters $ \a -> a - 1+ +definePersons :: Array Int Person -> Ref Int -> Ref Int -> Simulation ()+definePersons ps potentialAdopters adopters =+ forM_ (elems ps) $ \p -> + definePerson p ps potentialAdopters adopters+ +activatePerson :: Person -> Event ()+activatePerson p = selectState (personPotentialAdopter p)++activatePersons :: Array Int Person -> Event ()+activatePersons ps =+ forM_ (elems ps) $ \p -> activatePerson p++model :: Simulation Results+model =+ do potentialAdopters <- newRef 0+ adopters <- newRef 0+ ps <- createPersons+ definePersons ps potentialAdopters adopters+ runEventInStartTime $+ activatePersons ps+ return $ + results+ [resultSource + "potentialAdopter" "potential adopters" potentialAdopters,+ resultSource + "adopters" "adopters" adopters]++main =+ printSimulationResultsInIntegTimes+ printResultSourceInEnglish+ model specs
examples/ChemicalReaction.hs view
@@ -1,30 +1,30 @@- -{-# LANGUAGE RecursiveDo #-} - -import Simulation.Aivika -import Simulation.Aivika.SystemDynamics - -import qualified Data.Vector as V - -specs = Specs { spcStartTime = 0, - spcStopTime = 13, - spcDT = 0.01, - spcMethod = RungeKutta4, - spcGeneratorType = SimpleGenerator } - -model :: Simulation Results -model = - mdo a <- integ (- ka * a) 100 - b <- integ (ka * a - kb * b) 0 - c <- integ (kb * b) 0 - let ka = 1 - kb = 1 - return $ results - [resultSource "a" "variable A" a, - resultSource "b" "variable B" b, - resultSource "c" "variable C" c] - -main = - printSimulationResultsInStopTime - printResultSourceInEnglish - model specs ++{-# LANGUAGE RecursiveDo #-}++import Simulation.Aivika+import Simulation.Aivika.SystemDynamics++import qualified Data.Vector as V++specs = Specs { spcStartTime = 0, + spcStopTime = 13, + spcDT = 0.01,+ spcMethod = RungeKutta4,+ spcGeneratorType = SimpleGenerator }++model :: Simulation Results+model = + mdo a <- integ (- ka * a) 100+ b <- integ (ka * a - kb * b) 0+ c <- integ (kb * b) 0+ let ka = 1+ kb = 1+ return $ results+ [resultSource "a" "variable A" a,+ resultSource "b" "variable B" b,+ resultSource "c" "variable C" c]++main =+ printSimulationResultsInStopTime+ printResultSourceInEnglish+ model specs
examples/ChemicalReactionCircuit.hs view
@@ -1,43 +1,43 @@- --- Note that the integCircut function uses Euler's method regardless of --- the simulation specs specified. Therefore, to receieve almost the same --- results in the old example based on using the integ function, you should --- specify Euler's method in their specs in that file, although the Runge-Kutta --- method gives similar results too, which is expected. --- --- Finally, the integ function can be significantly faster than integCircuit, --- although they have different purposes. - -{-# LANGUAGE Arrows #-} - -import Control.Arrow - -import Simulation.Aivika - -specs = Specs { spcStartTime = 0, - spcStopTime = 13, - spcDT = 0.01, - spcMethod = RungeKutta4, - spcGeneratorType = SimpleGenerator } - -circuit :: Circuit () [Double] -circuit = - let ka = 1 - kb = 1 - in proc () -> do - rec let da = - ka * a - db = ka * a - kb * b - dc = kb * b - a <- integCircuit 100 -< da - b <- integCircuit 0 -< db - c <- integCircuit 0 -< dc - returnA -< [a, b, c] - -model :: Simulation [Double] -model = - do results <- - runTransform (circuitTransform circuit) $ - return () - runDynamicsInStopTime results - -main = runSimulation model specs >>= print ++-- Note that the integCircut function uses Euler's method regardless of+-- the simulation specs specified. Therefore, to receieve almost the same+-- results in the old example based on using the integ function, you should+-- specify Euler's method in their specs in that file, although the Runge-Kutta+-- method gives similar results too, which is expected.+--+-- Finally, the integ function can be significantly faster than integCircuit,+-- although they have different purposes.++{-# LANGUAGE Arrows #-}++import Control.Arrow++import Simulation.Aivika++specs = Specs { spcStartTime = 0, + spcStopTime = 13, + spcDT = 0.01,+ spcMethod = RungeKutta4,+ spcGeneratorType = SimpleGenerator }++circuit :: Circuit () [Double]+circuit =+ let ka = 1+ kb = 1+ in proc () -> do+ rec let da = - ka * a+ db = ka * a - kb * b+ dc = kb * b+ a <- integCircuit 100 -< da+ b <- integCircuit 0 -< db+ c <- integCircuit 0 -< dc+ returnA -< [a, b, c]++model :: Simulation [Double]+model =+ do results <-+ runTransform (circuitTransform circuit) $+ return ()+ runDynamicsInStopTime results++main = runSimulation model specs >>= print
examples/FishBank.hs view
@@ -1,60 +1,60 @@- -{-# LANGUAGE RecursiveDo #-} - -import Data.Array - -import Simulation.Aivika -import Simulation.Aivika.SystemDynamics - -specs = Specs { spcStartTime = 0, - spcStopTime = 13, - spcDT = 0.01, - -- spcDT = 0.000005, - spcMethod = RungeKutta4, - spcGeneratorType = SimpleGenerator } - -model :: Simulation Results -model = - mdo let annualProfit = profit - area = 100 - carryingCapacity = 1000 - catchPerShip = - lookupDynamics density $ - listArray (1, 11) [(0.0, -0.048), (1.2, 10.875), (2.4, 17.194), - (3.6, 20.548), (4.8, 22.086), (6.0, 23.344), - (7.2, 23.903), (8.4, 24.462), (9.6, 24.882), - (10.8, 25.301), (12.0, 25.86)] - deathFraction = - lookupDynamics (fish / carryingCapacity) $ - listArray (1, 11) [(0.0, 5.161), (0.1, 5.161), (0.2, 5.161), - (0.3, 5.161), (0.4, 5.161), (0.5, 5.161), - (0.6, 5.118), (0.7, 5.247), (0.8, 5.849), - (0.9, 6.151), (10.0, 6.194)] - density = fish / area - fish <- integ (fishHatchRate - fishDeathRate - totalCatchPerYear) 1000 - let fishDeathRate = maxDynamics 0 (fish * deathFraction) - fishHatchRate = maxDynamics 0 (fish * hatchFraction) - fishPrice = 20 - fractionInvested = 0.2 - hatchFraction = 6 - operatingCost = ships * 250 - profit = revenue - operatingCost - revenue = totalCatchPerYear * fishPrice - ships <- integ shipBuildingRate 10 - let shipBuildingRate = maxDynamics 0 (profit * fractionInvested / shipCost) - shipCost = 300 - totalProfit <- integ annualProfit 0 - let totalCatchPerYear = maxDynamics 0 (ships * catchPerShip) - -- results -- - return $ results - [resultSource "fish" "fish" fish, - resultSource "annualProfit" "the annual profit" annualProfit, - resultSource "totalProfit" "the total profit" totalProfit] - -main = - flip runSimulation specs $ - model >>= \results -> do - printResultsInStartTime - printResultSourceInEnglish results - printResultsInStopTime - printResultSourceInEnglish results ++{-# LANGUAGE RecursiveDo #-}++import Data.Array++import Simulation.Aivika+import Simulation.Aivika.SystemDynamics++specs = Specs { spcStartTime = 0, + spcStopTime = 13, + spcDT = 0.01,+ -- spcDT = 0.000005,+ spcMethod = RungeKutta4,+ spcGeneratorType = SimpleGenerator }++model :: Simulation Results+model =+ mdo let annualProfit = profit+ area = 100+ carryingCapacity = 1000+ catchPerShip = + lookupDynamics density $+ listArray (1, 11) [(0.0, -0.048), (1.2, 10.875), (2.4, 17.194), + (3.6, 20.548), (4.8, 22.086), (6.0, 23.344), + (7.2, 23.903), (8.4, 24.462), (9.6, 24.882), + (10.8, 25.301), (12.0, 25.86)]+ deathFraction = + lookupDynamics (fish / carryingCapacity) $+ listArray (1, 11) [(0.0, 5.161), (0.1, 5.161), (0.2, 5.161), + (0.3, 5.161), (0.4, 5.161), (0.5, 5.161), + (0.6, 5.118), (0.7, 5.247), (0.8, 5.849), + (0.9, 6.151), (10.0, 6.194)]+ density = fish / area+ fish <- integ (fishHatchRate - fishDeathRate - totalCatchPerYear) 1000+ let fishDeathRate = maxDynamics 0 (fish * deathFraction)+ fishHatchRate = maxDynamics 0 (fish * hatchFraction)+ fishPrice = 20+ fractionInvested = 0.2+ hatchFraction = 6+ operatingCost = ships * 250+ profit = revenue - operatingCost+ revenue = totalCatchPerYear * fishPrice+ ships <- integ shipBuildingRate 10+ let shipBuildingRate = maxDynamics 0 (profit * fractionInvested / shipCost)+ shipCost = 300+ totalProfit <- integ annualProfit 0+ let totalCatchPerYear = maxDynamics 0 (ships * catchPerShip)+ -- results --+ return $ results+ [resultSource "fish" "fish" fish,+ resultSource "annualProfit" "the annual profit" annualProfit,+ resultSource "totalProfit" "the total profit" totalProfit]++main =+ flip runSimulation specs $+ model >>= \results -> do+ printResultsInStartTime+ printResultSourceInEnglish results+ printResultsInStopTime+ printResultSourceInEnglish results
examples/Furnace.hs view
@@ -1,323 +1,323 @@- --- This is a model of the Furnace. It is described in different sources [1, 2]. --- --- [1] A. Alan B. Pritsker, Simulation with Visual SLAM and AweSim, 2nd ed. --- --- [2] Труб И.И., Объектно-ориентированное моделирование на C++: Учебный курс. - СПб.: Питер, 2006 - -import Data.Maybe -import System.Random -import Control.Monad -import Control.Monad.Trans - -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, - spcGeneratorType = SimpleGenerator } - --- | Return a random initial temperature of the item. -randomTemp :: Parameter Double -randomTemp = randomUniform 400 600 - --- | Represents the furnace. -data Furnace = - Furnace { furnacePits :: [Pit], - -- ^ The pits for ingots. - furnacePitCount :: Ref Int, - -- ^ The count of active pits with 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. - 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), - -- ^ The ingot in the pit. - pitTemp :: Ref Double - -- ^ The ingot temperature in the pit. - } - -data Ingot = - Ingot { ingotFurnace :: Furnace, - -- ^ The furnace. - ingotReceiveTime :: Double, - -- ^ The time at which the ingot was received. - ingotReceiveTemp :: Double, - -- ^ The temperature with which the ingot was received. - ingotLoadTime :: Double, - -- ^ The time of loading in the furnace. - ingotLoadTemp :: Double, - -- ^ The temperature when the ingot was loaded in the furnace. - ingotCoeff :: Double - -- ^ The heating coefficient. - } - --- | Create a furnace. -newFurnace :: Simulation Furnace -newFurnace = - do pits <- sequence [newPit | i <- [1..10]] - pitCount <- newRef 0 - queue <- runEventInStartTime newFCFSQueue - heatingTime <- newRef emptySamplingStats - h <- newRef 1650.0 - readyCount <- newRef 0 - readyTemps <- newRef [] - s <- newSignalSource - return Furnace { furnacePits = pits, - furnacePitCount = pitCount, - furnaceQueue = queue, - furnaceUnloadedSource = s, - furnaceHeatingTime = heatingTime, - furnaceTemp = h, - furnaceReadyCount = readyCount, - furnaceReadyTemps = readyTemps } - --- | Create a new pit. -newPit :: Simulation Pit -newPit = - do ingot <- newRef Nothing - h' <- newRef 0.0 - return Pit { pitIngot = ingot, - pitTemp = h' } - --- | Create a new ingot. -newIngot :: Furnace -> Event Ingot -newIngot furnace = - do t <- liftDynamics time - xi <- liftParameter $ randomNormal 0.05 0.01 - h' <- liftParameter randomTemp - let c = 0.1 + xi - return Ingot { ingotFurnace = furnace, - ingotReceiveTime = t, - ingotReceiveTemp = h', - ingotLoadTime = t, - ingotLoadTemp = h', - ingotCoeff = c } - --- | Heat the ingot up in the pit if there is such an ingot. -heatPitUp :: Pit -> Event () -heatPitUp pit = - do ingot <- readRef (pitIngot pit) - case ingot of - Nothing -> - return () - Just ingot -> do - - -- update the temperature of the ingot. - let furnace = ingotFurnace ingot - dt' <- liftParameter dt - h' <- readRef (pitTemp pit) - h <- readRef (furnaceTemp furnace) - writeRef (pitTemp pit) $ - h' + dt' * (h - h') * ingotCoeff ingot - --- | Check whether there are ready ingots in the pits. -ingotsReady :: Furnace -> Event Bool -ingotsReady furnace = - fmap (not . null) $ - filterM (fmap (>= 2200.0) . readRef . pitTemp) $ - furnacePits furnace - --- | Try to unload the ready ingot from the specified pit. -tryUnloadPit :: Furnace -> Pit -> Event () -tryUnloadPit furnace pit = - do h' <- readRef (pitTemp pit) - when (h' >= 2000.0) $ - do Just ingot <- readRef (pitIngot pit) - unloadIngot furnace ingot pit - --- | Try to load an awaiting ingot in the specified empty pit. -tryLoadPit :: Furnace -> Pit -> Event () -tryLoadPit furnace 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 :: 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 - modifyRef (furnacePitCount furnace) (+ (- 1)) - - -- how long did we heat the ingot up? - t' <- liftDynamics time - modifyRef (furnaceHeatingTime furnace) $ - addSamplingStats (t' - ingotLoadTime ingot) - - -- what is the temperature of the unloaded ingot? - modifyRef (furnaceReadyTemps furnace) (h' :) - - -- count the ready ingots - modifyRef (furnaceReadyCount furnace) (+ 1) - --- | Load the ingot in the specified 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 - modifyRef (furnacePitCount furnace) (+ 1) - count <- readRef (furnacePitCount furnace) - - -- decrease the furnace temperature - h <- readRef (furnaceTemp furnace) - let h' = ingotLoadTemp ingot - dh = - (h - h') / fromIntegral count - writeRef (furnaceTemp furnace) $ h + dh - --- | Start iterating the furnace processing through the event queue. -startIteratingFurnace :: Furnace -> Event () -startIteratingFurnace furnace = - let pits = furnacePits furnace - in enqueueEventWithIntegTimes $ - do -- try to unload ready ingots - ready <- ingotsReady furnace - when ready $ - do mapM_ (tryUnloadPit furnace) pits - triggerSignal (furnaceUnloadedSource furnace) () - - -- heat up - mapM_ heatPitUp pits - - -- update the temperature of the furnace - dt' <- liftParameter dt - h <- readRef (furnaceTemp furnace) - writeRef (furnaceTemp furnace) $ - h + dt' * (2600.0 - h) * 0.2 - --- | Return all empty pits. -emptyPits :: Furnace -> Event [Pit] -emptyPits furnace = - filterM (fmap isNothing . readRef . pitIngot) $ - furnacePits furnace - --- | 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 $ - do ingot <- newIngot furnace - enqueue (furnaceQueue furnace) ingot - -- repeat it again - inputProcess furnace - --- | Initialize the furnace. -initializeFurnace :: Furnace -> Event () -initializeFurnace furnace = - do x1 <- newIngot furnace - x2 <- newIngot furnace - x3 <- newIngot furnace - x4 <- newIngot furnace - x5 <- newIngot furnace - x6 <- newIngot furnace - let p1 : p2 : p3 : p4 : p5 : p6 : ps = - furnacePits furnace - 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 - --- | The simulation model. -model :: Simulation Results -model = - do furnace <- newFurnace - - -- initialize the furnace and start its iterating in start time - runEventInStartTime $ - do initializeFurnace furnace - startIteratingFurnace furnace - - -- generate randomly new input ingots - runProcessInStartTime $ - inputProcess furnace - - -- load permanently the input ingots in the furnace - runProcessInStartTime $ - loadingProcess furnace - - -- return the simulation results - return $ - resultSummary $ - results - [resultSource "inputIngotCount" "the input ingot count" $ - enqueueStoreCount (furnaceQueue furnace), - -- - resultSource "loadedIngotCount" "the loaded ingot count" $ - dequeueCount (furnaceQueue furnace), - -- - resultSource "outputIngotCount" "the output ingot count" $ - furnaceReadyCount furnace, - -- - resultSource "outputIngotTemp" "the output ingot temperature" $ - fmap listSamplingStats $ readRef $ furnaceReadyTemps furnace, - -- - resultSource "heatingTime" "the heating time" $ - furnaceHeatingTime furnace, - -- - resultSource "pitCount" "the number of ingots in pits" $ - furnacePitCount furnace, - -- - resultSource "furnaceQueue" "the furnace queue" $ - furnaceQueue furnace] - --- | The main program. -main = - printSimulationResultsInStopTime - printResultSourceInEnglish - model specs ++-- This is a model of the Furnace. It is described in different sources [1, 2].+--+-- [1] A. Alan B. Pritsker, Simulation with Visual SLAM and AweSim, 2nd ed.+--+-- [2] Труб И.И., Объектно-ориентированное моделирование на C++: Учебный курс. - СПб.: Питер, 2006++import Data.Maybe+import System.Random+import Control.Monad+import Control.Monad.Trans++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,+ spcGeneratorType = SimpleGenerator }+ +-- | Return a random initial temperature of the item. +randomTemp :: Parameter Double+randomTemp = randomUniform 400 600++-- | Represents the furnace.+data Furnace = + Furnace { furnacePits :: [Pit],+ -- ^ The pits for ingots.+ furnacePitCount :: Ref Int,+ -- ^ The count of active pits with 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.+ 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),+ -- ^ The ingot in the pit.+ pitTemp :: Ref Double+ -- ^ The ingot temperature in the pit.+ }++data Ingot = + Ingot { ingotFurnace :: Furnace,+ -- ^ The furnace.+ ingotReceiveTime :: Double,+ -- ^ The time at which the ingot was received.+ ingotReceiveTemp :: Double,+ -- ^ The temperature with which the ingot was received.+ ingotLoadTime :: Double,+ -- ^ The time of loading in the furnace.+ ingotLoadTemp :: Double,+ -- ^ The temperature when the ingot was loaded in the furnace.+ ingotCoeff :: Double+ -- ^ The heating coefficient.+ }++-- | Create a furnace.+newFurnace :: Simulation Furnace+newFurnace =+ do pits <- sequence [newPit | i <- [1..10]]+ pitCount <- newRef 0+ queue <- runEventInStartTime newFCFSQueue+ heatingTime <- newRef emptySamplingStats+ h <- newRef 1650.0+ readyCount <- newRef 0+ readyTemps <- newRef []+ s <- newSignalSource+ return Furnace { furnacePits = pits,+ furnacePitCount = pitCount,+ furnaceQueue = queue,+ furnaceUnloadedSource = s,+ furnaceHeatingTime = heatingTime,+ furnaceTemp = h,+ furnaceReadyCount = readyCount, + furnaceReadyTemps = readyTemps }++-- | Create a new pit.+newPit :: Simulation Pit+newPit =+ do ingot <- newRef Nothing+ h' <- newRef 0.0+ return Pit { pitIngot = ingot,+ pitTemp = h' }++-- | Create a new ingot.+newIngot :: Furnace -> Event Ingot+newIngot furnace =+ do t <- liftDynamics time+ xi <- liftParameter $ randomNormal 0.05 0.01+ h' <- liftParameter randomTemp+ let c = 0.1 + xi+ return Ingot { ingotFurnace = furnace,+ ingotReceiveTime = t,+ ingotReceiveTemp = h',+ ingotLoadTime = t,+ ingotLoadTemp = h',+ ingotCoeff = c }++-- | Heat the ingot up in the pit if there is such an ingot.+heatPitUp :: Pit -> Event ()+heatPitUp pit =+ do ingot <- readRef (pitIngot pit)+ case ingot of+ Nothing -> + return ()+ Just ingot -> do+ + -- update the temperature of the ingot.+ let furnace = ingotFurnace ingot+ dt' <- liftParameter dt+ h' <- readRef (pitTemp pit)+ h <- readRef (furnaceTemp furnace)+ writeRef (pitTemp pit) $ + h' + dt' * (h - h') * ingotCoeff ingot++-- | Check whether there are ready ingots in the pits.+ingotsReady :: Furnace -> Event Bool+ingotsReady furnace =+ fmap (not . null) $ + filterM (fmap (>= 2200.0) . readRef . pitTemp) $ + furnacePits furnace++-- | Try to unload the ready ingot from the specified pit.+tryUnloadPit :: Furnace -> Pit -> Event ()+tryUnloadPit furnace pit =+ do h' <- readRef (pitTemp pit)+ when (h' >= 2000.0) $+ do Just ingot <- readRef (pitIngot pit) + unloadIngot furnace ingot pit++-- | Try to load an awaiting ingot in the specified empty pit.+tryLoadPit :: Furnace -> Pit -> Event () +tryLoadPit furnace 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 :: 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+ modifyRef (furnacePitCount furnace) (+ (- 1))+ + -- how long did we heat the ingot up?+ t' <- liftDynamics time+ modifyRef (furnaceHeatingTime furnace) $+ addSamplingStats (t' - ingotLoadTime ingot)+ + -- what is the temperature of the unloaded ingot?+ modifyRef (furnaceReadyTemps furnace) (h' :)+ + -- count the ready ingots+ modifyRef (furnaceReadyCount furnace) (+ 1)+ +-- | Load the ingot in the specified 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+ modifyRef (furnacePitCount furnace) (+ 1)+ count <- readRef (furnacePitCount furnace)+ + -- decrease the furnace temperature+ h <- readRef (furnaceTemp furnace)+ let h' = ingotLoadTemp ingot+ dh = - (h - h') / fromIntegral count+ writeRef (furnaceTemp furnace) $ h + dh+ +-- | Start iterating the furnace processing through the event queue.+startIteratingFurnace :: Furnace -> Event ()+startIteratingFurnace furnace = + let pits = furnacePits furnace+ in enqueueEventWithIntegTimes $+ do -- try to unload ready ingots+ ready <- ingotsReady furnace+ when ready $ + do mapM_ (tryUnloadPit furnace) pits+ triggerSignal (furnaceUnloadedSource furnace) ()++ -- heat up+ mapM_ heatPitUp pits+ + -- update the temperature of the furnace+ dt' <- liftParameter dt+ h <- readRef (furnaceTemp furnace)+ writeRef (furnaceTemp furnace) $+ h + dt' * (2600.0 - h) * 0.2++-- | Return all empty pits.+emptyPits :: Furnace -> Event [Pit]+emptyPits furnace =+ filterM (fmap isNothing . readRef . pitIngot) $+ furnacePits furnace++-- | 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 $+ do ingot <- newIngot furnace+ enqueue (furnaceQueue furnace) ingot+ -- repeat it again+ inputProcess furnace++-- | Initialize the furnace.+initializeFurnace :: Furnace -> Event ()+initializeFurnace furnace =+ do x1 <- newIngot furnace+ x2 <- newIngot furnace+ x3 <- newIngot furnace+ x4 <- newIngot furnace+ x5 <- newIngot furnace+ x6 <- newIngot furnace+ let p1 : p2 : p3 : p4 : p5 : p6 : ps = + furnacePits furnace+ 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+ +-- | The simulation model.+model :: Simulation Results+model =+ do furnace <- newFurnace+ + -- initialize the furnace and start its iterating in start time+ runEventInStartTime $+ do initializeFurnace furnace+ startIteratingFurnace furnace+ + -- generate randomly new input ingots+ runProcessInStartTime $+ inputProcess furnace++ -- load permanently the input ingots in the furnace+ runProcessInStartTime $+ loadingProcess furnace++ -- return the simulation results+ return $+ resultSummary $+ results+ [resultSource "inputIngotCount" "the input ingot count" $+ enqueueStoreCount (furnaceQueue furnace),+ --+ resultSource "loadedIngotCount" "the loaded ingot count" $+ dequeueCount (furnaceQueue furnace),+ --+ resultSource "outputIngotCount" "the output ingot count" $+ furnaceReadyCount furnace,+ --+ resultSource "outputIngotTemp" "the output ingot temperature" $+ fmap listSamplingStats $ readRef $ furnaceReadyTemps furnace,+ --+ resultSource "heatingTime" "the heating time" $+ furnaceHeatingTime furnace,+ --+ resultSource "pitCount" "the number of ingots in pits" $+ furnacePitCount furnace,+ --+ resultSource "furnaceQueue" "the furnace queue" $+ furnaceQueue furnace]++-- | The main program.+main =+ printSimulationResultsInStopTime+ printResultSourceInEnglish+ model specs
examples/InspectionAdjustmentStations.hs view
@@ -1,161 +1,161 @@- -{-# LANGUAGE RecursiveDo, Arrows #-} - --- Example: Inspection and Adjustment Stations on a Production Line --- --- This is a model of the workflow with a loop. Also there are two infinite queues. --- --- It is described in different sources [1, 2]. So, this is chapter 8 of [2] and section 5.15 of [1]. --- --- [1] A. Alan B. Pritsker, Simulation with Visual SLAM and AweSim, 2nd ed. --- --- [2] Труб И.И., Объектно-ориентированное моделирование на C++: Учебный курс. - СПб.: Питер, 2006 - -import Prelude hiding (id, (.)) - -import Control.Monad -import Control.Monad.Trans -import Control.Arrow -import Control.Category (id, (.)) - -import Simulation.Aivika -import Simulation.Aivika.Queue.Infinite - --- | The simulation specs. -specs = Specs { spcStartTime = 0.0, - spcStopTime = 480.0, - spcDT = 0.1, - spcMethod = RungeKutta4, - spcGeneratorType = SimpleGenerator } - --- the minimum delay of arriving the next TV set -minArrivalDelay = 3.5 - --- the maximum delay of arriving the next TV set -maxArrivalDelay = 7.5 - --- the minimum time to inspect the TV set -minInspectionTime = 6 - --- the maximum time to inspect the TV set -maxInspectionTime = 12 - --- the probability of passing the inspection phase -inspectionPassingProb = 0.85 - --- how many are inspection stations? -inspectionStationCount = 2 - --- the minimum time to adjust an improper TV set -minAdjustmentTime = 20 - --- the maximum time to adjust an improper TV set -maxAdjustmentTime = 40 - --- how many are adjustment stations? -adjustmentStationCount = 1 - --- create an inspection station (server) -newInspectionStation = - newServer $ \a -> - do holdProcess =<< - (liftParameter $ - randomUniform minInspectionTime maxInspectionTime) - passed <- - liftParameter $ - randomTrue inspectionPassingProb - if passed - then return $ Right a - else return $ Left a - --- create an adjustment station (server) -newAdjustmentStation = - newServer $ \a -> - do holdProcess =<< - (liftParameter $ - randomUniform minAdjustmentTime maxAdjustmentTime) - return a - -model :: Simulation Results -model = mdo - -- to count the arrived TV sets for inspecting and adjusting - inputArrivalTimer <- newArrivalTimer - -- it will gather the statistics of the processing time - outputArrivalTimer <- newArrivalTimer - -- define a stream of input events - let inputStream = - randomUniformStream minArrivalDelay maxArrivalDelay - -- create a queue before the inspection stations - inspectionQueue <- - runEventInStartTime newFCFSQueue - -- create a queue before the adjustment stations - adjustmentQueue <- - runEventInStartTime newFCFSQueue - -- create the inspection stations (servers) - inspectionStations <- - forM [1 .. inspectionStationCount] $ \_ -> - newInspectionStation - -- create the adjustment stations (servers) - adjustmentStations <- - forM [1 .. adjustmentStationCount] $ \_ -> - newAdjustmentStation - -- a processor loop for the inspection stations' queue - let inspectionQueueProcessorLoop = - queueProcessorLoopSeq - (liftEvent . enqueue inspectionQueue) - (dequeue inspectionQueue) - inspectionProcessor - (adjustmentQueueProcessor >>> adjustmentProcessor) - -- a processor for the adjustment stations' queue - let adjustmentQueueProcessor = - queueProcessor - (liftEvent . enqueue adjustmentQueue) - (dequeue adjustmentQueue) - -- a parallel work of the inspection stations - let inspectionProcessor = - processorParallel (map serverProcessor inspectionStations) - -- a parallel work of the adjustment stations - let adjustmentProcessor = - processorParallel (map serverProcessor adjustmentStations) - -- the entire processor from input to output - let entireProcessor = - arrivalTimerProcessor inputArrivalTimer >>> - inspectionQueueProcessorLoop >>> - arrivalTimerProcessor outputArrivalTimer - -- start simulating the model - runProcessInStartTime $ - sinkStream $ runProcessor entireProcessor inputStream - -- return the simulation results in start time - return $ - results - [resultSource - "inspectionQueue" "the inspection queue" - inspectionQueue, - -- - resultSource - "adjustmentQueue" "the adjustment queue" - adjustmentQueue, - -- - resultSource - "inputArrivalTimer" "the input arrival timer" - inputArrivalTimer, - -- - resultSource - "outputArrivalTimer" "the output arrival timer" - outputArrivalTimer, - -- - resultSource - "inspectionStations" "the inspection stations" - inspectionStations, - -- - resultSource - "adjustmentStations" "the adjustment stations" - adjustmentStations] - -modelSummary :: Simulation Results -modelSummary = fmap resultSummary model - -main = - printSimulationResultsInStopTime - printResultSourceInEnglish - modelSummary specs ++{-# LANGUAGE RecursiveDo, Arrows #-}++-- Example: Inspection and Adjustment Stations on a Production Line+-- +-- This is a model of the workflow with a loop. Also there are two infinite queues.+--+-- It is described in different sources [1, 2]. So, this is chapter 8 of [2] and section 5.15 of [1].+--+-- [1] A. Alan B. Pritsker, Simulation with Visual SLAM and AweSim, 2nd ed.+--+-- [2] Труб И.И., Объектно-ориентированное моделирование на C++: Учебный курс. - СПб.: Питер, 2006++import Prelude hiding (id, (.)) ++import Control.Monad+import Control.Monad.Trans+import Control.Arrow+import Control.Category (id, (.))++import Simulation.Aivika+import Simulation.Aivika.Queue.Infinite++-- | The simulation specs.+specs = Specs { spcStartTime = 0.0,+ spcStopTime = 480.0,+ spcDT = 0.1,+ spcMethod = RungeKutta4,+ spcGeneratorType = SimpleGenerator }++-- the minimum delay of arriving the next TV set+minArrivalDelay = 3.5++-- the maximum delay of arriving the next TV set+maxArrivalDelay = 7.5++-- the minimum time to inspect the TV set+minInspectionTime = 6++-- the maximum time to inspect the TV set+maxInspectionTime = 12++-- the probability of passing the inspection phase+inspectionPassingProb = 0.85++-- how many are inspection stations?+inspectionStationCount = 2++-- the minimum time to adjust an improper TV set+minAdjustmentTime = 20++-- the maximum time to adjust an improper TV set+maxAdjustmentTime = 40++-- how many are adjustment stations?+adjustmentStationCount = 1++-- create an inspection station (server)+newInspectionStation =+ newServer $ \a ->+ do holdProcess =<<+ (liftParameter $+ randomUniform minInspectionTime maxInspectionTime)+ passed <- + liftParameter $+ randomTrue inspectionPassingProb+ if passed+ then return $ Right a+ else return $ Left a ++-- create an adjustment station (server)+newAdjustmentStation =+ newServer $ \a ->+ do holdProcess =<<+ (liftParameter $+ randomUniform minAdjustmentTime maxAdjustmentTime)+ return a+ +model :: Simulation Results+model = mdo+ -- to count the arrived TV sets for inspecting and adjusting+ inputArrivalTimer <- newArrivalTimer+ -- it will gather the statistics of the processing time+ outputArrivalTimer <- newArrivalTimer+ -- define a stream of input events+ let inputStream =+ randomUniformStream minArrivalDelay maxArrivalDelay + -- create a queue before the inspection stations+ inspectionQueue <-+ runEventInStartTime newFCFSQueue+ -- create a queue before the adjustment stations+ adjustmentQueue <-+ runEventInStartTime newFCFSQueue+ -- create the inspection stations (servers)+ inspectionStations <-+ forM [1 .. inspectionStationCount] $ \_ ->+ newInspectionStation+ -- create the adjustment stations (servers)+ adjustmentStations <-+ forM [1 .. adjustmentStationCount] $ \_ ->+ newAdjustmentStation+ -- a processor loop for the inspection stations' queue+ let inspectionQueueProcessorLoop =+ queueProcessorLoopSeq+ (liftEvent . enqueue inspectionQueue)+ (dequeue inspectionQueue)+ inspectionProcessor+ (adjustmentQueueProcessor >>> adjustmentProcessor)+ -- a processor for the adjustment stations' queue+ let adjustmentQueueProcessor =+ queueProcessor+ (liftEvent . enqueue adjustmentQueue)+ (dequeue adjustmentQueue)+ -- a parallel work of the inspection stations+ let inspectionProcessor =+ processorParallel (map serverProcessor inspectionStations)+ -- a parallel work of the adjustment stations+ let adjustmentProcessor =+ processorParallel (map serverProcessor adjustmentStations)+ -- the entire processor from input to output+ let entireProcessor =+ arrivalTimerProcessor inputArrivalTimer >>>+ inspectionQueueProcessorLoop >>>+ arrivalTimerProcessor outputArrivalTimer+ -- start simulating the model+ runProcessInStartTime $+ sinkStream $ runProcessor entireProcessor inputStream+ -- return the simulation results in start time+ return $+ results+ [resultSource+ "inspectionQueue" "the inspection queue"+ inspectionQueue,+ --+ resultSource+ "adjustmentQueue" "the adjustment queue"+ adjustmentQueue,+ --+ resultSource+ "inputArrivalTimer" "the input arrival timer"+ inputArrivalTimer,+ --+ resultSource+ "outputArrivalTimer" "the output arrival timer"+ outputArrivalTimer,+ --+ resultSource+ "inspectionStations" "the inspection stations"+ inspectionStations,+ --+ resultSource+ "adjustmentStations" "the adjustment stations"+ adjustmentStations]++modelSummary :: Simulation Results+modelSummary = fmap resultSummary model++main =+ printSimulationResultsInStopTime+ printResultSourceInEnglish+ modelSummary specs
examples/MachRep1.hs view
@@ -1,67 +1,67 @@- --- It corresponds to model MachRep1 described in document --- Introduction to Discrete-Event Simulation and the SimPy Language --- [http://heather.cs.ucdavis.edu/~matloff/156/PLN/DESimIntro.pdf]. --- SimPy is available on [http://simpy.sourceforge.net/]. --- --- The model description is as follows. --- --- Two machines, which sometimes break down. --- Up time is exponentially distributed with mean 1.0, and repair time is --- exponentially distributed with mean 0.5. There are two repairpersons, --- so the two machines can be repaired simultaneously if they are down --- at the same time. --- --- Output is long-run proportion of up time. Should get value of about --- 0.66. - -import Control.Monad.Trans - -import Simulation.Aivika - -meanUpTime = 1.0 -meanRepairTime = 0.5 - -specs = Specs { spcStartTime = 0.0, - spcStopTime = 1000.0, - spcDT = 1.0, - spcMethod = RungeKutta4, - spcGeneratorType = SimpleGenerator } - -model :: Simulation Results -model = - do totalUpTime <- newRef 0.0 - - let machine :: Process () - machine = - do upTime <- - liftParameter $ - randomExponential meanUpTime - holdProcess upTime - liftEvent $ - modifyRef totalUpTime (+ upTime) - repairTime <- - liftParameter $ - randomExponential meanRepairTime - holdProcess repairTime - machine - - runProcessInStartTime machine - runProcessInStartTime machine - - let upTimeProp = - do x <- readRef totalUpTime - y <- liftDynamics time - return $ x / (2 * y) - - return $ - results - [resultSource - "upTimeProp" - "The long-run proportion of up time (~ 0.66)" - upTimeProp] - -main = - printSimulationResultsInStopTime - printResultSourceInEnglish - model specs ++-- It corresponds to model MachRep1 described in document +-- Introduction to Discrete-Event Simulation and the SimPy Language+-- [http://heather.cs.ucdavis.edu/~matloff/156/PLN/DESimIntro.pdf]. +-- SimPy is available on [http://simpy.sourceforge.net/].+-- +-- The model description is as follows.+--+-- Two machines, which sometimes break down.+-- Up time is exponentially distributed with mean 1.0, and repair time is+-- exponentially distributed with mean 0.5. There are two repairpersons,+-- so the two machines can be repaired simultaneously if they are down+-- at the same time.+--+-- Output is long-run proportion of up time. Should get value of about+-- 0.66.++import Control.Monad.Trans++import Simulation.Aivika++meanUpTime = 1.0+meanRepairTime = 0.5++specs = Specs { spcStartTime = 0.0,+ spcStopTime = 1000.0,+ spcDT = 1.0,+ spcMethod = RungeKutta4,+ spcGeneratorType = SimpleGenerator }+ +model :: Simulation Results+model =+ do totalUpTime <- newRef 0.0+ + let machine :: Process ()+ machine =+ do upTime <-+ liftParameter $+ randomExponential meanUpTime+ holdProcess upTime+ liftEvent $ + modifyRef totalUpTime (+ upTime)+ repairTime <-+ liftParameter $+ randomExponential meanRepairTime+ holdProcess repairTime+ machine++ runProcessInStartTime machine+ runProcessInStartTime machine++ let upTimeProp =+ do x <- readRef totalUpTime+ y <- liftDynamics time+ return $ x / (2 * y)++ return $+ results+ [resultSource+ "upTimeProp"+ "The long-run proportion of up time (~ 0.66)"+ upTimeProp]+ +main =+ printSimulationResultsInStopTime+ printResultSourceInEnglish+ model specs
examples/MachRep1EventDriven.hs view
@@ -1,81 +1,81 @@- --- It corresponds to model MachRep1 described in document --- Introduction to Discrete-Event Simulation and the SimPy Language --- [http://heather.cs.ucdavis.edu/~matloff/156/PLN/DESimIntro.pdf]. --- SimPy is available on [http://simpy.sourceforge.net/]. --- --- The model description is as follows. --- --- Two machines, which sometimes break down. --- Up time is exponentially distributed with mean 1.0, and repair time is --- exponentially distributed with mean 0.5. There are two repairpersons, --- so the two machines can be repaired simultaneously if they are down --- at the same time. --- --- Output is long-run proportion of up time. Should get value of about --- 0.66. - -import Control.Monad.Trans - -import Simulation.Aivika - -meanUpTime = 1.0 -meanRepairTime = 0.5 - -specs = Specs { spcStartTime = 0.0, - spcStopTime = 1000.0, - spcDT = 1.0, - spcMethod = RungeKutta4, - spcGeneratorType = SimpleGenerator } - -model :: Simulation Results -model = - do totalUpTime <- newRef 0.0 - - let machineBroken :: Double -> Event () - machineBroken startUpTime = - - do finishUpTime <- liftDynamics time - modifyRef totalUpTime (+ (finishUpTime - startUpTime)) - repairTime <- - liftParameter $ - randomExponential meanRepairTime - - -- enqueue a new event - let t = finishUpTime + repairTime - enqueueEvent t machineRepaired - - machineRepaired :: Event () - machineRepaired = - - do startUpTime <- liftDynamics time - upTime <- - liftParameter $ - randomExponential meanUpTime - - -- enqueue a new event - let t = startUpTime + upTime - enqueueEvent t $ machineBroken startUpTime - - runEventInStartTime $ - do -- start the first machine - machineRepaired - -- start the second machine - machineRepaired - - let upTimeProp = - do x <- readRef totalUpTime - y <- liftDynamics time - return $ x / (2 * y) - - return $ - results - [resultSource - "upTimeProp" - "The long-run proportion of up time (~ 0.66)" - upTimeProp] - -main = - printSimulationResultsInStopTime - printResultSourceInEnglish - model specs ++-- It corresponds to model MachRep1 described in document +-- Introduction to Discrete-Event Simulation and the SimPy Language+-- [http://heather.cs.ucdavis.edu/~matloff/156/PLN/DESimIntro.pdf]. +-- SimPy is available on [http://simpy.sourceforge.net/].+-- +-- The model description is as follows.+--+-- Two machines, which sometimes break down.+-- Up time is exponentially distributed with mean 1.0, and repair time is+-- exponentially distributed with mean 0.5. There are two repairpersons,+-- so the two machines can be repaired simultaneously if they are down+-- at the same time.+--+-- Output is long-run proportion of up time. Should get value of about+-- 0.66.++import Control.Monad.Trans++import Simulation.Aivika++meanUpTime = 1.0+meanRepairTime = 0.5++specs = Specs { spcStartTime = 0.0,+ spcStopTime = 1000.0,+ spcDT = 1.0,+ spcMethod = RungeKutta4,+ spcGeneratorType = SimpleGenerator }+ +model :: Simulation Results+model =+ do totalUpTime <- newRef 0.0+ + let machineBroken :: Double -> Event ()+ machineBroken startUpTime =+ + do finishUpTime <- liftDynamics time+ modifyRef totalUpTime (+ (finishUpTime - startUpTime))+ repairTime <-+ liftParameter $+ randomExponential meanRepairTime+ + -- enqueue a new event+ let t = finishUpTime + repairTime+ enqueueEvent t machineRepaired+ + machineRepaired :: Event ()+ machineRepaired =+ + do startUpTime <- liftDynamics time+ upTime <-+ liftParameter $+ randomExponential meanUpTime+ + -- enqueue a new event+ let t = startUpTime + upTime+ enqueueEvent t $ machineBroken startUpTime++ runEventInStartTime $+ do -- start the first machine+ machineRepaired+ -- start the second machine+ machineRepaired++ let upTimeProp =+ do x <- readRef totalUpTime+ y <- liftDynamics time+ return $ x / (2 * y)++ return $+ results+ [resultSource+ "upTimeProp"+ "The long-run proportion of up time (~ 0.66)"+ upTimeProp]+ +main =+ printSimulationResultsInStopTime+ printResultSourceInEnglish+ model specs
examples/MachRep1TimeDriven.hs view
@@ -1,118 +1,118 @@- --- It corresponds to model MachRep1 described in document --- Introduction to Discrete-Event Simulation and the SimPy Language --- [http://heather.cs.ucdavis.edu/~matloff/156/PLN/DESimIntro.pdf]. --- SimPy is available on [http://simpy.sourceforge.net/]. --- --- The model description is as follows. --- --- Two machines, which sometimes break down. --- Up time is exponentially distributed with mean 1.0, and repair time is --- exponentially distributed with mean 0.5. There are two repairpersons, --- so the two machines can be repaired simultaneously if they are down --- at the same time. --- --- Output is long-run proportion of up time. Should get value of about --- 0.66. - -import Control.Monad.Trans - -import Simulation.Aivika - -meanUpTime = 1.0 -meanRepairTime = 0.5 - -specs = Specs { spcStartTime = 0.0, - spcStopTime = 1000.0, - spcDT = 0.05, - spcMethod = RungeKutta4, - spcGeneratorType = SimpleGenerator } - -model :: Simulation Results -model = - do totalUpTime <- newRef 0.0 - - let machine :: Simulation (Event ()) - machine = - do startUpTime <- newRef 0.0 - - -- a number of iterations when - -- the machine works - upNum <- newRef (-1) - - -- a number of iterations when - -- the machine is broken - repairNum <- newRef (-1) - - -- create a simulation model - return $ - do upNum' <- readRef upNum - repairNum' <- readRef repairNum - - let untilBroken = - modifyRef upNum $ \a -> a - 1 - - untilRepaired = - modifyRef repairNum $ \a -> a - 1 - - broken = - do writeRef upNum (-1) - -- the machine is broken - startUpTime' <- readRef startUpTime - finishUpTime' <- liftDynamics time - dt' <- liftParameter dt - modifyRef totalUpTime $ - \a -> a + - (finishUpTime' - startUpTime') - repairTime' <- - liftParameter $ - randomExponential meanRepairTime - writeRef repairNum $ - round (repairTime' / dt') - - repaired = - do writeRef repairNum (-1) - -- the machine is repaired - t' <- liftDynamics time - dt' <- liftParameter dt - writeRef startUpTime t' - upTime' <- - liftParameter $ - randomExponential meanUpTime - writeRef upNum $ - round (upTime' / dt') - - result | upNum' > 0 = untilBroken - | upNum' == 0 = broken - | repairNum' > 0 = untilRepaired - | repairNum' == 0 = repaired - | otherwise = repaired - result - - -- create two machines with type Event () - m1 <- machine - m2 <- machine - - -- start the time-driven simulation of the machines - runEventInStartTime $ - -- in the integration time points - enqueueEventWithIntegTimes $ - do m1 - m2 - - let upTimeProp = - do x <- readRef totalUpTime - y <- liftDynamics time - return $ x / (2 * y) - - return $ - results - [resultSource - "upTimeProp" - "The long-run proportion of up time (~ 0.66)" - upTimeProp] - -main = - printSimulationResultsInStopTime - printResultSourceInEnglish - model specs ++-- It corresponds to model MachRep1 described in document +-- Introduction to Discrete-Event Simulation and the SimPy Language+-- [http://heather.cs.ucdavis.edu/~matloff/156/PLN/DESimIntro.pdf]. +-- SimPy is available on [http://simpy.sourceforge.net/].+-- +-- The model description is as follows.+--+-- Two machines, which sometimes break down.+-- Up time is exponentially distributed with mean 1.0, and repair time is+-- exponentially distributed with mean 0.5. There are two repairpersons,+-- so the two machines can be repaired simultaneously if they are down+-- at the same time.+--+-- Output is long-run proportion of up time. Should get value of about+-- 0.66.++import Control.Monad.Trans++import Simulation.Aivika++meanUpTime = 1.0+meanRepairTime = 0.5++specs = Specs { spcStartTime = 0.0,+ spcStopTime = 1000.0,+ spcDT = 0.05,+ spcMethod = RungeKutta4,+ spcGeneratorType = SimpleGenerator }+ +model :: Simulation Results+model =+ do totalUpTime <- newRef 0.0+ + let machine :: Simulation (Event ())+ machine =+ do startUpTime <- newRef 0.0 + + -- a number of iterations when + -- the machine works+ upNum <- newRef (-1)+ + -- a number of iterations when + -- the machine is broken+ repairNum <- newRef (-1)+ + -- create a simulation model+ return $+ do upNum' <- readRef upNum+ repairNum' <- readRef repairNum+ + let untilBroken = + modifyRef upNum $ \a -> a - 1+ + untilRepaired =+ modifyRef repairNum $ \a -> a - 1+ + broken =+ do writeRef upNum (-1)+ -- the machine is broken+ startUpTime' <- readRef startUpTime+ finishUpTime' <- liftDynamics time+ dt' <- liftParameter dt+ modifyRef totalUpTime $ + \a -> a ++ (finishUpTime' - startUpTime')+ repairTime' <-+ liftParameter $+ randomExponential meanRepairTime+ writeRef repairNum $+ round (repairTime' / dt')+ + repaired =+ do writeRef repairNum (-1)+ -- the machine is repaired+ t' <- liftDynamics time+ dt' <- liftParameter dt+ writeRef startUpTime t'+ upTime' <-+ liftParameter $+ randomExponential meanUpTime+ writeRef upNum $+ round (upTime' / dt')+ + result | upNum' > 0 = untilBroken+ | upNum' == 0 = broken+ | repairNum' > 0 = untilRepaired+ | repairNum' == 0 = repaired+ | otherwise = repaired + result+ + -- create two machines with type Event ()+ m1 <- machine+ m2 <- machine++ -- start the time-driven simulation of the machines+ runEventInStartTime $+ -- in the integration time points+ enqueueEventWithIntegTimes $+ do m1+ m2++ let upTimeProp =+ do x <- readRef totalUpTime+ y <- liftDynamics time+ return $ x / (2 * y)++ return $+ results+ [resultSource+ "upTimeProp"+ "The long-run proportion of up time (~ 0.66)"+ upTimeProp]+ +main =+ printSimulationResultsInStopTime+ printResultSourceInEnglish+ model specs
examples/MachRep2.hs view
@@ -1,104 +1,104 @@- --- It corresponds to model MachRep2 described in document --- Introduction to Discrete-Event Simulation and the SimPy Language --- [http://heather.cs.ucdavis.edu/~matloff/156/PLN/DESimIntro.pdf]. --- SimPy is available on [http://simpy.sourceforge.net/]. --- --- The model description is as follows. --- --- Two machines, but sometimes break down. Up time is exponentially --- distributed with mean 1.0, and repair time is exponentially distributed --- with mean 0.5. In this example, there is only one repairperson, so --- the two machines cannot be repaired simultaneously if they are down --- at the same time. --- --- In addition to finding the long-run proportion of up time as in --- model MachRep1, let’s also find the long-run proportion of the time --- 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 Control.Monad -import Control.Monad.Trans - -import Simulation.Aivika - -meanUpTime = 1.0 -meanRepairTime = 0.5 - -specs = Specs { spcStartTime = 0.0, - spcStopTime = 1000.0, - spcDT = 1.0, - spcMethod = RungeKutta4, - spcGeneratorType = SimpleGenerator } - -model :: Simulation Results -model = - do -- number of times the machines have broken down - nRep <- newRef 0 - - -- number of breakdowns in which the machine - -- started repair service right away - nImmedRep <- newRef 0 - - -- total up time for all machines - totalUpTime <- newRef 0.0 - - repairPerson <- newFCFSResource 1 - - let machine :: Process () - machine = - do upTime <- - liftParameter $ - randomExponential meanUpTime - holdProcess upTime - liftEvent $ - modifyRef totalUpTime (+ upTime) - - -- check the resource availability - liftEvent $ - do modifyRef nRep (+ 1) - n <- resourceCount repairPerson - when (n == 1) $ - modifyRef nImmedRep (+ 1) - - requestResource repairPerson - repairTime <- - liftParameter $ - randomExponential meanRepairTime - holdProcess repairTime - releaseResource repairPerson - - machine - - runProcessInStartTime machine - runProcessInStartTime machine - - let upTimeProp = - do x <- readRef totalUpTime - y <- liftDynamics time - return $ x / (2 * y) - - immedProp :: Event Double - immedProp = - do n <- readRef nRep - nImmed <- readRef nImmedRep - return $ - fromIntegral nImmed / - fromIntegral n - - return $ - results - [resultSource - "upTimeProp" - "The long-run proportion of up time (~ 0.6)" - upTimeProp, - -- - resultSource - "immedProp" - "The proption of time of immediate access (~0.67)" - immedProp] - -main = - printSimulationResultsInStopTime - printResultSourceInEnglish - model specs ++-- It corresponds to model MachRep2 described in document +-- Introduction to Discrete-Event Simulation and the SimPy Language+-- [http://heather.cs.ucdavis.edu/~matloff/156/PLN/DESimIntro.pdf]. +-- SimPy is available on [http://simpy.sourceforge.net/].+-- +-- The model description is as follows.+-- +-- Two machines, but sometimes break down. Up time is exponentially +-- distributed with mean 1.0, and repair time is exponentially distributed +-- with mean 0.5. In this example, there is only one repairperson, so +-- the two machines cannot be repaired simultaneously if they are down +-- at the same time.+--+-- In addition to finding the long-run proportion of up time as in+-- model MachRep1, let’s also find the long-run proportion of the time +-- 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 Control.Monad+import Control.Monad.Trans++import Simulation.Aivika++meanUpTime = 1.0+meanRepairTime = 0.5++specs = Specs { spcStartTime = 0.0,+ spcStopTime = 1000.0,+ spcDT = 1.0,+ spcMethod = RungeKutta4,+ spcGeneratorType = SimpleGenerator }+ +model :: Simulation Results+model =+ do -- number of times the machines have broken down+ nRep <- newRef 0 + + -- number of breakdowns in which the machine + -- started repair service right away+ nImmedRep <- newRef 0+ + -- total up time for all machines+ totalUpTime <- newRef 0.0+ + repairPerson <- newFCFSResource 1+ + let machine :: Process ()+ machine =+ do upTime <-+ liftParameter $+ randomExponential meanUpTime+ holdProcess upTime+ liftEvent $+ modifyRef totalUpTime (+ upTime) + + -- check the resource availability+ liftEvent $+ do modifyRef nRep (+ 1)+ n <- resourceCount repairPerson+ when (n == 1) $+ modifyRef nImmedRep (+ 1)+ + requestResource repairPerson+ repairTime <-+ liftParameter $+ randomExponential meanRepairTime+ holdProcess repairTime+ releaseResource repairPerson+ + machine++ runProcessInStartTime machine+ runProcessInStartTime machine++ let upTimeProp =+ do x <- readRef totalUpTime+ y <- liftDynamics time+ return $ x / (2 * y)++ immedProp :: Event Double+ immedProp =+ do n <- readRef nRep+ nImmed <- readRef nImmedRep+ return $+ fromIntegral nImmed /+ fromIntegral n++ return $+ results+ [resultSource+ "upTimeProp"+ "The long-run proportion of up time (~ 0.6)"+ upTimeProp,+ --+ resultSource+ "immedProp"+ "The proption of time of immediate access (~0.67)"+ immedProp]+ +main =+ printSimulationResultsInStopTime+ printResultSourceInEnglish+ model specs
examples/MachRep3.hs view
@@ -1,94 +1,94 @@- --- It corresponds to model MachRep3 described in document --- Introduction to Discrete-Event Simulation and the SimPy Language --- [http://heather.cs.ucdavis.edu/~matloff/156/PLN/DESimIntro.pdf]. --- SimPy is available on [http://simpy.sourceforge.net/]. --- --- The model description is as follows. --- --- Variation of models MachRep1, MachRep2. Two machines, but --- sometimes break down. Up time is exponentially distributed with mean --- 1.0, and repair time is exponentially distributed with mean 0.5. In --- this example, there is only one repairperson, and she is not summoned --- until both machines are down. We find the proportion of up time. It --- should come out to about 0.45. - -import Control.Monad -import Control.Monad.Trans - -import Simulation.Aivika - -meanUpTime = 1.0 -meanRepairTime = 0.5 - -specs = Specs { spcStartTime = 0.0, - spcStopTime = 1000.0, - spcDT = 1.0, - spcMethod = RungeKutta4, - spcGeneratorType = SimpleGenerator } - -model :: Simulation Results -model = - do -- number of machines currently up - nUp <- newRef 2 - - -- total up time for all machines - totalUpTime <- newRef 0.0 - - repairPerson <- newResource FCFS 1 - - pid1 <- newProcessId - pid2 <- newProcessId - - let machine :: ProcessId -> Process () - machine pid = - do upTime <- - liftParameter $ - randomExponential meanUpTime - holdProcess upTime - liftEvent $ - modifyRef totalUpTime (+ upTime) - - liftEvent $ - modifyRef nUp (+ (-1)) - nUp' <- liftEvent $ readRef nUp - if nUp' == 1 - then passivateProcess - else liftEvent $ - do n <- resourceCount repairPerson - when (n == 1) $ - reactivateProcess pid - - requestResource repairPerson - repairTime <- - liftParameter $ - randomExponential meanRepairTime - holdProcess repairTime - liftEvent $ - modifyRef nUp (+ 1) - releaseResource repairPerson - - machine pid - - runProcessInStartTimeUsingId - pid1 (machine pid2) - - runProcessInStartTimeUsingId - pid2 (machine pid1) - - let upTimeProp = - do x <- readRef totalUpTime - y <- liftDynamics time - return $ x / (2 * y) - - return $ - results - [resultSource - "upTimeProp" - "The long-run proportion of up time (~ 0.45)" - upTimeProp] - -main = - printSimulationResultsInStopTime - printResultSourceInEnglish - model specs ++-- It corresponds to model MachRep3 described in document +-- Introduction to Discrete-Event Simulation and the SimPy Language+-- [http://heather.cs.ucdavis.edu/~matloff/156/PLN/DESimIntro.pdf]. +-- SimPy is available on [http://simpy.sourceforge.net/].+-- +-- The model description is as follows.+--+-- Variation of models MachRep1, MachRep2. Two machines, but+-- sometimes break down. Up time is exponentially distributed with mean+-- 1.0, and repair time is exponentially distributed with mean 0.5. In+-- this example, there is only one repairperson, and she is not summoned+-- until both machines are down. We find the proportion of up time. It+-- should come out to about 0.45.++import Control.Monad+import Control.Monad.Trans++import Simulation.Aivika++meanUpTime = 1.0+meanRepairTime = 0.5++specs = Specs { spcStartTime = 0.0,+ spcStopTime = 1000.0,+ spcDT = 1.0,+ spcMethod = RungeKutta4,+ spcGeneratorType = SimpleGenerator }+ +model :: Simulation Results+model =+ do -- number of machines currently up+ nUp <- newRef 2+ + -- total up time for all machines+ totalUpTime <- newRef 0.0+ + repairPerson <- newResource FCFS 1+ + pid1 <- newProcessId+ pid2 <- newProcessId+ + let machine :: ProcessId -> Process ()+ machine pid =+ do upTime <-+ liftParameter $+ randomExponential meanUpTime+ holdProcess upTime+ liftEvent $+ modifyRef totalUpTime (+ upTime) + + liftEvent $+ modifyRef nUp (+ (-1))+ nUp' <- liftEvent $ readRef nUp+ if nUp' == 1+ then passivateProcess+ else liftEvent $+ do n <- resourceCount repairPerson+ when (n == 1) $ + reactivateProcess pid+ + requestResource repairPerson+ repairTime <-+ liftParameter $+ randomExponential meanRepairTime+ holdProcess repairTime+ liftEvent $+ modifyRef nUp (+ 1)+ releaseResource repairPerson+ + machine pid++ runProcessInStartTimeUsingId+ pid1 (machine pid2)++ runProcessInStartTimeUsingId+ pid2 (machine pid1)++ let upTimeProp =+ do x <- readRef totalUpTime+ y <- liftDynamics time+ return $ x / (2 * y)++ return $+ results+ [resultSource+ "upTimeProp"+ "The long-run proportion of up time (~ 0.45)"+ upTimeProp]+ +main =+ printSimulationResultsInStopTime+ printResultSourceInEnglish+ model specs
+ examples/QuarryOperations.hs view
@@ -0,0 +1,208 @@++{-# LANGUAGE Arrows #-}++-- Example: In this example, the operations of a quarry are modeled.+--+-- It is described in different sources [1, 2]. So, this is chapter 10 of [2] and section 5.16 of [1].+--+-- [1] A. Alan B. Pritsker, Simulation with Visual SLAM and AweSim, 2nd ed.+--+-- [2] Труб И.И., Объектно-ориентированное моделирование на C++: Учебный курс. - СПб.: Питер, 2006++import Control.Monad+import Control.Monad.Trans+import Control.Category++import Simulation.Aivika+import qualified Simulation.Aivika.Queue.Infinite as IQ++-- | The simulation specs.+specs = Specs { spcStartTime = 0.0,+ spcStopTime = 480.0,+ spcDT = 0.1,+ spcMethod = RungeKutta4,+ spcGeneratorType = SimpleGenerator }++-- | The average loading time for twenty-ton truck+avgLoadingTime20 = 5++-- | A constant travel time for twenty-ton truck+travelTime20 = 2.5++-- | The average dumping time for twenty-ton truck+avgDumpingTime20 = 2++-- | A constant return trip time for twenty-ton truck+returnTripTime20 = 1.5++-- | A priority of the twenty-ton truck (less is higher)+crushingPriority20 = 2++-- | The average loading time for fifty-ton truck+avgLoadingTime50 = 10++-- | A constant travel time for fifty-ton truck+travelTime50 = 3++-- | The average dumping time for fifty-ton truck+avgDumpingTime50 = 4++-- | A constant return trip time for fifty-ton truck+returnTripTime50 = 2++-- | A priority of the fifty-ton truck (less is higher)+crushingPriority50 = 1++-- | It models a truck assigned to some queue.+data Truck =+ Truck { truckQueue :: TruckQueue,+ -- ^ a queue to which the truck is assigned+ truckTonSize :: TruckTonSize,+ -- ^ the truck ton size+ truckAvgLoadingTime :: Double,+ -- ^ the average loading time+ truckTravelTime :: Double,+ -- ^ a constant travel time+ truckCrushingPriority :: Double,+ -- ^ a priority for crushing (less is higher)+ truckAvgDumpingTime :: Double,+ -- ^ the average dumping time+ truckReturnTripTime :: Double+ -- ^ a constant return trip time+ }++-- | It defines the truck ton size+data TruckTonSize = TwentyTonSize | FiftyTonSize++-- | Specifies a queue to which the truck is assigned+data TruckQueue = TruckQueue1 | TruckQueue2 | TruckQueue3++-- | Return a truck assigned to the specified queue with the given ton size.+truck :: TruckQueue -> TruckTonSize -> Truck+truck tq TwentyTonSize =+ Truck { truckQueue = tq,+ truckTonSize = TwentyTonSize,+ truckAvgLoadingTime = avgLoadingTime20,+ truckTravelTime = travelTime20,+ truckCrushingPriority = crushingPriority20,+ truckAvgDumpingTime = avgDumpingTime20,+ truckReturnTripTime = returnTripTime20 }+truck tq FiftyTonSize =+ Truck { truckQueue = tq,+ truckTonSize = FiftyTonSize,+ truckAvgLoadingTime = avgLoadingTime50,+ truckTravelTime = travelTime50,+ truckCrushingPriority = crushingPriority50,+ truckAvgDumpingTime = avgDumpingTime50,+ truckReturnTripTime = returnTripTime50 }+ +model :: Simulation Results+model = do+ -- create a queue for the first shovel+ shovelQueue1 <-+ runEventInStartTime IQ.newFCFSQueue+ -- create another queue for the second shovel+ shovelQueue2 <-+ runEventInStartTime IQ.newFCFSQueue+ -- create a queue for the thrid shovel+ shovelQueue3 <-+ runEventInStartTime IQ.newFCFSQueue+ -- add initial trucks to the queue+ let initShovelQueue q tq =+ do IQ.enqueue q $ truck tq TwentyTonSize+ IQ.enqueue q $ truck tq TwentyTonSize+ IQ.enqueue q $ truck tq FiftyTonSize+ -- initiate the three shovel queues+ runEventInStartTime $+ do initShovelQueue shovelQueue1 TruckQueue1+ initShovelQueue shovelQueue2 TruckQueue2+ initShovelQueue shovelQueue3 TruckQueue3+ -- create a priority queue for the crusher+ crusherQueue <-+ runEventInStartTime IQ.newPriorityQueue+ -- define how the specified truck travels from the shovel to the crusher+ let truckTravel t =+ spawnProcess $+ do holdProcess (truckTravelTime t)+ liftEvent $+ IQ.enqueueWithStoringPriority crusherQueue (truckCrushingPriority t) t+ -- define how the specified truck returns to the queue+ let truckReturnTrip t =+ spawnProcess $+ do holdProcess (truckReturnTripTime t)+ let q = case truckQueue t of+ TruckQueue1 -> shovelQueue1+ TruckQueue2 -> shovelQueue2+ TruckQueue3 -> shovelQueue3+ liftEvent $+ IQ.enqueue q t+ -- utilise the crusher's activity+ let utiliseCrusher q t =+ do dumpingTime <-+ liftParameter $+ randomExponential $+ truckAvgDumpingTime t+ holdProcess dumpingTime+ return t+ -- utilise the shovel's activity+ let utiliseShovel q t =+ do loadingTime <-+ liftParameter $+ randomExponential $+ truckAvgLoadingTime t+ holdProcess loadingTime+ return t+ -- create shovel activities+ shovelAct1 <-+ newActivity $ utiliseShovel shovelQueue1+ shovelAct2 <-+ newActivity $ utiliseShovel shovelQueue2+ shovelAct3 <-+ newActivity $ utiliseShovel shovelQueue3+ -- create the crusher's activity+ crusherAct <-+ newActivity $ utiliseCrusher crusherQueue+ -- define how we should iterate the crusher+ let crusherNet act q =+ proc () ->+ do t <- arrNet (const $ IQ.dequeue q) -< ()+ t' <- activityNet act -< t+ arrNet truckReturnTrip -< t'+ let shovelNet act q =+ proc () ->+ do t <- arrNet (const $ IQ.dequeue q) -< ()+ t' <- activityNet act -< t+ arrNet truckTravel -< t'+ -- start processing the cursher's queue+ runProcessInStartTime $+ iterateNet (crusherNet crusherAct crusherQueue) ()+ -- start processing the shovel queues+ runProcessInStartTime $+ iterateNet (shovelNet shovelAct1 shovelQueue1) ()+ runProcessInStartTime $+ iterateNet (shovelNet shovelAct2 shovelQueue2) ()+ runProcessInStartTime $+ iterateNet (shovelNet shovelAct3 shovelQueue3) ()+ -- return the simulation results in start time+ return $+ results+ [resultSource+ "shovelQueue" "the shovel's queue"+ [shovelQueue1, shovelQueue2, shovelQueue3],+ --+ resultSource+ "crusherQueue" "the crusher's queue"+ crusherQueue,+ --+ resultSource+ "shovelActvty" "the shovel's activity"+ [shovelAct1, shovelAct2, shovelAct3],+ --+ resultSource+ "crusherActvty" "the crusher's activity"+ crusherAct]++main =+ printSimulationResultsInStopTime+ printResultSourceInEnglish+ (fmap resultSummary model) specs
examples/TimeOut.hs view
@@ -1,96 +1,96 @@- --- It corresponds to model TimeOut described in document --- Advanced Features of the SimPy Language --- [http://heather.cs.ucdavis.edu/~matloff/156/PLN/AdvancedSimPy.pdf]. --- SimPy is available on [http://simpy.sourceforge.net/]. --- --- The model description is as follows. --- --- Introductory example to illustrate the modeling of "competing --- events" such as timeouts, especially using the cancelProcess function. A --- network node sends a message but also sets a timeout period; if the --- node times out, it assumes the message it had sent was lost, and it --- will send again. The time to get an acknowledgement for a message is --- exponentially distributed with mean 1.0, and the timeout period is --- 0.5. Immediately after receiving an acknowledgement, the node sends --- out a new message. --- --- We find the proportion of messages which timeout. The output should --- be about 0.61. - -import Control.Monad -import Control.Monad.Trans - -import Simulation.Aivika - -ackRate = 1.0 / 1.0 -- reciprocal of the acknowledge mean time -toPeriod = 0.5 -- timeout period - -specs = Specs { spcStartTime = 0.0, - spcStopTime = 10000.0, - spcDT = 1.0, - spcMethod = RungeKutta4, - spcGeneratorType = SimpleGenerator } - -model :: Simulation Double -model = - do -- number of messages sent - nMsgs <- newRef 0 - - -- number of timeouts which have occured - nTimeOuts <- newRef 0 - - -- reactivatedCode will 1 if timeout occurred, - -- 2 ACK if received - reactivatedCode <- newRef 0 - - nodePid <- newProcessId - - let node :: Process () - node = - do liftEvent $ modifyRef nMsgs $ (+) 1 - -- create process IDs - timeoutPid <- liftSimulation newProcessId - ackPid <- liftSimulation newProcessId - -- set up the timeout - liftEvent $ runProcessUsingId timeoutPid (timeout ackPid) - -- set up the message send/ACK - liftEvent $ runProcessUsingId ackPid (acknowledge timeoutPid) - passivateProcess - liftEvent $ - do code <- readRef reactivatedCode - when (code == 1) $ - modifyRef nTimeOuts $ (+) 1 - writeRef reactivatedCode 0 - node - - timeout :: ProcessId -> Process () - timeout ackPid = - do holdProcess toPeriod - liftEvent $ - do writeRef reactivatedCode 1 - reactivateProcess nodePid - cancelProcessWithId ackPid - - acknowledge :: ProcessId -> Process () - acknowledge timeoutPid = - do ackTime <- - liftParameter $ - randomExponential (1 / ackRate) - holdProcess ackTime - liftEvent $ - do writeRef reactivatedCode 2 - reactivateProcess nodePid - cancelProcessWithId timeoutPid - - runProcessInStartTimeUsingId - nodePid node - - runEventInStopTime $ - do x <- readRef nTimeOuts - y <- readRef nMsgs - return $ x / y - -main = - do putStr "The percentage of timeout was " - runSimulation model specs >>= print ++-- It corresponds to model TimeOut described in document +-- Advanced Features of the SimPy Language+-- [http://heather.cs.ucdavis.edu/~matloff/156/PLN/AdvancedSimPy.pdf]. +-- SimPy is available on [http://simpy.sourceforge.net/].+-- +-- The model description is as follows.+--+-- Introductory example to illustrate the modeling of "competing+-- events" such as timeouts, especially using the cancelProcess function. A+-- network node sends a message but also sets a timeout period; if the+-- node times out, it assumes the message it had sent was lost, and it+-- will send again. The time to get an acknowledgement for a message is+-- exponentially distributed with mean 1.0, and the timeout period is+-- 0.5. Immediately after receiving an acknowledgement, the node sends+-- out a new message.+--+-- We find the proportion of messages which timeout. The output should+-- be about 0.61.++import Control.Monad+import Control.Monad.Trans++import Simulation.Aivika++ackRate = 1.0 / 1.0 -- reciprocal of the acknowledge mean time+toPeriod = 0.5 -- timeout period++specs = Specs { spcStartTime = 0.0,+ spcStopTime = 10000.0,+ spcDT = 1.0,+ spcMethod = RungeKutta4,+ spcGeneratorType = SimpleGenerator }+ +model :: Simulation Double+model =+ do -- number of messages sent+ nMsgs <- newRef 0+ + -- number of timeouts which have occured+ nTimeOuts <- newRef 0+ + -- reactivatedCode will 1 if timeout occurred, + -- 2 ACK if received+ reactivatedCode <- newRef 0+ + nodePid <- newProcessId+ + let node :: Process ()+ node =+ do liftEvent $ modifyRef nMsgs $ (+) 1+ -- create process IDs+ timeoutPid <- liftSimulation newProcessId+ ackPid <- liftSimulation newProcessId+ -- set up the timeout+ liftEvent $ runProcessUsingId timeoutPid (timeout ackPid)+ -- set up the message send/ACK+ liftEvent $ runProcessUsingId ackPid (acknowledge timeoutPid)+ passivateProcess+ liftEvent $+ do code <- readRef reactivatedCode+ when (code == 1) $+ modifyRef nTimeOuts $ (+) 1+ writeRef reactivatedCode 0+ node+ + timeout :: ProcessId -> Process ()+ timeout ackPid =+ do holdProcess toPeriod+ liftEvent $+ do writeRef reactivatedCode 1+ reactivateProcess nodePid+ cancelProcessWithId ackPid+ + acknowledge :: ProcessId -> Process ()+ acknowledge timeoutPid =+ do ackTime <-+ liftParameter $+ randomExponential (1 / ackRate)+ holdProcess ackTime+ liftEvent $+ do writeRef reactivatedCode 2+ reactivateProcess nodePid+ cancelProcessWithId timeoutPid++ runProcessInStartTimeUsingId+ nodePid node+ + runEventInStopTime $+ do x <- readRef nTimeOuts+ y <- readRef nMsgs+ return $ x / y+ +main = + do putStr "The percentage of timeout was "+ runSimulation model specs >>= print
examples/TimeOutInt.hs view
@@ -1,77 +1,77 @@- --- It corresponds to model TimeOutInt described in document --- Advanced Features of the SimPy Language --- [http://heather.cs.ucdavis.edu/~matloff/156/PLN/AdvancedSimPy.pdf]. --- SimPy is available on [http://simpy.sourceforge.net/]. --- --- The model description is as follows. --- --- Same as TimeOut.hs but using interrupts. A network node sends a message --- but also sets a timeout period; if the node times out, it assumes the --- message it had sent was lost, and it will send again. The time to get --- an acknowledgement for a message is exponentially distributed with --- mean 1.0, and the timeout period is 0.5. Immediately after receiving --- an acknowledgement, the node sends out a new message. --- --- We find the proportion of messages which timeout. The output should --- be about 0.61. - -import Control.Monad -import Control.Monad.Trans - -import Simulation.Aivika - -ackRate = 1.0 / 1.0 -- reciprocal of the acknowledge mean time -toPeriod = 0.5 -- timeout period - -specs = Specs { spcStartTime = 0.0, - spcStopTime = 10000.0, - spcDT = 1.0, - spcMethod = RungeKutta4, - spcGeneratorType = SimpleGenerator } - -model :: Simulation Double -model = - do -- number of messages sent - nMsgs <- newRef 0 - - -- number of timeouts which have occured - nTimeOuts <- newRef 0 - - nodePid <- newProcessId - - let node :: Process () - node = - do liftEvent $ modifyRef nMsgs $ (+) 1 - -- create the process ID - timeoutPid <- liftSimulation newProcessId - -- set up the timeout - liftEvent $ runProcessUsingId timeoutPid timeout - -- wait for ACK, but could be timeout - ackTime <- - liftParameter $ - randomExponential (1 / ackRate) - holdProcess ackTime - liftEvent $ - do interrupted <- processInterrupted nodePid - if interrupted - then modifyRef nTimeOuts $ (+) 1 - else cancelProcessWithId timeoutPid - node - - timeout :: Process () - timeout = - do holdProcess toPeriod - liftEvent $ interruptProcess nodePid - - runProcessInStartTimeUsingId - nodePid node - - runEventInStopTime $ - do x <- readRef nTimeOuts - y <- readRef nMsgs - return $ x / y - -main = - do putStr "The percentage of timeout was " - runSimulation model specs >>= print ++-- It corresponds to model TimeOutInt described in document +-- Advanced Features of the SimPy Language+-- [http://heather.cs.ucdavis.edu/~matloff/156/PLN/AdvancedSimPy.pdf]. +-- SimPy is available on [http://simpy.sourceforge.net/].+-- +-- The model description is as follows.+--+-- Same as TimeOut.hs but using interrupts. A network node sends a message+-- but also sets a timeout period; if the node times out, it assumes the+-- message it had sent was lost, and it will send again. The time to get+-- an acknowledgement for a message is exponentially distributed with+-- mean 1.0, and the timeout period is 0.5. Immediately after receiving+-- an acknowledgement, the node sends out a new message.+--+-- We find the proportion of messages which timeout. The output should+-- be about 0.61.++import Control.Monad+import Control.Monad.Trans++import Simulation.Aivika++ackRate = 1.0 / 1.0 -- reciprocal of the acknowledge mean time+toPeriod = 0.5 -- timeout period++specs = Specs { spcStartTime = 0.0,+ spcStopTime = 10000.0,+ spcDT = 1.0,+ spcMethod = RungeKutta4,+ spcGeneratorType = SimpleGenerator }+ +model :: Simulation Double+model =+ do -- number of messages sent+ nMsgs <- newRef 0+ + -- number of timeouts which have occured+ nTimeOuts <- newRef 0++ nodePid <- newProcessId+ + let node :: Process ()+ node =+ do liftEvent $ modifyRef nMsgs $ (+) 1+ -- create the process ID+ timeoutPid <- liftSimulation newProcessId+ -- set up the timeout+ liftEvent $ runProcessUsingId timeoutPid timeout+ -- wait for ACK, but could be timeout+ ackTime <-+ liftParameter $+ randomExponential (1 / ackRate)+ holdProcess ackTime+ liftEvent $+ do interrupted <- processInterrupted nodePid+ if interrupted+ then modifyRef nTimeOuts $ (+) 1+ else cancelProcessWithId timeoutPid+ node+ + timeout :: Process ()+ timeout =+ do holdProcess toPeriod+ liftEvent $ interruptProcess nodePid++ runProcessInStartTimeUsingId+ nodePid node + + runEventInStopTime $+ do x <- readRef nTimeOuts+ y <- readRef nMsgs+ return $ x / y+ +main = + do putStr "The percentage of timeout was "+ runSimulation model specs >>= print
examples/TimeOutWait.hs view
@@ -1,70 +1,70 @@- --- It corresponds to model TimeOut described in document --- Advanced Features of the SimPy Language --- [http://heather.cs.ucdavis.edu/~matloff/156/PLN/AdvancedSimPy.pdf]. --- SimPy is available on [http://simpy.sourceforge.net/]. --- --- The model description is as follows. --- --- Introductory example to illustrate the modeling of "competing --- events" such as timeouts, especially using the timeoutProcess --- function. A network node starts a process within the specified --- timeout and receives a signal that notifies whether the process --- has finished successfully within the timeout; if the node --- times out, it assumes the message it had sent was lost, and it --- will send again. The time to get an acknowledgement for a message is --- exponentially distributed with mean 1.0, and the timeout period is --- 0.5. Immediately after receiving an acknowledgement, the node sends --- out a new message. --- --- We find the proportion of messages which timeout. The output should --- be about 0.61. - -import Control.Monad -import Control.Monad.Trans - -import Data.Maybe - -import Simulation.Aivika - -ackRate = 1.0 / 1.0 -- reciprocal of the acknowledge mean time -toPeriod = 0.5 -- timeout period - -specs = Specs { spcStartTime = 0.0, - spcStopTime = 10000.0, - spcDT = 1.0, - spcMethod = RungeKutta4, - spcGeneratorType = SimpleGenerator } - -model :: Simulation Double -model = - do -- number of messages sent - nMsgs <- newRef 0 - - -- number of timeouts which have occured - nTimeOuts <- newRef 0 - - let node :: Process () - node = - do liftEvent $ modifyRef nMsgs $ (+) 1 - result <- - timeoutProcess toPeriod $ - do ackTime <- - liftParameter $ - randomExponential (1 / ackRate) - holdProcess ackTime - liftEvent $ - when (isNothing result) $ - modifyRef nTimeOuts $ (+) 1 - node - - runProcessInStartTime node - - runEventInStopTime $ - do x <- readRef nTimeOuts - y <- readRef nMsgs - return $ x / y - -main = - do putStr "The percentage of timeout was " - runSimulation model specs >>= print ++-- It corresponds to model TimeOut described in document +-- Advanced Features of the SimPy Language+-- [http://heather.cs.ucdavis.edu/~matloff/156/PLN/AdvancedSimPy.pdf]. +-- SimPy is available on [http://simpy.sourceforge.net/].+-- +-- The model description is as follows.+--+-- Introductory example to illustrate the modeling of "competing+-- events" such as timeouts, especially using the timeoutProcess+-- function. A network node starts a process within the specified +-- timeout and receives a signal that notifies whether the process +-- has finished successfully within the timeout; if the node+-- times out, it assumes the message it had sent was lost, and it+-- will send again. The time to get an acknowledgement for a message is+-- exponentially distributed with mean 1.0, and the timeout period is+-- 0.5. Immediately after receiving an acknowledgement, the node sends+-- out a new message.+--+-- We find the proportion of messages which timeout. The output should+-- be about 0.61.++import Control.Monad+import Control.Monad.Trans++import Data.Maybe++import Simulation.Aivika++ackRate = 1.0 / 1.0 -- reciprocal of the acknowledge mean time+toPeriod = 0.5 -- timeout period++specs = Specs { spcStartTime = 0.0,+ spcStopTime = 10000.0,+ spcDT = 1.0,+ spcMethod = RungeKutta4,+ spcGeneratorType = SimpleGenerator }+ +model :: Simulation Double+model =+ do -- number of messages sent+ nMsgs <- newRef 0+ + -- number of timeouts which have occured+ nTimeOuts <- newRef 0+ + let node :: Process ()+ node =+ do liftEvent $ modifyRef nMsgs $ (+) 1+ result <-+ timeoutProcess toPeriod $+ do ackTime <-+ liftParameter $+ randomExponential (1 / ackRate)+ holdProcess ackTime+ liftEvent $+ when (isNothing result) $+ modifyRef nTimeOuts $ (+) 1+ node++ runProcessInStartTime node+ + runEventInStopTime $+ do x <- readRef nTimeOuts+ y <- readRef nMsgs+ return $ x / y+ +main = + do putStr "The percentage of timeout was "+ runSimulation model specs >>= print
examples/WorkStationsInSeries.hs view
@@ -1,139 +1,139 @@- --- Example: Work Stations in Series --- --- This is a model of two work stations connected in a series and separated by finite queues. --- --- It is described in different sources [1, 2]. So, this is chapter 7 of [2] and section 5.14 of [1]. --- --- [1] A. Alan B. Pritsker, Simulation with Visual SLAM and AweSim, 2nd ed. --- --- [2] Труб И.И., Объектно-ориентированное моделирование на C++: Учебный курс. - СПб.: Питер, 2006 - -import Prelude hiding (id, (.)) - -import Control.Monad -import Control.Monad.Trans -import Control.Arrow -import Control.Category (id, (.)) - -import Simulation.Aivika -import Simulation.Aivika.Queue - --- | The simulation specs. -specs = Specs { spcStartTime = 0.0, - spcStopTime = 300.0, - spcDT = 0.1, - spcMethod = RungeKutta4, - spcGeneratorType = SimpleGenerator } - --- the mean delay of the input arrivals distributed exponentially -meanOrderDelay = 0.4 - --- the capacity of the queue before the first work places -queueMaxCount1 = 4 - --- the capacity of the queue before the second work places -queueMaxCount2 = 2 - --- the mean processing time distributed exponentially in --- the first work stations -meanProcessingTime1 = 0.25 - --- the mean processing time distributed exponentially in --- the second work stations -meanProcessingTime2 = 0.5 - --- the number of the first work stations --- (in parallel but the commented code allocates them sequentially) -workStationCount1 = 1 - --- the number of the second work stations --- (in parallel but the commented code allocates them sequentially) -workStationCount2 = 1 - --- create a work station (server) with the exponential processing time -newWorkStationExponential meanTime = - newServer $ \a -> - do holdProcess =<< - (liftParameter $ - randomExponential meanTime) - return a - --- interpose the prefetch processor between two processors -interposePrefetchProcessor x y = - x >>> prefetchProcessor >>> y - -model :: Simulation Results -model = do - -- it will gather the statistics of the processing time - arrivalTimer <- newArrivalTimer - -- define a stream of input events - let inputStream = randomExponentialStream meanOrderDelay - -- create a queue before the first work stations - queue1 <- - runEventInStartTime $ - newFCFSQueue queueMaxCount1 - -- create a queue before the second work stations - queue2 <- - runEventInStartTime $ - newFCFSQueue queueMaxCount2 - -- create the first work stations (servers) - workStation1s <- forM [1 .. workStationCount1] $ \_ -> - newWorkStationExponential meanProcessingTime1 - -- create the second work stations (servers) - workStation2s <- forM [1 .. workStationCount2] $ \_ -> - newWorkStationExponential meanProcessingTime2 - -- processor for the queue before the first work station - let queueProcessor1 = - queueProcessor - (\a -> liftEvent $ enqueueOrLost_ queue1 a) - (dequeue queue1) - -- processor for the queue before the second work station - let queueProcessor2 = - queueProcessor - (enqueue queue2) - (dequeue queue2) - -- the entire processor from input to output - let entireProcessor = - queueProcessor1 >>> - processorParallel (map serverProcessor workStation1s) >>> - -- foldr1 interposePrefetchProcessor (map serverProcessor workStation1s) >>> - queueProcessor2 >>> - processorParallel (map serverProcessor workStation2s) >>> - -- foldr1 interposePrefetchProcessor (map serverProcessor workStation2s) >>> - arrivalTimerProcessor arrivalTimer - -- start simulating the model - runProcessInStartTime $ - sinkStream $ runProcessor entireProcessor inputStream - -- return the simulation results - return $ - results - [resultSource - "queue1" "Queue no. 1" - queue1, - -- - resultSource - "workStation1s" "Work Stations of line no. 1" - workStation1s, - -- - resultSource - "queue2" "Queue no. 2" - queue2, - -- - resultSource - "workStation2s" "Work Stations of line no. 2" - workStation2s, - -- - resultSource - "arrivalTimer" "The arrival timer" - arrivalTimer] - -modelSummary :: Simulation Results -modelSummary = - fmap resultSummary model - -main = - printSimulationResultsInStopTime - printResultSourceInEnglish - -- model specs - modelSummary specs ++-- Example: Work Stations in Series+--+-- This is a model of two work stations connected in a series and separated by finite queues.+--+-- It is described in different sources [1, 2]. So, this is chapter 7 of [2] and section 5.14 of [1].+--+-- [1] A. Alan B. Pritsker, Simulation with Visual SLAM and AweSim, 2nd ed.+--+-- [2] Труб И.И., Объектно-ориентированное моделирование на C++: Учебный курс. - СПб.: Питер, 2006++import Prelude hiding (id, (.)) ++import Control.Monad+import Control.Monad.Trans+import Control.Arrow+import Control.Category (id, (.))++import Simulation.Aivika+import Simulation.Aivika.Queue++-- | The simulation specs.+specs = Specs { spcStartTime = 0.0,+ spcStopTime = 300.0,+ spcDT = 0.1,+ spcMethod = RungeKutta4,+ spcGeneratorType = SimpleGenerator }++-- the mean delay of the input arrivals distributed exponentially+meanOrderDelay = 0.4 ++-- the capacity of the queue before the first work places+queueMaxCount1 = 4++-- the capacity of the queue before the second work places+queueMaxCount2 = 2++-- the mean processing time distributed exponentially in+-- the first work stations+meanProcessingTime1 = 0.25++-- the mean processing time distributed exponentially in+-- the second work stations+meanProcessingTime2 = 0.5++-- the number of the first work stations+-- (in parallel but the commented code allocates them sequentially)+workStationCount1 = 1++-- the number of the second work stations+-- (in parallel but the commented code allocates them sequentially)+workStationCount2 = 1++-- create a work station (server) with the exponential processing time+newWorkStationExponential meanTime =+ newServer $ \a ->+ do holdProcess =<<+ (liftParameter $+ randomExponential meanTime)+ return a++-- interpose the prefetch processor between two processors+interposePrefetchProcessor x y = + x >>> prefetchProcessor >>> y++model :: Simulation Results+model = do+ -- it will gather the statistics of the processing time+ arrivalTimer <- newArrivalTimer+ -- define a stream of input events+ let inputStream = randomExponentialStream meanOrderDelay + -- create a queue before the first work stations+ queue1 <-+ runEventInStartTime $+ newFCFSQueue queueMaxCount1+ -- create a queue before the second work stations+ queue2 <-+ runEventInStartTime $+ newFCFSQueue queueMaxCount2+ -- create the first work stations (servers)+ workStation1s <- forM [1 .. workStationCount1] $ \_ ->+ newWorkStationExponential meanProcessingTime1+ -- create the second work stations (servers)+ workStation2s <- forM [1 .. workStationCount2] $ \_ ->+ newWorkStationExponential meanProcessingTime2+ -- processor for the queue before the first work station+ let queueProcessor1 =+ queueProcessor+ (\a -> liftEvent $ enqueueOrLost_ queue1 a)+ (dequeue queue1)+ -- processor for the queue before the second work station+ let queueProcessor2 =+ queueProcessor+ (enqueue queue2)+ (dequeue queue2)+ -- the entire processor from input to output+ let entireProcessor =+ queueProcessor1 >>>+ processorParallel (map serverProcessor workStation1s) >>>+ -- foldr1 interposePrefetchProcessor (map serverProcessor workStation1s) >>>+ queueProcessor2 >>>+ processorParallel (map serverProcessor workStation2s) >>>+ -- foldr1 interposePrefetchProcessor (map serverProcessor workStation2s) >>>+ arrivalTimerProcessor arrivalTimer+ -- start simulating the model+ runProcessInStartTime $+ sinkStream $ runProcessor entireProcessor inputStream+ -- return the simulation results+ return $+ results+ [resultSource+ "queue1" "Queue no. 1"+ queue1,+ --+ resultSource+ "workStation1s" "Work Stations of line no. 1"+ workStation1s,+ --+ resultSource+ "queue2" "Queue no. 2"+ queue2,+ --+ resultSource+ "workStation2s" "Work Stations of line no. 2"+ workStation2s,+ --+ resultSource+ "arrivalTimer" "The arrival timer"+ arrivalTimer]++modelSummary :: Simulation Results+modelSummary =+ fmap resultSummary model++main =+ printSimulationResultsInStopTime+ printResultSourceInEnglish+ -- model specs+ modelSummary specs