Simulation arrows
Simple update of the simulation arrows from Programming with Arrows to work with the modern Arrow libraries.
{-# LANGUAGE Arrows #-}
module Sim where
-- discrete event simulation library.
-- This time, every channel always carries a value, and an *initial* value must
-- be supplied before simulation starts.
import Control.Category
import Control.Monad
import Control.Monad.Fix
import Control.Arrow
import Data.IORef
import Prelude hiding ((.), id)
type Time = Double
infinity :: Time
infinity = 1/0
data Event a = Event {time::Time, value::a}
instance Show a => Show (Event a) where
show t = show (value t)++"@"++show (time t)
-- The simulation arrow: given initial value of input signal, deliver initial
-- value of output signal and a running simulation.
-- invariant: no output event should precede the first input.
newtype Sim m a b = Sim (a -> m (b, State m a b))
sim :: Monad m => (a -> m (b, State m a b)) -> Sim m a b
sim f = Sim $ \a -> do
(b,s) <- f a
return (b,quiescent s)
quiescent :: Monad m => State m a b -> State m a b
quiescent (Lift m) = Lift (liftM quiescent m)
quiescent (Wait t s k) = wait t (quiescent s) k
quiescent (Ready _ _) = error "Trying to output before first input"
-- running simulations.
-- invariant: output events are in non-decreasing time order,
-- output events do not precede inputs or timeouts they depend on,
-- enforced by smart constructors
data State m a b = Ready (Event b) (State m a b)
| Lift (m (State m a b))
| Wait Time (State m a b) (Event a -> State m a b)
ready :: Monad m => Event a1 -> State m a a1 -> State m a a1
ready e r = Ready e (checkSequence ("Ready "++show (time e)) (time e) r)
lift :: Monad m => m (State m a b) -> State m a b
lift = Lift
wait :: Monad m =>
Time -> State m a b -> (Event a -> State m a b) -> State m a b
wait t f k = Wait t (checkSequence ("Wait "++show t) t f)
(\e -> checkSequence
("Wait "++show t++" \\"++show (time e)++" ->")
(time e) (k e))
-- ensure all outputs occur no earlier than t
{-
-- checkSequence is a version of causal which maintains a trace of events
-- to report on an eventual failure. Useful for debugging new arrows.
-- If debugging is unnecessary, it can be replaced by causal.
checkSequence s t (Ready e f) | t <= time e = Ready e f
checkSequence s t (Lift m) =
Lift (liftM (checkSequence (s++"\nLift") t) m)
checkSequence s t (Wait t' f k) =
Wait t' (checkSequence (s++"\nWait "++show t') t f)
(\e -> checkSequence
(s++"\nWait "++show t'++" \\"++show (time e)++" ->")
t (k e))
checkSequence s t (Ready e f) =
error $ "checkSequence: "++show t++" > "++show (time e)++"\n"++s++
"\nReady "++show (time e)
-}
checkSequence :: Monad m => t -> Time -> State m a b -> State m a b
checkSequence _ = causal
causal :: Monad m => Time -> State m a b -> State m a b
causal t (Ready e f) | t <= time e = Ready e f
| otherwise = error "Violation of causality"
causal t (Lift m) = Lift (liftM (causal t) m)
causal t (Wait t' s k) = Wait t' (causal t s) (causal t.k)
-- run function supplies initial value and input events, and runs simulation
-- in the underlying monad.
runSim :: Monad m => Sim m t t1 -> t -> [Event t] -> m ()
runSim (Sim f) a as = do
(_,r) <- f a
runState r as
runState :: Monad m => State m t t1 -> [Event t] -> m ()
runState (Ready _ s) as = runState s as
runState (Lift m) as = do s <- m
runState s as
runState (Wait t s _) []
| t==infinity = return () -- infinity never comes
| otherwise = runState s [] -- timeout
runState (Wait t s k) (a:as)
| t <= time a = runState s (a:as) -- timeout
| otherwise = runState (k a) as -- receive event
-- Transition function when a simulation receives an input
after :: Monad m => State m a a1 -> Event a -> State m a a1
Ready b s `after` a = ready b (s `after` a)
Lift m `after` a = lift (liftM (`after` a) m)
Wait t s k `after` a
| t <= time a = s `after` a
| otherwise = k a
instance Monad m => Category (Sim m) where
id = simArr id
(.) = simComp
instance Monad m => Arrow (Sim m) where
arr = simArr
first = simFirst
simArr :: Monad m => (a -> b) -> Sim m a b
simArr f = sim $ \a -> return (f a, s)
where s = waitInput (\a -> ready (Event (time a) (f (value a))) s)
waitInput :: Monad m => (Event a -> State m a b) -> State m a b
waitInput k = wait infinity undefined k
simComp :: Monad m => Sim m t1 b -> Sim m t t1 -> Sim m t b
Sim g `simComp` Sim f = sim $ \a -> do
(b,sf) <- f a
(c,sg) <- g b
return (c,sf `stateComp` sg)
stateComp :: Monad m => State m t1 t -> State m t a1 -> State m t1 a1
sf `stateComp` Ready c sg = ready c (sf `stateComp` sg)
sf `stateComp` Lift m = lift (liftM (sf `stateComp`) m)
Ready b sf `stateComp` sg = sf `stateComp` (sg `after` b)
Lift m `stateComp` sg = lift (liftM (`stateComp` sg) m)
Wait tf sf kf `stateComp` Wait tg sg kg =
wait (min tf tg) timeout (\a -> kf a `stateComp` Wait tg sg kg)
where timeout | tf<tg = sf `stateComp` Wait tg sg kg
| tf>tg = Wait tf sf kf `stateComp` sg
| otherwise = sf `stateComp` sg
simFirst :: Monad m => Sim m a b -> Sim m (a, c) (b, c)
simFirst (Sim f) = sim $ \(a,c) -> do
(b,s) <- f a
return ((b,c), stateFirst b c s)
stateFirst :: Monad m => b -> c -> State m a b -> State m (a, c) (b, c)
stateFirst b c (Ready b' s) =
wait (time b')
(ready (Event (time b') (value b',c)) (stateFirst (value b') c s))
(\(Event t' (a,c')) ->
ready (Event t' (b,c'))
(stateFirst b c' (ready b' (s `after` (Event t' a)))))
stateFirst b c (Lift m) = Lift (liftM (stateFirst b c) m)
stateFirst b c (Wait t s k) =
wait t (stateFirst b c s) $ \(Event t' (a,c')) ->
ready (Event t' (b,c')) (stateFirst b c' (k (Event t' a)))
-- Can we define a loop?
instance MonadFix m => ArrowLoop (Sim m) where
loop = simLoop
simLoop :: MonadFix m => Sim m (t, t1) (b, t1) -> Sim m t b
simLoop (Sim f) = sim $ \a -> do
((b,c),s) <- mfix (\(~((_,c),_)) -> f (a,c))
return (b,stateLoop a c [] s)
-- stateLoop a c q s
-- a = initial value of input
-- c = initial value of state
-- q = queue of future state changes
-- s = running simulation (a,c) to (b,c)
-- result is a running simulation from a to b, where state changes are
-- fed back at the appropriate times.
stateLoop :: Monad m =>
a -> t -> [(Time, t)] -> State m (a, t) (b, t) -> State m a b
stateLoop a c q (Ready (Event t (b,c')) s) =
ready (Event t b) (stateLoop a c (q++[(t,c')]) s)
stateLoop a c q (Lift m) = lift $ liftM (stateLoop a c q) m
stateLoop a c ((t',c'):q) (Wait t s k) =
wait (min t t') timeout $ \(Event t'' a') ->
stateLoop a' c ((t',c'):q) (k (Event t'' (a',c)))
where timeout | t'<t = stateLoop a c' q (k (Event t' (a,c')))
| t'>t = stateLoop a c ((t',c'):q) s
| otherwise = stateLoop a c' q (s `after` Event t (a,c'))
stateLoop a c [] (Wait t s k) =
wait t (stateLoop a c [] s) $ \(Event t' a') ->
stateLoop a' c [] (k (Event t' (a',c)))
-- arrM lifts a monadic function into a Sim arrow.
arrM :: Monad m => (a -> m b) -> Sim m a b
arrM f = sim $ \a -> do
b <- f a
return (b,s)
where s = waitInput $ \(Event t a) -> lift $ do
b <- f a
return (ready (Event t b) s)
--printA prints all events that pass through
printA :: Show b => [Char] -> Sim IO b b
printA name = sim $ \a -> do
message (show a++"@init")
return (a,s)
where s = waitInput $ \a -> Lift $ do
message (show a)
return (ready a s)
message a = if null name then putStrLn a else putStrLn (name++": "++a)
--delay1 d delays events by d, removing events at the same time
delay1 :: Monad m => Time -> Sim m b b
delay1 d = sim (\a -> return (a,r))
where r = waitInput go
go (Event t a) =
wait (t+d) (ready (Event (t+d) a) r) $ \(Event t' a') ->
if t==t'
then go (Event t' a')
else ready (Event (t+d) a) (go (Event t' a'))
initially :: Monad m => b -> Sim m t b -> Sim m t b
initially x (Sim f) = Sim $ \a -> do (_,s) <- f a
return (x,s)
--nubA filters out events that repeat values
nubA :: (Eq a, Monad m) => Sim m a a
nubA = sim $ \a -> return (a,go a)
where go a = waitInput $ \(Event t a') ->
if a==a' then go a else ready (Event t a') (go a')
-- cutoff t s stops a simulation after time t
cutoff :: Monad m => Time -> Sim m t b -> Sim m t b
cutoff t (Sim f) = sim $ \a -> do
(b,r) <- f a
return (b, cutoffState t r)
cutoffState :: Monad m =>
Time -> State m a a1 -> State m a a1
cutoffState t (Ready b s)
| time b<=t = ready b (cutoffState t s)
| otherwise = stop
where stop = waitInput (const stop)
cutoffState t (Lift m) = lift (liftM (cutoffState t) m)
cutoffState t (Wait t' s k)
| t'<=t = wait t' (cutoffState t s) (cutoffState t.k)
| otherwise = wait infinity undefined (cutoffState t.k)
-- Experiments with arrow notation
nor :: Monad m => Sim m (Bool,Bool) Bool
nor = proc (a,b) -> do
(a',b') <- delay1 0.1 -< (a,b)
returnA -< not (a'||b')
afix :: (MonadFix m, Eq b) => Sim m (a,b) b -> Sim m a b
afix f = loop (f >>> nubA >>> arr id &&& arr id) >>> nubA
flipflop :: MonadFix m => Sim m (Bool,Bool) (Bool,Bool)
flipflop = proc (reset,set) ->
(|afix (\ ~(x,y)->do
x' <- initially False nor -< (reset,y)
y' <- initially True nor -< (set,x)
returnA -< (x',y'))|)
oscillator :: MonadFix m => Sim m Bool Bool
oscillator = proc enable ->
(|afix (\x -> nor -< (enable,x))|)
-- probe counts the transitions on a channel
-- this is useful for estimating power consumption
probe :: Metric a => String -> (Sim IO a a -> IO b) -> IO b
probe s k = do r <- newIORef 0
ans <- k (probeArr r)
n <- readIORef r
putStrLn (s++": "++show n++" transitions")
return ans
where probeArr r = sim $ \a -> return (a, stateProbe r a)
stateProbe r a = waitInput $ \(Event t b) ->
lift $ do
modifyIORef r (+distance a b)
return (ready (Event t b) (stateProbe r b))
class Metric a where
distance :: a -> a -> Double
bound :: a -> Double -- the distance between any two points is below bound
-- bound does not evaluate its argument
instance Metric Bool where
distance a b = if a==b then 0 else 1
bound _ = 1
instance (Metric a, Metric b) => Metric (a,b) where
distance (a,b) (c,d) = distance a c+distance b d
bound ~(a,b)= bound a + bound b
instance (Metric a, Metric b) => Metric (Either a b) where
distance (Left a) (Left a') = distance a a'
distance (Right b) (Right b') = distance b b'
distance x _ = 1 + (bound a `max` bound b)
where Left a = x
Right b = x
bound x = 1 + (bound l `max` bound r)
where Left l = x
Right r = x
instance Metric a => Metric [a] where
distance [] [] = 0
distance (x:xs) (y:ys) = distance x y + distance xs ys
distance [] (y:ys) = (bound y+1)*(fromInteger (toInteger (length ys))+1)
distance xs [] = distance [] xs
bound _ = infinity