content: 
  -- |
  -- Module     : Simulation.Aivika.Var
  -- Copyright  : Copyright (c) 2009-2017, David Sorokin <david.sorokin@gmail.com>
  -- License    : BSD3
  -- Maintainer : David Sorokin <david.sorokin@gmail.com>
  -- Stability  : experimental
  -- Tested with: GHC 8.0.1
  --
  -- 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.
  --
  -- Because of using the arrays, it would usually be a logical mistake to
  -- use this variable for collecting statistics. In most cases,
  -- the statistics can actually be collected with a very small footprint
  -- by updating immutable 'SamplingStats' and 'TimingStats' values in
  -- a mutable 'Ref' reference.
  --
  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.Ref
  import Simulation.Aivika.Signal
  import Simulation.Aivika.Statistics
  
  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. Only this variable is
  -- much slower than the reference.
  --
  -- For example, the memoised values of the variable can be used in
  -- the differential and difference equations of System Dynamics, while
  -- the variable iself can be updated within the discrete event simulation.
  --
  -- Because of using arrays under the hood, it would usually be a logical
  -- mistake to use the variable for collecting statistics. In most cases,
  -- the statistics can actually be collected with a very small footprint
  -- by updating immutable 'SamplingStats' and 'TimingStats' values in
  -- a mutable @Ref@ 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 to be used 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 the corresponding 
  -- 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     
  