packages feed

aivika (empty) → 0.1

raw patch · 15 files changed

+2625/−0 lines, 15 filesdep +arraydep +basedep +mtlsetup-changedbinary-added

Dependencies added: array, base, mtl

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2009, 2010, 2011 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
@@ -0,0 +1,3 @@+#!/usr/bin/env runhaskell+> import Distribution.Simple+> main = defaultMain
+ Simulation/Aivika/Dynamics.hs view
@@ -0,0 +1,1628 @@++-- Copyright (c) 2009, 2010, 2011 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.++{-# LANGUAGE FlexibleContexts, FlexibleInstances, UndecidableInstances #-}++-- |+-- Module     : Simulation.Aivika.Dynamics+-- Copyright  : Copyright (c) 2009-2011, David Sorokin <david.sorokin@gmail.com>+-- License    : BSD3+-- Maintainer : David Sorokin <david.sorokin@gmail.com>+-- Stability  : experimental+-- Tested with: GHC 6.12.1+--+-- Aivika is a multi-paradigm simulation library. It allows us to integrate +-- a system of ordinary differential equations. Also it can be applied to+-- the Discrete Event Simulation. It supports the event-oriented, +-- process-oriented and activity-oriented paradigms. Aivika also supports +-- the Agent-based Modeling. Finally, it can be applied to System Dynamics.+--+module Simulation.Aivika.Dynamics +       (-- * Dynamics+        Dynamics,+        DynamicsTrans(..),+        Specs(..),+        Method(..),+        runDynamics1,+        runDynamics,+        runDynamicsIO,+        -- ** Time parameters+        starttime,+        stoptime,+        dt,+        time,+        -- ** Maximum and Minimum+        maxD,+        minD,+        -- ** Integrals+        Integ,+        newInteg,+        integInit,+        integValue,+        integDiff,+        -- ** Table Functions+        lookupD,+        lookupStepwiseD,+        -- ** Interpolation+        initD,+        discrete,+        interpolate,+        -- ** Memoization and Sequential Calculations+        Memo,+        UMemo,+        memo,+        umemo,+        memo0,+        umemo0,+        -- ** Utility+        once,+        -- * Event Queue+        DynamicsQueue,+        newQueue,+        enqueueDC,+        enqueueD,+        runQueue,+        -- * References+        DynamicsRef,+        newRef,+        refQueue,+        readRef,+        writeRef,+        writeRef',+        modifyRef,+        modifyRef',+        -- * Discontinuous Processes+        DynamicsPID,+        DynamicsProc,+        newPID,+        pidQueue,+        holdProcD,+        holdProc,+        passivateProc,+        procPassive,+        reactivateProc,+        procPID,+        runProc,+        -- * Resources+        DynamicsResource,+        newResource,+        resourceQueue,+        resourceInitCount,+        resourceCount,+        requestResource,+        releaseResource,+        -- * Agent-based Modeling+        Agent,+        AgentState,+        newAgent,+        newState,+        newSubstate,+        agentQueue,+        agentState,+        activateState,+        initState,+        stateAgent,+        stateParent,+        addTimeoutD,+        addTimeout,+        addTimerD,+        addTimer,+        stateActivation,+        stateDeactivation) where++import Data.Array+import Data.Array.IO+import Data.IORef+import Control.Monad+import Control.Monad.Trans++import qualified Simulation.Aivika.Queue as Q+import qualified Simulation.Aivika.PriorityQueue as PQ++--+-- The Dynamics Monad+--+-- A value of the Dynamics monad represents an abstract dynamic +-- process, i.e. a time varying polymorphic function. This is +-- a key point of the Aivika simulation library.+--++-- | A value in the 'Dynamics' monad represents a dynamic process, i.e.+-- a polymorphic time varying function.+newtype Dynamics a = Dynamics (Parameters -> IO a)++-- | It defines the simulation time appended with additional information.+data Parameters = Parameters { parSpecs :: Specs,    -- ^ the simulation specs+                               parTime :: Double,    -- ^ the current time+                               parIteration :: Int,  -- ^ the current iteration+                               parPhase :: Int }     -- ^ the current phase++-- | It defines the simulation specs.+data Specs = Specs { spcStartTime :: Double,    -- ^ the start time+                     spcStopTime :: Double,     -- ^ the stop time+                     spcDT :: Double,           -- ^ the integration time step+                     spcMethod :: Method        -- ^ the integration method+                   } deriving (Eq, Ord, Show)++-- | 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)++iterations :: Specs -> [Int]+iterations sc = [i1 .. i2] where+  i1 = 0+  i2 = round ((spcStopTime sc - +               spcStartTime sc) / spcDT sc)++iterationBnds :: Specs -> (Int, Int)+iterationBnds sc = (0, round ((spcStopTime sc - +                               spcStartTime sc) / spcDT sc))++iterationLoBnd :: Specs -> Int+iterationLoBnd sc = 0++iterationHiBnd :: Specs -> Int+iterationHiBnd sc = round ((spcStopTime sc - +                            spcStartTime sc) / spcDT sc)++phases :: Specs -> [Int]+phases sc = +  case spcMethod sc of+    Euler -> [0]+    RungeKutta2 -> [0, 1]+    RungeKutta4 -> [0, 1, 2, 3]++phaseBnds :: Specs -> (Int, Int)+phaseBnds sc = +  case spcMethod sc of+    Euler -> (0, 0)+    RungeKutta2 -> (0, 1)+    RungeKutta4 -> (0, 3)++phaseLoBnd :: Specs -> Int+phaseLoBnd sc = 0+                  +phaseHiBnd :: Specs -> Int+phaseHiBnd sc = +  case spcMethod sc of+    Euler -> 0+    RungeKutta2 -> 1+    RungeKutta4 -> 3++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' = fromInteger (toInteger 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++neighborhood :: Specs -> Double -> Double -> Bool+neighborhood sc t t' = +  abs (t - t') <= spcDT sc / 1.0e6++instance Monad Dynamics where+  return  = returnD+  m >>= k = bindD m k++returnD :: a -> Dynamics a+returnD a = Dynamics (\ps -> return a)++bindD :: Dynamics a -> (a -> Dynamics b) -> Dynamics b+bindD (Dynamics m) k = +  Dynamics $ \ps -> +  do a <- m ps+     let Dynamics m' = k a+     m' ps++subrunDynamics1 :: Dynamics a -> Specs -> IO a+subrunDynamics1 (Dynamics m) sc =+  do let n = iterationHiBnd sc+         t = basicTime sc n 0+     m Parameters { parSpecs = sc,+                    parTime = t,+                    parIteration = n,+                    parPhase = 0 }++subrunDynamics :: Dynamics a -> Specs -> [IO a]+subrunDynamics (Dynamics m) sc =+  do let (nl, nu) = iterationBnds sc+         parameterise n = Parameters { parSpecs = sc,+                                       parTime = basicTime sc n 0,+                                       parIteration = n,+                                       parPhase = 0 }+     map (m . parameterise) [nl .. nu]++-- | Run the simulation and return the result in the last +-- time point using the specified simulation specs.+runDynamics1 :: Dynamics (Dynamics a) -> Specs -> IO a+runDynamics1 (Dynamics m) sc = +  do d <- m Parameters { parSpecs = sc,+                         parTime = spcStartTime sc,+                         parIteration = 0,+                         parPhase = 0 }+     subrunDynamics1 d sc++-- | Run the simulation and return the results in all +-- integration time points using the specified simulation specs.+runDynamics :: Dynamics (Dynamics a) -> Specs -> IO [a]+runDynamics (Dynamics m) sc = +  do d <- m Parameters { parSpecs = sc,+                         parTime = spcStartTime sc,+                         parIteration = 0,+                         parPhase = 0 }+     sequence $ subrunDynamics d sc++-- | Run the simulation and return the results in all +-- integration time points using the specified simulation specs.+runDynamicsIO :: Dynamics (Dynamics a) -> Specs -> IO [IO a]+runDynamicsIO (Dynamics m) sc =+  do d <- m Parameters { parSpecs = sc,+                         parTime = spcStartTime sc,+                         parIteration = 0,+                         parPhase = 0 }+     return $ subrunDynamics d sc++instance Functor Dynamics where+  fmap f (Dynamics m) = +    Dynamics $ \ps -> do { a <- m ps; return $ f a }++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+liftMD f (Dynamics x) =+  Dynamics $ \ps -> do { a <- x ps; return $ f a }++liftM2D :: (a -> b -> c) -> Dynamics a -> Dynamics b -> Dynamics c+liftM2D f (Dynamics x) (Dynamics y) =+  Dynamics $ \ps -> do { a <- x ps; b <- y ps; 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++-- | The 'DynamicsTrans' class defines a type which the 'Dynamics' +-- computation can be lifted to.+class DynamicsTrans m where+  -- | Lift the computation.+  liftD :: Dynamics a -> m a++--+-- Integration Parameters and Time+--++-- | Return the start simulation time.+starttime :: Dynamics Double+starttime = Dynamics $ return . spcStartTime . parSpecs++-- | Return the stop simulation time.+stoptime :: Dynamics Double+stoptime = Dynamics $ return . spcStopTime . parSpecs++-- | Return the integration time step.+dt :: Dynamics Double+dt = Dynamics $ return . spcDT . parSpecs++-- | Return the current simulation time.+time :: Dynamics Double+time = Dynamics $ return . parTime ++--+-- Maximum and Minimum+--++-- | Return the maximum.+maxD :: (Ord a) => Dynamics a -> Dynamics a -> Dynamics a+maxD = liftM2D max++-- | Return the minimum.+minD :: (Ord a) => Dynamics a -> Dynamics a -> Dynamics a+minD = liftM2D min+           +--+-- System Dynamics+--++-- | The 'Integ' type represents an integral.+data Integ = Integ { integInit     :: Dynamics Double,   -- ^ The initial value.+                     integExternal :: IORef (Dynamics Double),+                     integInternal :: IORef (Dynamics Double) }++-- | Create a new integral with the specified initial value.+newInteg :: Dynamics Double -> Dynamics Integ+newInteg i = +  do r1 <- liftIO $ newIORef $ initD i +     r2 <- liftIO $ newIORef $ initD i +     let integ = Integ { integInit     = i, +                         integExternal = r1,+                         integInternal = r2 }+         z = Dynamics $ \ps -> +           do (Dynamics m) <- readIORef (integInternal integ)+              m ps+     y <- umemo interpolate z+     liftIO $ writeIORef (integExternal integ) y+     return integ++-- | Return the integral's value.+integValue :: Integ -> Dynamics Double+integValue integ = +  Dynamics $ \ps ->+  do (Dynamics m) <- readIORef (integExternal integ)+     m ps++-- | Set the derivative for the integral.+integDiff :: Integ -> Dynamics Double -> Dynamics ()+integDiff integ diff =+  do let z = Dynamics $ \ps ->+           do y <- readIORef (integExternal integ)+              let i = integInit integ+              case spcMethod (parSpecs ps) of+                Euler -> integEuler diff i y ps+                RungeKutta2 -> integRK2 diff i y ps+                RungeKutta4 -> integRK4 diff i y ps+     liftIO $ writeIORef (integInternal integ) z++integEuler :: Dynamics Double+             -> Dynamics Double +             -> Dynamics Double +             -> Parameters -> IO Double+integEuler (Dynamics f) (Dynamics i) (Dynamics y) ps = +  case parIteration ps of+    0 -> +      i ps+    n -> do +      let sc  = parSpecs ps+          ty  = basicTime sc (n - 1) 0+          psy = ps { parTime = ty, parIteration = n - 1, parPhase = 0 }+      a <- y psy+      b <- f psy+      let !v = a + spcDT (parSpecs ps) * b+      return v++integRK2 :: Dynamics Double+           -> Dynamics Double+           -> Dynamics Double+           -> Parameters -> IO Double+integRK2 (Dynamics f) (Dynamics i) (Dynamics y) ps =+  case parPhase ps of+    0 -> case parIteration ps of+      0 ->+        i ps+      n -> do+        let sc = parSpecs ps+            ty = basicTime sc (n - 1) 0+            t1 = ty+            t2 = basicTime sc (n - 1) 1+            psy = ps { parTime = ty, parIteration = n - 1, parPhase = 0 }+            ps1 = psy+            ps2 = ps { parTime = t2, parIteration = n - 1, parPhase = 1 }+        vy <- y psy+        k1 <- f ps1+        k2 <- f ps2+        let !v = vy + spcDT sc / 2.0 * (k1 + k2)+        return v+    1 -> do+      let sc = parSpecs ps+          n  = parIteration ps+          ty = basicTime sc n 0+          t1 = ty+          psy = ps { parTime = ty, parIteration = n, parPhase = 0 }+          ps1 = psy+      vy <- y psy+      k1 <- f ps1+      let !v = vy + spcDT sc * k1+      return v+    _ -> +      error "Incorrect phase: integ"++integRK4 :: Dynamics Double+           -> Dynamics Double+           -> Dynamics Double+           -> Parameters -> IO Double+integRK4 (Dynamics f) (Dynamics i) (Dynamics y) ps =+  case parPhase ps of+    0 -> case parIteration ps of+      0 -> +        i ps+      n -> do+        let sc = parSpecs ps+            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+            psy = ps { parTime = ty, parIteration = n - 1, parPhase = 0 }+            ps1 = psy+            ps2 = ps { parTime = t2, parIteration = n - 1, parPhase = 1 }+            ps3 = ps { parTime = t3, parIteration = n - 1, parPhase = 2 }+            ps4 = ps { parTime = t4, parIteration = n - 1, parPhase = 3 }+        vy <- y psy+        k1 <- f ps1+        k2 <- f ps2+        k3 <- f ps3+        k4 <- f ps4+        let !v = vy + spcDT sc / 6.0 * (k1 + 2.0 * k2 + 2.0 * k3 + k4)+        return v+    1 -> do+      let sc = parSpecs ps+          n  = parIteration ps+          ty = basicTime sc n 0+          t1 = ty+          psy = ps { parTime = ty, parIteration = n, parPhase = 0 }+          ps1 = psy+      vy <- y psy+      k1 <- f ps1+      let !v = vy + spcDT sc / 2.0 * k1+      return v+    2 -> do+      let sc = parSpecs ps+          n  = parIteration ps+          ty = basicTime sc n 0+          t2 = basicTime sc n 1+          psy = ps { parTime = ty, parIteration = n, parPhase = 0 }+          ps2 = ps { parTime = t2, parIteration = n, parPhase = 1 }+      vy <- y psy+      k2 <- f ps2+      let !v = vy + spcDT sc / 2.0 * k2+      return v+    3 -> do+      let sc = parSpecs ps+          n  = parIteration ps+          ty = basicTime sc n 0+          t3 = basicTime sc n 2+          psy = ps { parTime = ty, parIteration = n, parPhase = 0 }+          ps3 = ps { parTime = t3, parIteration = n, parPhase = 2 }+      vy <- y psy+      k3 <- f ps3+      let !v = vy + spcDT sc * k3+      return v+    _ -> +      error "Incorrect phase: integ"++-- smoothI :: Dynamics Double -> Dynamics Double -> Dynamics Double +--           -> Dynamics Double+-- smoothI x t i = y where+--   y = integ ((x - y) / t) i++-- smooth :: Dynamics Double -> Dynamics Double -> Dynamics Double+-- smooth x t = smoothI x t x++-- smooth3I :: Dynamics Double -> Dynamics Double -> Dynamics Double +--            -> Dynamics Double+-- smooth3I x t i = y where+--   y  = integ ((s1 - y) / t') i+--   s1 = integ ((s0 - s1) / t') i+--   s0 = integ ((x - s0) / t') i+--   t' = t / 3.0++-- smooth3 :: Dynamics Double -> Dynamics Double -> Dynamics Double+-- smooth3 x t = smooth3I x t x++-- smoothNI :: Dynamics Double -> Dynamics Double -> Int -> Dynamics Double +--            -> Dynamics Double+-- smoothNI x t n i = s ! n where+--   s   = array (1, n) [(k, f k) | k <- [1 .. n]]+--   f 0 = integ ((x - s ! 0) / t') i+--   f k = integ ((s ! (k - 1) - s ! k) / t') i+--   t'  = t / fromIntegral n++-- smoothN :: Dynamics Double -> Dynamics Double -> Int -> Dynamics Double+-- smoothN x t n = smoothNI x t n x++-- delay1I :: Dynamics Double -> Dynamics Double -> Dynamics Double +--           -> Dynamics Double+-- delay1I x t i = y where+--   y = integ (x - y) (i * t) / t++-- delay1 :: Dynamics Double -> Dynamics Double -> Dynamics Double+-- delay1 x t = delay1I x t x++-- delay3I :: Dynamics Double -> Dynamics Double -> Dynamics Double +--           -> Dynamics Double+-- delay3I x t i = y where+--   y  = integ (s1 - y) (i * t') / t'+--   s1 = integ (s0 - s1) (i * t') / t'+--   s0 = integ (x - s0) (i * t') / t'+--   t' = t / 3.0++-- delay3 :: Dynamics Double -> Dynamics Double -> Dynamics Double+-- delay3 x t = delay3I x t x++-- delayNI :: Dynamics Double -> Dynamics Double -> Int -> Dynamics Double +--           -> Dynamics Double+-- delayNI x t n i = s ! n where+--   s   = array (1, n) [(k, f k) | k <- [1 .. n]]+--   f 0 = integ (x - s ! 0) (i * t') / t'+--   f k = integ (s ! (k - 1) - s ! k) (i * t') / t'+--   t'  = t / fromIntegral n++-- delayN :: Dynamics Double -> Dynamics Double -> Int -> Dynamics Double+-- delayN x t n = delayNI x t n x++-- forecast :: Dynamics Double -> Dynamics Double -> Dynamics Double +--            -> Dynamics Double+-- forecast x at hz =+--   x * (1.0 + (x / smooth x at - 1.0) / at * hz)++-- trend :: Dynamics Double -> Dynamics Double -> Dynamics Double +--         -> Dynamics Double+-- trend x at i =+--   (x / smoothI x at (x / (1.0 + i * at)) - 1.0) / at++--+-- Table Functions+--++-- | Lookup @x@ in a table of pairs @(x, y)@ using linear interpolation.+lookupD :: Dynamics Double -> Array Int (Double, Double) -> Dynamics Double+lookupD (Dynamics m) tbl =+  Dynamics (\ps -> do a <- m ps; return $ find first last a) where+    (first, last) = bounds tbl+    find left right x =+      if left > right then+        error "Incorrect index: table"+      else+        let index = (left + 1 + right) `div` 2+            x1    = fst $ tbl ! index+        in if x1 <= x then +             let y | index < right = find index right x+                   | right == last  = snd $ tbl ! right+                   | otherwise     = +                     let x2 = fst $ tbl ! (index + 1)+                         y1 = snd $ tbl ! index+                         y2 = snd $ tbl ! (index + 1)+                     in y1 + (y2 - y1) * (x - x1) / (x2 - x1) +             in y+           else+             let y | left < index = find left (index - 1) x+                   | left == first = snd $ tbl ! left+                   | otherwise    = error "Incorrect index: table"+             in y++-- | Lookup @x@ in a table of pairs @(x, y)@ using stepwise function.+lookupStepwiseD :: Dynamics Double -> Array Int (Double, Double)+                  -> Dynamics Double+lookupStepwiseD (Dynamics m) tbl =+  Dynamics (\ps -> do a <- m ps; return $ find first last a) where+    (first, last) = bounds tbl+    find left right x =+      if left > right then+        error "Incorrect index: table"+      else+        let index = (left + 1 + right) `div` 2+            x1    = fst $ tbl ! index+        in if x1 <= x then +             let y | index < right = find index right x+                   | right == last  = snd $ tbl ! right+                   | otherwise     = snd $ tbl ! right+             in y+           else+             let y | left < index = find left (index - 1) x+                   | left == first = snd $ tbl ! left+                   | otherwise    = error "Incorrect index: table"+             in y++-- --+-- -- Discrete Functions+-- --+    +-- delayTrans :: Dynamics a -> Dynamics Double -> Dynamics a +--               -> (Dynamics a -> Dynamics a) -> Dynamics a+-- delayTrans (Dynamics x) (Dynamics d) (Dynamics i) tr = tr $ Dynamics r +--   where+--     r ps = do +--       let t  = parTime ps+--           sc = parSpecs ps+--           n  = parIteration ps+--       a <- d ps+--       let t' = (t - a) - spcStartTime sc+--           n' = fromInteger $ toInteger $ floor $ t' / spcDT sc+--           y | n' < 0    = i $ ps { parTime = spcStartTime sc,+--                                    parIteration = 0, +--                                    parPhase = 0 }+--             | n' < n    = x $ ps { parTime = t',+--                                    parIteration = n',+--                                    parPhase = -1 }+--             | n' > n    = error "Cannot return the future data: delay"+--             | otherwise = error "Cannot return the current data: delay"+--       y    ++-- delay :: (Memo a) => Dynamics a -> Dynamics Double -> Dynamics a+-- delay x d = delayTrans x d x $ memo0 discrete++-- delay' :: (UMemo a) => Dynamics a -> Dynamics Double -> Dynamics a+-- delay' x d = delayTrans x d x $ memo0' discrete++-- delayI :: (Memo a) => Dynamics a -> Dynamics Double -> Dynamics a -> Dynamics a+-- delayI x d i = delayTrans x d i $ memo0 discrete         ++-- delayI' :: (UMemo a) => Dynamics a -> Dynamics Double -> Dynamics a -> Dynamics a+-- delayI' x d i = delayTrans x d i $ memo0' discrete         ++--+-- Interpolation and Initial Value+--+-- These functions complement the memoization, possibly except for +-- the initial function which can be also useful to get an initial +-- value of any dynamic process. See comments to the Memoization +-- section.+--      ++-- | Return the initial value.+initD :: Dynamics a -> Dynamics a+initD (Dynamics m) =+  Dynamics $ \ps ->+  if parIteration ps == 0 && parPhase ps == 0 then+    m ps+  else+    let sc = parSpecs ps+    in m $ ps { parTime = basicTime sc 0 0,+                parIteration = 0,+                parPhase = 0 } ++-- | Discretize the computation in the integration time points.+discrete :: Dynamics a -> Dynamics a+discrete (Dynamics m) =+  Dynamics $ \ps ->+  let ph = parPhase ps+      r | ph == 0    = m ps+        | ph > 0    = let sc = parSpecs ps+                          n  = parIteration ps+                      in m $ ps { parTime = basicTime sc n 0,+                                  parPhase = 0 }+        | otherwise = let sc = parSpecs ps+                          t  = parTime ps+                          n  = parIteration ps+                          t' = spcStartTime sc + fromIntegral (n + 1) * spcDT sc+                          n' = if neighborhood sc t t' then n + 1 else n+                      in m $ ps { parTime = basicTime sc n' 0,+                                  parIteration = n',+                                  parPhase = 0 }+  in r++-- | Interpolate the computation based on the integration time points only.+interpolate :: Dynamics Double -> Dynamics Double+interpolate (Dynamics m) = +  Dynamics $ \ps -> +  if parPhase ps >= 0 then +    m ps+  else +    let sc = parSpecs ps+        t  = parTime ps+        x  = (t - spcStartTime sc) / spcDT sc+        n1 = max (floor x) (iterationLoBnd sc)+        n2 = min (ceiling x) (iterationHiBnd sc)+        t1 = basicTime sc n1 0+        t2 = basicTime sc n2 0+        z1 = m $ ps { parTime = t1, +                      parIteration = n1, +                      parPhase = 0 }+        z2 = m $ ps { parTime = t2,+                      parIteration = n2,+                      parPhase = 0 }+        r | t == t1   = z1+          | t == t2   = z2+          | otherwise = +            do y1 <- z1+               y2 <- z2+               return $ y1 + (y2 - y1) * (t - t1) / (t2 - t1)+    in r++-- --+-- -- Memoization+-- --+-- -- The memoization creates such processes, which values are +-- -- defined and then stored in the cache for the points of+-- -- integration. You should use some kind of interpolation +-- -- like the interpolate function to process all other time +-- -- points that don't coincide with the integration points:+-- --+-- --   x = memo interpolate y    -- a linear interpolation+-- --   x = memo discrete y       -- a discrete process+-- --++-- | The 'Memo' class specifies a type for which an array can be created.+class (MArray IOArray e IO) => Memo e where+  newMemoArray_ :: Ix i => (i, i) -> IO (IOArray i e)++-- | The 'UMemo' class specifies a type for which an unboxed array exists.+class (MArray IOUArray e IO) => UMemo e where+  newMemoUArray_ :: Ix i => (i, i) -> IO (IOUArray i e)++instance Memo e where+  newMemoArray_ = newArray_+    +instance (MArray IOUArray e IO) => UMemo e where+  newMemoUArray_ = newArray_++-- | Memoize and order the computation in the integration time points using +-- the specified interpolation and being aware of the Runge-Kutta method.+memo :: Memo e => (Dynamics e -> Dynamics e) -> Dynamics e +       -> Dynamics (Dynamics e)+memo tr (Dynamics m) = +  Dynamics $ \ps ->+  do let sc = parSpecs ps+         (phl, phu) = phaseBnds sc+         (nl, nu)   = iterationBnds sc+     arr   <- newMemoArray_ ((phl, nl), (phu, nu))+     nref  <- newIORef 0+     phref <- newIORef 0+     let r ps = +           do let sc  = parSpecs ps+                  n   = parIteration ps+                  ph  = parPhase ps+                  phu = phaseHiBnd sc +                  loop n' ph' = +                    if (n' > n) || ((n' == n) && (ph' > ph)) +                    then +                      readArray arr (ph, n)+                    else +                      let ps' = ps { parIteration = n', parPhase = ph',+                                     parTime = basicTime sc n' ph' }+                      in do a <- m ps'+                            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 $ tr $ Dynamics r++-- | Memoize and order the computation in the integration time points using +-- the specified interpolation and being aware of the Runge-Kutta method.+umemo :: UMemo e => (Dynamics e -> Dynamics e) -> Dynamics e +        -> Dynamics (Dynamics e)+umemo tr (Dynamics m) = +  Dynamics $ \ps ->+  do let sc = parSpecs ps+         (phl, phu) = phaseBnds sc+         (nl, nu)   = iterationBnds sc+     arr   <- newMemoUArray_ ((phl, nl), (phu, nu))+     nref  <- newIORef 0+     phref <- newIORef 0+     let r ps =+           do let sc  = parSpecs ps+                  n   = parIteration ps+                  ph  = parPhase ps+                  phu = phaseHiBnd sc +                  loop n' ph' = +                    if (n' > n) || ((n' == n) && (ph' > ph)) +                    then +                      readArray arr (ph, n)+                    else +                      let ps' = ps { parIteration = n', +                                     parPhase = ph',+                                     parTime = basicTime sc n' ph' }+                      in do a <- m ps'+                            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 $ tr $ Dynamics r++-- | Memoize and order the computation in the integration time points using +-- the specified interpolation and without knowledge of the Runge-Kutta method.+memo0 :: Memo e => (Dynamics e -> Dynamics e) -> Dynamics e +        -> Dynamics (Dynamics e)+memo0 tr (Dynamics m) = +  Dynamics $ \ps ->+  do let sc   = parSpecs ps+         bnds = iterationBnds sc+     arr   <- newMemoArray_ bnds+     nref  <- newIORef 0+     let r ps =+           do let sc = parSpecs ps+                  n  = parIteration ps+                  loop n' = +                    if n' > n+                    then +                      readArray arr n+                    else +                      let ps' = ps { parIteration = n', parPhase = 0,+                                     parTime = basicTime sc n' 0 }+                      in do a <- m ps'+                            a `seq` writeArray arr n' a+                            writeIORef nref (n' + 1)+                            loop (n' + 1)+              n' <- readIORef nref+              loop n'+     return $ tr $ Dynamics r++-- | Memoize and order the computation in the integration time points using +-- the specified interpolation and without knowledge of the Runge-Kutta method.+umemo0 :: UMemo e => (Dynamics e -> Dynamics e) -> Dynamics e +         -> Dynamics (Dynamics e)+umemo0 tr (Dynamics m) = +  Dynamics $ \ps ->+  do let sc   = parSpecs ps+         bnds = iterationBnds sc+     arr   <- newMemoUArray_ bnds+     nref  <- newIORef 0+     let r ps =+           do let sc = parSpecs ps+                  n  = parIteration ps+                  loop n' = +                    if n' > n+                    then +                      readArray arr n+                    else +                      let ps' = ps { parIteration = n', parPhase = 0,+                                     parTime = basicTime sc n' 0 }+                      in do a <- m ps'+                            a `seq` writeArray arr n' a+                            writeIORef nref (n' + 1)+                            loop (n' + 1)+              n' <- readIORef nref+              loop n'+     return $ tr $ Dynamics r++--+-- Once+--++-- | Call the computation only once.+once :: Dynamics a -> Dynamics (Dynamics a)+once (Dynamics m) =+  Dynamics $ \ps ->+  do x <- newIORef Nothing+     let r ps =+           do a <- readIORef x+              case a of+                Just b -> +                  return b+                Nothing ->+                  do b <- m ps+                     writeIORef x $ Just b+                     return $! b+     return $ Dynamics r++--+-- The DynamicsCont Monad+--+-- It looks somewhere like the ContT monad transformer parameterized by +-- the Dynamics monad, although this analogy is not strong. The main +-- idea is to represent the continuation as a dynamic process varying +-- in time.+--++newtype DynamicsCont a = DynamicsCont (Dynamics (a -> IO ()) -> Dynamics ())++instance Monad DynamicsCont where+  return  = returnDC+  m >>= k = bindDC m k++returnDC :: a -> DynamicsCont a+returnDC a = +  DynamicsCont $ \(Dynamics c) -> +  Dynamics $ \ps -> +  do cont' <- c ps+     cont' a+                          +bindDC :: DynamicsCont a -> (a -> DynamicsCont b) -> DynamicsCont b+bindDC (DynamicsCont m) k =+  DynamicsCont $ \c ->+  m $ Dynamics $ \ps -> +  let cont' a = let (DynamicsCont m') = k a+                    (Dynamics u) = m' c+                in u ps+  in return cont'++runCont :: DynamicsCont a -> IO (a -> IO ()) -> Dynamics ()+runCont (DynamicsCont m) f = m $ Dynamics $ const f++instance DynamicsTrans DynamicsCont where+  liftD (Dynamics m) =+    DynamicsCont $ \(Dynamics c) ->+    Dynamics $ \ps ->+    do cont' <- c ps+       a <- m ps+       cont' a++instance Functor DynamicsCont where+  fmap = liftM++instance MonadIO DynamicsCont where+  liftIO m =+    DynamicsCont $ \(Dynamics c) ->+    Dynamics $ \ps ->+    do cont' <- c ps+       a <- m+       cont' a++--+-- The Event Queue (The Dynamics Queue)+--+-- The most exciting thing is that any event is just some value in +-- the Dynamics monad, i.e. a computation, or saying differently, +-- a dynamic process that has a single purpose to perform some +-- side effect. To pass the message, we actually use a closure.+--++-- | The 'DynamicsQueue' type represents the event queue.+data DynamicsQueue = DynamicsQueue { +  queuePQ   :: PQ.PriorityQueue (Dynamics (() -> IO ())),+  queueBusy :: IORef Bool,+  queueTime :: IORef Double, +  queueRun  :: Dynamics () }++-- | Create a new event queue.+newQueue :: Dynamics DynamicsQueue+newQueue = +  Dynamics $ \ps ->+  do let sc = parSpecs ps+     f <- newIORef False+     t <- newIORef $ spcStartTime sc+     let cont () = return ()+     pq <- PQ.newQueue $ return cont+     let q = DynamicsQueue { queuePQ   = pq,+                             queueBusy = f,+                             queueTime = t, +                             queueRun  = subrunQueue q }+     return q+             +-- | Enqueue the event which must be actuated at the specified time.+enqueueDC :: DynamicsQueue -> Dynamics Double -> Dynamics (() -> IO ()) +            -> Dynamics ()+enqueueDC q (Dynamics t) c = Dynamics r where+  r ps =+    do t' <- t ps+       let pq = queuePQ q+       PQ.enqueue pq t' c+    +-- | Enqueue the event which must be actuated at the specified time.+enqueueD :: DynamicsQueue -> Dynamics Double -> Dynamics () -> Dynamics ()+enqueueD q t (Dynamics m) = enqueueDC q t (Dynamics c) where+  c ps = let f () = m ps in return f+    +subrunQueue :: DynamicsQueue -> Dynamics ()+subrunQueue q = Dynamics r where+  r ps =+    do let f = queueBusy q+       f' <- readIORef f+       unless f' $+         do writeIORef f True+            call q ps+            writeIORef f False+  call q ps =+    do let pq = queuePQ q+       f <- PQ.queueNull pq+       unless f $+         do (t2, Dynamics c2) <- PQ.queueFront pq+            let t = queueTime q+            t' <- readIORef t+            when (t2 < t') $ +              error "The time value is too small: subrunQueue"+            when (t2 <= parTime ps) $+              do writeIORef t t2+                 PQ.dequeue pq+                 let sc  = parSpecs ps+                     t0  = spcStartTime sc+                     dt  = spcDT sc+                     n2  = fromInteger $ toInteger $ floor ((t2 - t0) / dt)+                 k <- c2 $ ps { parTime = t2,+                               parIteration = n2,+                               parPhase = -1 }+                 k ()    -- raise the event+                 call q ps++-- | Run the event queue processing its events.+runQueue :: DynamicsQueue -> Dynamics ()+runQueue = queueRun++--+-- DynamicsPID and DynamicsProc+--+-- A value in the DynamicsProc monad represents a control process that can be +-- suspended and resumed at any time. It behaves like a dynamic process too. +-- Any value in the Dynamics monad can be lifted to the DynamicsProc monad. +-- Moreover, a value in the DynamicsProc monad can be run in the Dynamics monad.+--+-- A value of the DynamicsPID type is just an identifier of such a process.+--++-- Public functions:+--+--   pidQueue+--   holdProcD+--   holdProc+--   passivateProc+--   procPassive+--   reactivateProc+--   runProc+--   procPID+--   newPID++-- | Represents a process handler, its PID.+data DynamicsPID = +  DynamicsPID { pidQueue   :: DynamicsQueue,  -- ^ Return the bound event queue.+                pidStarted :: IORef Bool,+                pidCont    :: IORef (Maybe (Dynamics (() -> IO ()))) }++-- | Specifies a discontinuous process that can be suspended at any time+-- and then resumed later.+newtype DynamicsProc a = DynamicsProc (DynamicsPID -> DynamicsCont a)++-- | Hold the process for the specified time period.+holdProcD :: Dynamics Double -> DynamicsProc ()+holdProcD t =+  DynamicsProc $ \pid ->+  DynamicsCont $ \c ->+  enqueueDC (pidQueue pid) (t + time) c++-- | Hold the process for the specified time period.+holdProc :: Double -> DynamicsProc ()+holdProc t = holdProcD $ return t++-- | Passivate the process.+passivateProc :: DynamicsProc ()+passivateProc =+  DynamicsProc $ \pid ->+  DynamicsCont $ \c ->+  Dynamics $ \ps ->+  do let x = pidCont pid+     a <- readIORef x+     case a of+       Nothing -> writeIORef x $ Just c+       Just _  -> error "Cannot passivate the process twice: passivate"++-- | Test whether the process with the specified PID is passivated.+procPassive :: DynamicsPID -> DynamicsProc Bool+procPassive pid =+  DynamicsProc $ \_ ->+  DynamicsCont $ \(Dynamics c) ->+  Dynamics $ \ps ->+  do cont' <- c ps +     let x = pidCont pid+     a <- readIORef x+     case a of+       Nothing -> cont' False+       Just _  -> cont' True++-- | Reactivate a process with the specified PID.+reactivateProc :: DynamicsPID -> DynamicsProc ()+reactivateProc pid =+  DynamicsProc $ \pid' ->+  DynamicsCont $ \c@(Dynamics cont) ->+  Dynamics $ \ps ->+  do let x = pidCont pid+     a <- readIORef x+     case a of+       Nothing ->+         do cont' <- cont ps+            cont' ()+       Just (Dynamics cont2) ->+         do writeIORef x Nothing+            let Dynamics m = enqueueDC (pidQueue pid') time c+            m ps+            cont2' <- cont2 ps+            cont2' ()++-- | Start the process with the specified PID at the desired time.+runProc :: DynamicsProc () -> DynamicsPID -> Dynamics Double -> Dynamics ()+runProc (DynamicsProc p) pid t =+  runCont m r+    where m = do y <- liftIO $ readIORef (pidStarted pid)+                 if y +                   then error $+                        "A process with such PID " +++                        "has been started already: runProc"+                   else liftIO $ writeIORef (pidStarted pid) True+                 DynamicsCont $ \c -> enqueueDC (pidQueue pid) t c+                 p pid+          r = let f () = return () in return f++-- | Return the current process PID.+procPID :: DynamicsProc DynamicsPID+procPID = DynamicsProc $ \pid -> return pid++-- | Create a new process PID.+newPID :: DynamicsQueue -> Dynamics DynamicsPID+newPID q =+  do x <- liftIO $ newIORef Nothing+     y <- liftIO $ newIORef False+     return DynamicsPID { pidQueue   = q,+                          pidStarted = y,+                          pidCont    = x }++instance Eq DynamicsPID where+  x == y = pidCont x == pidCont y    -- for the references are unique++instance Monad DynamicsProc where+  return  = returnDP+  m >>= k = bindDP m k++returnDP :: a -> DynamicsProc a+returnDP a = DynamicsProc (\pid -> return a)++bindDP :: DynamicsProc a -> (a -> DynamicsProc b) -> DynamicsProc b+bindDP (DynamicsProc m) k = +  DynamicsProc $ \pid -> +  do a <- m pid+     let DynamicsProc m' = k a+     m' pid++instance Functor DynamicsProc where+  fmap = liftM++instance DynamicsTrans DynamicsProc where+  liftD m = DynamicsProc $ \pid -> liftD m+  +instance MonadIO DynamicsProc where+  liftIO m = DynamicsProc $ \pid -> liftIO m++--+-- DynamicsResource+--+  +-- Public functions:  +--+--   resourceQueue+--   resourceInitCount+--   resourceCount+--   requestResource+--   releaseResource+--   newResource+  +-- | Represents a limited resource.+data DynamicsResource = +  DynamicsResource { resourceQueue     :: DynamicsQueue,  +                     -- ^ Return the bound event queue.+                     resourceInitCount :: Int,+                     -- ^ Return the initial count of the resource.+                     resourceCountRef  :: IORef Int, +                     resourceWaitQueue :: Q.Queue (Dynamics (() -> IO ()))}++instance Eq DynamicsResource where+  x == y = resourceCountRef x == resourceCountRef y  -- unique references++-- | Create a new resource with the specified initial count.+newResource :: DynamicsQueue -> Int -> Dynamics DynamicsResource+newResource q initCount =+  Dynamics $ \ps ->+  do countRef  <- newIORef initCount+     waitQueue <- Q.newQueue+     return DynamicsResource { resourceQueue     = q,+                               resourceInitCount = initCount,+                               resourceCountRef  = countRef,+                               resourceWaitQueue = waitQueue }++-- | Return the current count of the resource.+resourceCount :: DynamicsResource -> DynamicsProc Int+resourceCount r =+  DynamicsProc $ \_ ->+  DynamicsCont $ \(Dynamics c) ->+  Dynamics $ \ps ->+  do cont' <- c ps +     a <- readIORef (resourceCountRef r)+     cont' a++-- | Request for the resource decreasing its count in case of success,+-- otherwise suspending the discontinuous process until some other +-- process releases the resource.+requestResource :: DynamicsResource -> DynamicsProc ()+requestResource r =+  DynamicsProc $ \_ ->+  DynamicsCont $ \c@(Dynamics cont) ->+  Dynamics $ \ps ->+  do a <- readIORef (resourceCountRef r)+     if a == 0 +       then Q.enqueue (resourceWaitQueue r) c+       else do let a' = a - 1+               a' `seq` writeIORef (resourceCountRef r) a'+               cont' <- cont ps+               cont' ()++-- | Release the resource increasing its count and resuming one of the+-- previously suspended processes as possible.+releaseResource :: DynamicsResource -> DynamicsProc ()+releaseResource r =+  DynamicsProc $ \_ ->+  DynamicsCont $ \(Dynamics c) ->+  Dynamics $ \ps ->+  do a <- readIORef (resourceCountRef r)+     let a' = a + 1+     when (a' > resourceInitCount r) $+       error $+       "The resource count cannot be greater than " +++       "its initial value: releaseResource."+     f <- Q.queueNull (resourceWaitQueue r)+     if f +       then a' `seq` writeIORef (resourceCountRef r) a'+       else do c2 <- Q.queueFront (resourceWaitQueue r)+               Q.dequeue (resourceWaitQueue r)+               let Dynamics m = enqueueDC (resourceQueue r) time c2+               m ps+     cont' <- c ps+     cont' ()++--+-- DynamicsRef+--++-- | The 'DynamicsRef' type represents a mutable variable similar to +-- the 'IORef' variable but only bound to some event queue, which makes +-- the variable coordinated with that queue.+data DynamicsRef a = +  DynamicsRef { refQueue  :: DynamicsQueue,    -- ^ Return the bound event queue.+                refRunner :: Dynamics (),+                refValue  :: IORef a }++-- | Create a new reference bound to the specified event queue.+newRef :: DynamicsQueue -> a -> Dynamics (DynamicsRef a)+newRef q a =+  do x <- liftIO $ newIORef a+     return DynamicsRef { refQueue  = q,+                          refRunner = runQueue q,+                          refValue  = x }+     +-- | Read the value of a reference, forcing the bound event queue to raise +-- the events in case of need.+readRef :: DynamicsRef a -> Dynamics a+readRef r = Dynamics $ \ps -> +  do let Dynamics m = refRunner r+     m ps+     readIORef (refValue r)++-- | Write a new value into the reference.+writeRef :: DynamicsRef a -> a -> Dynamics ()+writeRef r a = Dynamics $ \ps -> +  do writeIORef (refValue r) a+     let Dynamics m = refRunner r +     m ps++-- | Mutate the contents of the reference, forcing the bound event queue to+-- raise all pending events in case of need.+modifyRef :: DynamicsRef a -> (a -> a) -> Dynamics ()+modifyRef r f = Dynamics $ \ps -> +  do let Dynamics m = refRunner r +     m ps+     modifyIORef (refValue r) f++-- | A strict version of the 'writeRef' function.+writeRef' :: DynamicsRef a -> a -> Dynamics ()+writeRef' r a = a `seq` writeRef r a++-- | A strict version of the 'modifyRef' function.+modifyRef' :: DynamicsRef a -> (a -> a) -> Dynamics ()+modifyRef' r f = Dynamics $ \ps ->+  do let Dynamics m = refRunner r+     m ps+     a <- readIORef (refValue r)+     let b = f a+     b `seq` writeIORef (refValue r) b++--+-- Agent-based Modeling+--++-- Public functions:+--+--   agentQueue+--   agentState+--   activateState+--   initState+--   stateAgent     +--   stateParent+--   addTimeoutD+--   addTimeout+--   addTimerD+--   addTimer+--   stateActivation+--   stateDeactivation+--   newState     +--   newSubstate+--   newAgent++-- | Represents an agent.+data Agent = Agent { agentQueue :: DynamicsQueue,+                     -- ^ Return the bound event queue.+                     agentModeRef :: IORef AgentMode,+                     agentStateRef :: IORef (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 (Dynamics ()),+                               stateDeactivateRef :: IORef (Dynamics ()), +                               stateVersionRef :: IORef Int }+                  +data AgentMode = CreationMode+               | InitialMode+               | 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++findPath :: AgentState -> AgentState -> ([AgentState], [AgentState])+findPath source target = +  if stateAgent source == stateAgent target +  then+    partitionPath path1 path2+  else+    error "Different agents: findPath."+      where+        path1 = fullPath source []+        path2 = fullPath target []+        fullPath st acc =+          case stateParent st of+            Nothing  -> st : acc+            Just st' -> fullPath st' (st : acc)+        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)+            +traversePath :: AgentState -> AgentState -> Dynamics ()+traversePath source target =+  let (path1, path2) = findPath source target+      agent = stateAgent source+      activate st ps   =+        do Dynamics m <- readIORef (stateActivateRef st)+           m ps+      deactivate st ps =+        do Dynamics m <- readIORef (stateDeactivateRef st)+           m ps+  in Dynamics $ \ps ->+       do writeIORef (agentModeRef agent) TransientMode+          forM_ path1 $ \st ->+            do writeIORef (agentStateRef agent) (Just st)+               deactivate st ps+               -- it makes all timeout and timer handlers obsolete+               modifyIORef (stateVersionRef st) (1 +)+          forM_ path2 $ \st ->+            do when (st == target) $+                 writeIORef (agentModeRef agent) InitialMode+               writeIORef (agentStateRef agent) (Just st)+               activate st ps+               when (st == target) $+                 writeIORef (agentModeRef agent) ProcessingMode++-- | Add to the state a timeout handler that will be actuated +-- in the specified time period, while the state remains active.+addTimeoutD :: AgentState -> Dynamics Double -> Dynamics () -> Dynamics ()+addTimeoutD st t (Dynamics action) =+  Dynamics $ \ps ->+  do v <- readIORef (stateVersionRef st)+     let m1 = Dynamics $ \ps ->+           do v' <- readIORef (stateVersionRef st)+              when (v == v') $ action ps+         q = agentQueue (stateAgent st)+         Dynamics m2 = enqueueD q (t + time) m1+     m2 ps++-- | 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.+addTimerD :: AgentState -> Dynamics Double -> Dynamics () -> Dynamics ()+addTimerD st t (Dynamics action) =+  Dynamics $ \ps ->+  do v <- readIORef (stateVersionRef st)+     let m1 = Dynamics $ \ps ->+           do v' <- readIORef (stateVersionRef st)+              when (v == v') $ do { m2 ps; action ps }+         q = agentQueue (stateAgent st)+         Dynamics m2 = enqueueD q (t + time) m1+     m2 ps++-- | Add to the state a timeout handler that will be actuated +-- in the specified time period, while the state remains active.+addTimeout :: AgentState -> Double -> Dynamics () -> Dynamics ()+addTimeout st t = addTimeoutD st (return t)++-- | 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 -> Double -> Dynamics () -> Dynamics ()+addTimer st t = addTimerD st (return t)++-- | Create a new state.+newState :: Agent -> Dynamics AgentState+newState agent =+  Dynamics $ \ps ->+  do aref <- newIORef $ return ()+     dref <- newIORef $ return ()+     vref <- newIORef 0+     return AgentState { stateAgent = agent,+                         stateParent = Nothing,+                         stateActivateRef = aref,+                         stateDeactivateRef = dref,+                         stateVersionRef = vref }++-- | Create a child state.+newSubstate :: AgentState -> Dynamics AgentState+newSubstate parent =+  Dynamics $ \ps ->+  do let agent = stateAgent parent +     aref <- newIORef $ return ()+     dref <- newIORef $ return ()+     vref <- newIORef 0+     return AgentState { stateAgent = agent,+                         stateParent = Just parent,+                         stateActivateRef= aref,+                         stateDeactivateRef = dref,+                         stateVersionRef = vref }++-- | Create an agent bound with the specified event queue.+newAgent :: DynamicsQueue -> Dynamics Agent+newAgent queue =+  Dynamics $ \ps ->+  do modeRef    <- newIORef CreationMode+     stateRef   <- newIORef Nothing+     return Agent { agentQueue = queue,+                    agentModeRef = modeRef,+                    agentStateRef = stateRef }++-- | Return the selected downmost active state.+agentState :: Agent -> Dynamics (Maybe AgentState)+agentState agent =+  Dynamics $ \ps -> +  do let Dynamics m = queueRun $ agentQueue agent +     m ps    -- ensure that the agent state is actual+     readIORef (agentStateRef agent)+                   +-- | Select the next downmost active state.       +activateState :: AgentState -> Dynamics ()+activateState st =+  Dynamics $ \ps ->+  do let agent = stateAgent st+         Dynamics m = queueRun $ agentQueue agent +     m ps    -- ensure that the agent state is actual+     mode <- readIORef (agentModeRef agent)+     case mode of+       CreationMode ->+         case stateParent st of+           Just _ ->+             error $ +             "To run the agent for the first time, an initial state " +++             "must be top-level: activateState."+           Nothing ->+             do writeIORef (agentModeRef agent) InitialMode+                writeIORef (agentStateRef agent) (Just st)+                Dynamics m <- readIORef (stateActivateRef st)+                m ps+                writeIORef (agentModeRef agent) ProcessingMode+       InitialMode ->+         error $ +         "Use the initState function during " +++         "the state activation: activateState."+       TransientMode ->+         error $+         "Use the initState function during " +++         "the state activation: activateState."+       ProcessingMode ->+         do Just st0 <- readIORef (agentStateRef agent)+            let Dynamics m = traversePath st0 st+            m ps+              +-- | Activate the child state during the direct activation of +-- the parent state. This call is ignored in other cases.+initState :: AgentState -> Dynamics ()+initState st =+  Dynamics $ \ps ->+  do let agent = stateAgent st+         Dynamics m = queueRun $ agentQueue agent +     m ps    -- ensure that the agent state is actual+     mode <- readIORef (agentModeRef agent)+     case mode of+       CreationMode ->+         error $+         "To run the agent for the fist time, use " +++         "the activateState function: initState."+       InitialMode ->+         do Just st0 <- readIORef (agentStateRef agent)+            let Dynamics m = traversePath st0 st+            m ps+       TransientMode -> +         return ()+       ProcessingMode ->+         error $+         "Use the activateState function everywhere outside " +++         "the state activation: initState."++-- | Set the activation computation for the specified state.+stateActivation :: AgentState -> Dynamics () -> Dynamics ()+stateActivation st action =+  Dynamics $ \ps ->+  writeIORef (stateActivateRef st) action+  +-- | Set the deactivation computation for the specified state.+stateDeactivation :: AgentState -> Dynamics () -> Dynamics ()+stateDeactivation st action =+  Dynamics $ \ps ->+  writeIORef (stateDeactivateRef st) action+  +          
+ Simulation/Aivika/PriorityQueue.hs view
@@ -0,0 +1,173 @@++-- Copyright (c) 2009, 2010, 2011 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.++-- This is an imperative version of the heap-based priority queue in monad IO.++module Simulation.Aivika.PriorityQueue +       (PriorityQueue, +        queueNull, +        newQueue, +        enqueue, +        dequeue, +        queueFront) where ++import Data.Array+import Data.Array.MArray+import Data.Array.IO+import Data.IORef+import Control.Monad++data PriorityQueue a = +  PriorityQueue { pqKeys  :: IORef (IOUArray Int Double),+                  pqVals  :: IORef (IOArray Int a),+                  pqSize  :: IORef Int,+                  pqNoVal :: a    -- to release references                 +                }++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++queueNull :: PriorityQueue a -> IO Bool+queueNull pq =+  do size <- readIORef (pqSize pq)+     return $ size == 0++newQueue :: a -> IO (PriorityQueue a)+newQueue defaultValue =+  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,+                            pqNoVal = defaultValue }++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 + 1) $ increase pq (i + 1)+     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 :: 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+     writeArray keys i 0.0+     writeArray vals i (pqNoVal pq)    -- to release the reference!+     siftDown keys vals i 0 k v++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/Queue.hs view
@@ -0,0 +1,112 @@++-- Copyright (c) 2009, 2010, 2011 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.++-- This is an imperative version of the queue in monad IO.++module Simulation.Aivika.Queue +       (Queue, +        queueNull, +        newQueue, +        enqueue, +        dequeue, +        queueFront) where ++import Data.IORef+import Control.Monad++data QueueItem a = +  QueueItem { qiVal  :: a,+              qiPrev :: IORef (Maybe (QueueItem a)),+              qiNext :: IORef (Maybe (QueueItem a)) }+  +data Queue a =  +  Queue { qHead :: IORef (Maybe (QueueItem a)),+          qTail :: IORef (Maybe (QueueItem a)) }++queueNull :: Queue a -> IO Bool+queueNull q =+  do head <- readIORef (qHead q) +     case head of+       Nothing -> return True+       Just _  -> return False+    +newQueue :: IO (Queue a)+newQueue =+  do head <- newIORef Nothing +     tail <- newIORef Nothing+     return Queue { qHead = head, qTail = tail }++enqueue :: Queue a -> a -> IO ()+enqueue q v =+  do head <- readIORef (qHead q)+     case head of+       Nothing ->+         do prev <- newIORef Nothing+            next <- newIORef Nothing+            let item = Just QueueItem { qiVal = v, +                                        qiPrev = prev, +                                        qiNext = next }+            writeIORef (qHead q) item+            writeIORef (qTail q) item+       Just h ->+         do prev <- newIORef Nothing+            next <- newIORef head+            let item = Just QueueItem { qiVal = v,+                                        qiPrev = prev,+                                        qiNext = next }+            writeIORef (qiPrev h) item+            writeIORef (qHead q) item++dequeue :: Queue a -> IO ()+dequeue q =+  do tail <- readIORef (qTail q) +     case tail of+       Nothing ->+         error "Empty queue: dequeue"+       Just t ->+         do tail' <- readIORef (qiPrev t)+            case tail' of+              Nothing ->+                do writeIORef (qHead q) Nothing+                   writeIORef (qTail q) Nothing+              Just t' ->+                do writeIORef (qiNext t') Nothing+                   writeIORef (qTail q) tail'++queueFront :: Queue a -> IO a+queueFront q =+  do tail <- readIORef (qTail q)+     case tail of+       Nothing ->+         error "Empty queue: front"+       Just t ->+         return $ qiVal t
+ aivika.cabal view
@@ -0,0 +1,47 @@+name:            aivika+version:         0.1+synopsis:        A multi-paradigm simulation library+description:+    Aivika is a multi-paradigm simulation library. It allows us to integrate +    a system of ordinary differential equations. Also it can be applied to+    the Discrete Event Simulation. It supports the event-oriented, +    process-oriented and activity-oriented paradigms. Aivika also supports +    the Agent-based Modeling. Finally, it can be applied to System Dynamics.+    .+    The library widely uses monads. The dynamic system is represented as +    a computation in the Dynamics monad. There is also the DynamicsProc+    monad to represent the discontinuous processes which can be suspended+    at any time and then resumed later. Everything else is expressed through +    these two monads, including the events, agent handlers and even integrals.+    .+category:        Simulation+license:         BSD3+license-file:    LICENSE+copyright:       (c) 2009-2011. David Sorokin <david.sorokin@gmail.com>+author:          David Sorokin+maintainer:      David Sorokin <david.sorokin@gmail.com>+cabal-version:   >= 1.2.0+build-type:      Simple+tested-with:     GHC == 6.12.1++extra-source-files:  examples/BassDiffusion.hs+                     examples/ChemicalReaction.hs+                     examples/FishBank.hs+                     examples/MachRep1.hs+                     examples/MachRep1EventDriven.hs+                     examples/MachRep1TimeDriven.hs+                     examples/MachRep2.hs+                     examples/MachRep3.hs++data-files:          doc/aivika.pdf++library+    exposed-modules: Simulation.Aivika.Dynamics+    other-modules:   Simulation.Aivika.Queue+                     Simulation.Aivika.PriorityQueue+                     +    build-depends:   base >= 3 && < 5,+                     mtl >= 1.1.0.2,+                     array >= 0.3.0.0+                     +    ghc-options:     -O2
+ doc/aivika.pdf view

binary file changed (absent → 275619 bytes)

+ examples/BassDiffusion.hs view
@@ -0,0 +1,106 @@++import Random+import Data.Array+import Control.Monad+import Control.Monad.Trans++import Simulation.Aivika.Dynamics++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 }++exprnd :: Double -> IO Double+exprnd lambda =+  do x <- getStdRandom random+     return (- log x / lambda)+     +boolrnd :: Double -> IO Bool+boolrnd p =+  do x <- getStdRandom random+     return (x <= p)++data Person = Person { personAgent :: Agent,+                       personPotentialAdopter :: AgentState,+                       personAdopter :: AgentState }+              +createPerson :: DynamicsQueue -> Dynamics Person              +createPerson q =    +  do agent <- newAgent q+     potentialAdopter <- newState agent+     adopter <- newState agent+     return Person { personAgent = agent,+                     personPotentialAdopter = potentialAdopter,+                     personAdopter = adopter }+       +createPersons :: DynamicsQueue -> Dynamics (Array Int Person)+createPersons q =+  do list <- forM [1 .. n] $ \i ->+       do p <- createPerson q+          return (i, p)+     return $ array (1, n) list+     +definePerson :: Person -> Array Int Person +               -> DynamicsRef Int -> DynamicsRef Int+               -> Dynamics ()+definePerson p ps potentialAdopters adopters =+  do stateActivation (personPotentialAdopter p) $+       do modifyRef' potentialAdopters $ \a -> a + 1+          -- add a timeout+          t <- liftIO $ exprnd advertisingEffectiveness +          let st  = personPotentialAdopter p+              st' = personAdopter p+          addTimeout st t $ activateState st'+     stateActivation (personAdopter p) $ +       do modifyRef' adopters  $ \a -> a + 1+          -- add a timer that works while the state is active+          let t = liftIO $ exprnd contactRate    -- many times!+          addTimerD (personAdopter p) t $+            do i <- liftIO $ getStdRandom $ randomR (1, n)+               let p' = ps ! i+               st <- agentState (personAgent p')+               when (st == Just (personPotentialAdopter p')) $+                 do b <- liftIO $ boolrnd adoptionFraction+                    when b $ activateState (personAdopter p')+     stateDeactivation (personPotentialAdopter p) $+       modifyRef' potentialAdopters $ \a -> a - 1+     stateDeactivation (personAdopter p) $+       modifyRef' adopters $ \a -> a - 1+        +definePersons :: Array Int Person +                -> DynamicsRef Int +                -> DynamicsRef Int +                -> Dynamics ()+definePersons ps potentialAdopters adopters =+  forM_ (elems ps) $ \p -> +  definePerson p ps potentialAdopters adopters+                               +activatePerson :: Person -> Dynamics ()+activatePerson p = activateState (personPotentialAdopter p)++activatePersons :: Array Int Person -> Dynamics ()+activatePersons ps =+  forM_ (elems ps) $ \p -> activatePerson p++model :: Dynamics (Dynamics [Int])+model =+  do q <- newQueue+     potentialAdopters <- newRef q 0+     adopters <- newRef q 0+     ps <- createPersons q+     definePersons ps potentialAdopters adopters+     activatePersons ps+     return $ do i1 <- readRef potentialAdopters+                 i2 <- readRef adopters+                 return [i1, i2]++main =+  do xs <- runDynamics model specs+     print xs
+ examples/ChemicalReaction.hs view
@@ -0,0 +1,26 @@++import Simulation.Aivika.Dynamics++specs = Specs { spcStartTime = 0, +                spcStopTime = 13, +                spcDT = 0.01,+                spcMethod = RungeKutta4 }++model :: Dynamics (Dynamics [Double])+model =+  do integA <- newInteg 100+     integB <- newInteg 0+     integC <- newInteg 0+     let a = integValue integA+         b = integValue integB+         c = integValue integC+     let ka = 1+         kb = 1+     integDiff integA (- ka * a)+     integDiff integB (ka * a - kb * b)+     integDiff integC (kb * b)+     return $ sequence [a, b, c]++main = +  do a <- runDynamics1 model specs+     print a
+ examples/FishBank.hs view
@@ -0,0 +1,57 @@++import Data.Array++import Simulation.Aivika.Dynamics++specs = Specs { spcStartTime = 0, +                spcStopTime = 13, +                spcDT = 0.01,+                -- spcDT = 0.000005,+                spcMethod = RungeKutta4 }++model :: Dynamics (Dynamics Double)+model =+  do fishInteg <- newInteg 1000+     shipsInteg <- newInteg 10+     totalProfitInteg <- newInteg 0+     -- integral values --+     let fish = integValue fishInteg+         ships = integValue shipsInteg+         totalProfit = integValue totalProfitInteg+     -- auxiliary values --+     let annualProfit = profit+         area = 100+         carryingCapacity = 1000+         catchPerShip = +           lookupD 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 = +           lookupD (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+         fishDeathRate = maxD 0 (fish * deathFraction)+         fishHatchRate = maxD 0 (fish * hatchFraction)+         fishPrice = 20+         fractionInvested = 0.2+         hatchFraction = 6+         operatingCost = ships * 250+         profit = revenue - operatingCost+         revenue = totalCatchPerYear * fishPrice+         shipBuildingRate = maxD 0 (profit * fractionInvested / shipCost)+         shipCost = 300+         totalCatchPerYear = maxD 0 (ships * catchPerShip)+     -- derivatives --+     integDiff fishInteg (fishHatchRate - fishDeathRate - totalCatchPerYear)+     integDiff shipsInteg shipBuildingRate+     integDiff totalProfitInteg annualProfit+     -- results --+     return annualProfit++main = do a <- runDynamics1 model specs+          print a
+ examples/MachRep1.hs view
@@ -0,0 +1,69 @@++-- 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 Random+import Control.Monad.Trans++import Simulation.Aivika.Dynamics++upRate = 1.0 / 1.0       -- reciprocal of mean up time+repairRate = 1.0 / 0.5   -- reciprocal of mean repair time++specs = Specs { spcStartTime = 0.0,+                spcStopTime = 1000.0,+                spcDT = 1.0,+                spcMethod = RungeKutta4 }+        +exprnd :: Double -> IO Double+exprnd lambda =+  do x <- getStdRandom random+     return (- log x / lambda)+     +model :: Dynamics (Dynamics Double)+model =+  do queue <- newQueue+     totalUpTime <- newRef queue 0.0+     +     pid1 <- newPID queue+     pid2 <- newPID queue+     +     let machine :: DynamicsProc ()+         machine =+           do startUpTime <- liftD time+              upTime <- liftIO $ exprnd upRate+              holdProc upTime+              finishUpTime <- liftD time+              liftD $ modifyRef' totalUpTime+                (+ (finishUpTime - startUpTime))+              repairTime <- liftIO $ exprnd repairRate+              holdProc repairTime+              machine+         +     runProc machine pid1 starttime+     runProc machine pid2 starttime+     +     let system :: Dynamics Double+         system =+           do x <- readRef totalUpTime+              y <- stoptime+              return $ x / (2 * y)+     +     return system+  +main =         +  do a <- runDynamics1 model specs+     print a
+ examples/MachRep1EventDriven.hs view
@@ -0,0 +1,75 @@++-- 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 Random+import Control.Monad.Trans++import Simulation.Aivika.Dynamics++upRate = 1.0 / 1.0       -- reciprocal of mean up time+repairRate = 1.0 / 0.5   -- reciprocal of mean repair time++specs = Specs { spcStartTime = 0.0,+                spcStopTime = 1000.0,+                spcDT = 1.0,+                spcMethod = RungeKutta4 }+        +exprnd :: Double -> IO Double+exprnd lambda =+  do x <- getStdRandom random+     return (- log x / lambda)+     +model :: Dynamics (Dynamics Double)+model =+  do queue <- newQueue+     totalUpTime <- newRef queue 0.0+     +     let machineBroken :: Double -> Dynamics ()+         machineBroken startUpTime =+           +           do finishUpTime <- time+              modifyRef' totalUpTime (+ (finishUpTime - startUpTime))+              repairTime <- liftIO $ exprnd repairRate+              +              -- enqueue a new event+              let t = return $ finishUpTime + repairTime+              enqueueD queue t machineRepaired+              +         machineRepaired :: Dynamics ()+         machineRepaired =+           +           do startUpTime <- time+              upTime <- liftIO $ exprnd upRate+              +              -- enqueue a new event+              let t = return $ startUpTime + upTime+              enqueueD queue t $ machineBroken startUpTime+     +     enqueueD queue starttime machineRepaired    -- start the first machine+     enqueueD queue starttime machineRepaired    -- start the second machine+     +     let system :: Dynamics Double+         system =+           do x <- readRef totalUpTime+              y <- stoptime+              return $ x / (2 * y)+              +     return system+  +main =         +  do a <- runDynamics1 model specs+     print a
+ examples/MachRep1TimeDriven.hs view
@@ -0,0 +1,116 @@++-- 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 Random+import Control.Monad.Trans++import Simulation.Aivika.Dynamics++upRate = 1.0 / 1.0       -- reciprocal of mean up time+repairRate = 1.0 / 0.5   -- reciprocal of mean repair time++specs = Specs { spcStartTime = 0.0,+                spcStopTime = 1000.0,+                spcDT = 0.05,+                spcMethod = RungeKutta4 }+        +exprnd :: Double -> IO Double+exprnd lambda =+  do x <- getStdRandom random+     return (- log x / lambda)+     +model :: Dynamics (Dynamics Double)+model =+  do queue <- newQueue+     totalUpTime <- newRef queue 0.0+     +     let machine :: Dynamics (Dynamics ())+         machine =+           do startUpTime <- newRef queue 0.0 +             +              -- a number of iterations when +              -- the machine works+              upNum <- newRef queue (-1)+              +              -- a number of iterations when +              -- the machine is broken+              repairNum <- newRef queue (-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' <- time+                            dt' <- dt+                            modifyRef' totalUpTime $ +                              \a -> a ++                              (finishUpTime' - startUpTime')+                            repairTime' <- +                              liftIO $ exprnd repairRate+                            writeRef' repairNum $+                              round (repairTime' / dt')+                              +                       repaired =+                         do writeRef' repairNum (-1)+                            -- the machine is repaired+                            t'  <- time+                            dt' <- dt+                            writeRef' startUpTime t'+                            upTime' <- +                              liftIO $ exprnd upRate+                            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 Dynamics ()+     m1 <- machine+     m2 <- machine+     +     -- create strictly sequential computations+     c1 <- memo0 discrete m1+     c2 <- memo0 discrete m2+       +     let system :: Dynamics Double+         system =+           do c1    -- involve in the simulation+              c2    -- involve in the simulation+              x <- readRef totalUpTime+              y <- stoptime+              return $ x / (2 * y)+     +     return system+  +main =         +  do a <- runDynamics1 model specs+     print a
+ examples/MachRep2.hs view
@@ -0,0 +1,96 @@++-- 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 Random+import Control.Monad+import Control.Monad.Trans++import Simulation.Aivika.Dynamics++upRate = 1.0 / 1.0       -- reciprocal of mean up time+repairRate = 1.0 / 0.5   -- reciprocal of mean repair time++specs = Specs { spcStartTime = 0.0,+                spcStopTime = 1000.0,+                spcDT = 1.0,+                spcMethod = RungeKutta4 }+        +exprnd :: Double -> IO Double+exprnd lambda =+  do x <- getStdRandom random+     return (- log x / lambda)+     +model :: Dynamics (Dynamics (Double, Double))+model =+  do queue <- newQueue+     +     -- number of times the machines have broken down+     nRep <- newRef queue 0 +     +     -- number of breakdowns in which the machine +     -- started repair service right away+     nImmedRep <- newRef queue 0+     +     -- total up time for all machines+     totalUpTime <- newRef queue 0.0+     +     repairPerson <- newResource queue 1+     +     pid1 <- newPID queue+     pid2 <- newPID queue+     +     let machine :: DynamicsProc ()+         machine =+           do startUpTime <- liftD time+              upTime <- liftIO $ exprnd upRate+              holdProc upTime+              finishUpTime <- liftD time+              liftD $ modifyRef' totalUpTime +                (+ (finishUpTime - startUpTime))+              +              -- check the resource availability+              liftD $ modifyRef' nRep (+ 1)+              n <- resourceCount repairPerson+              when (n == 1) $+                liftD $ modifyRef' nImmedRep (+ 1)+                +              requestResource repairPerson+              repairTime <- liftIO $ exprnd repairRate+              holdProc repairTime+              releaseResource repairPerson+              +              machine+         +     runProc machine pid1 starttime+     runProc machine pid2 starttime+     +     let system :: Dynamics (Double, Double)+         system =+           do x <- readRef totalUpTime+              y <- stoptime+              n <- readRef nRep+              nImmed <- readRef nImmedRep+              return (x / (2 * y), +                      fromIntegral nImmed / fromIntegral n)+     +     return system+  +main =         +  do a <- runDynamics1 model specs+     print a
+ examples/MachRep3.hs view
@@ -0,0 +1,87 @@++-- 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 Random+import Control.Monad+import Control.Monad.Trans++import Simulation.Aivika.Dynamics++upRate = 1.0 / 1.0       -- reciprocal of mean up time+repairRate = 1.0 / 0.5   -- reciprocal of mean repair time++specs = Specs { spcStartTime = 0.0,+                spcStopTime = 1000.0,+                spcDT = 1.0,+                spcMethod = RungeKutta4 }+        +exprnd :: Double -> IO Double+exprnd lambda =+  do x <- getStdRandom random+     return (- log x / lambda)+     +model :: Dynamics (Dynamics Double)+model =+  do queue <- newQueue+     +     -- number of machines currently up+     nUp <- newRef queue 2+     +     -- total up time for all machines+     totalUpTime <- newRef queue 0.0+     +     repairPerson <- newResource queue 1+     +     pid1 <- newPID queue+     pid2 <- newPID queue+     +     let machine :: DynamicsPID -> DynamicsProc ()+         machine pid =+           do startUpTime <- liftD time+              upTime <- liftIO $ exprnd upRate+              holdProc upTime+              finishUpTime <- liftD time+              liftD $ modifyRef' totalUpTime +                (+ (finishUpTime - startUpTime))+                +              liftD $ modifyRef' nUp $ \a -> a - 1+              nUp' <- liftD $ readRef nUp+              if nUp' == 1+                then passivateProc+                else do n <- resourceCount repairPerson+                        when (n == 1) $ reactivateProc pid+              +              requestResource repairPerson+              repairTime <- liftIO $ exprnd repairRate+              holdProc repairTime+              liftD $ modifyRef' nUp $ \a -> a + 1+              releaseResource repairPerson+              +              machine pid++     runProc (machine pid2) pid1 starttime+     runProc (machine pid1) pid2 starttime+     +     let system :: Dynamics Double+         system =+           do x <- readRef totalUpTime+              y <- stoptime+              return $ x / (2 * y)+     +     return system+  +main =         +  do a <- runDynamics1 model specs+     print a