{-# LANGUAGE DeriveDataTypeable, ScopedTypeVariables, TypeFamilies #-} module Reflex.Dom.Contrib.Time ( poissonLossyFrom , poissonLossy , inhomogeneousPoissonFrom , inhomogeneousPoisson ) where import Reflex import Reflex.Dom.Class import Reflex.Dom.Time import Control.Concurrent import Control.Monad import Control.Monad.IO.Class import Data.Time.Clock import System.Random -- | Send events with Poisson timing with the given basis and rate -- Each occurence of the resulting event will contain the index of -- the current interval, with 0 representing the basis time poissonLossyFrom :: (RandomGen g, MonadWidget t m) => g -> Double -- ^ Poisson event rate (Hz) -> UTCTime -- ^ Baseline time for events -> Event t a -- ^ Event that starts a tick generation thread. Usually you want this to -- be something like the result of getPostBuild that only fires once. But -- there could be uses for starting multiple timer threads. -- Start sending events in response to the event parameter. -> m (Event t TickInfo) poissonLossyFrom rnd rate t0 t = inhomogeneousPoissonFrom rnd (current $ constDyn rate) rate t0 t -- | Send events with Poisson timing with the given basis and rate -- Each occurence of the resulting event will contain the index of -- the current interval, with 0 representing the basis time. -- Automatically begin sending events when the DOM is built poissonLossy :: (RandomGen g, MonadWidget t m) => g -> Double -- ^ Poisson event rate (Hz) -> UTCTime -- ^ Baseline time for events -> m (Event t TickInfo) poissonLossy rnd rate t0 = poissonLossyFrom rnd rate t0 =<< getPostBuild -- | Send events with inhomogeneous Poisson timing with the given basis -- and variable rate. Provide a maxRate that you expect to support. inhomogeneousPoissonFrom :: (RandomGen g, MonadWidget t m) => g -> Behavior t Double -> Double -> UTCTime -> Event t a -> m (Event t TickInfo) inhomogeneousPoissonFrom rnd rate maxRate t0 e = do -- Create a thread for producing homogeneous poisson events -- along with random Doubles (usage of Double's explained below) ticksWithRateRand <- performEventAsync $ fmap callAtNextInterval e -- Filter homogeneous events according to associated -- random values and the current rate parameter return $ attachWithMaybe filterFun rate ticksWithRateRand where -- Inhomogeneous poisson processes are built from faster -- homogeneous ones by randomly dropping events from the -- fast process. For each fast homogeneous event, choose -- a uniform random sample from (0, rMax). If the -- inhomogeneous rate at this moment is greater than the -- random sample, then keep this event, otherwise drop it filterFun :: Double -> (TickInfo, Double) -> Maybe TickInfo filterFun r (tInfo, p) | r >= p = Just tInfo | otherwise = Nothing callAtNextInterval _ cb = void $ liftIO $ forkIO $ go t0 rnd cb 0 go tTargetLast lastGen cb lastN = do t <- getCurrentTime -- Generate random numbers for this poisson interval (u) -- and sample-retention likelihood (p) let (u, nextGen) = randomR (0,1) lastGen (p :: Double, nextGen') = randomR (0,maxRate) nextGen -- Inter-event interval is drawn from exponential -- distribution accourding to u let dt = realToFrac $ -1 * log(u)/maxRate :: NominalDiffTime nEvents = lastN + 1 alreadyElapsed = diffUTCTime t tTargetLast tTarget = addUTCTime dt tTargetLast thisDelay = realToFrac $ diffUTCTime tTarget t :: Double threadDelay $ ceiling $ thisDelay * 1000000 void $ cb $ (TickInfo t nEvents alreadyElapsed, p) go tTarget nextGen' cb nEvents -- | Send events with inhomogeneous Poisson timing with the given basis -- and variable rate. Provide a maxRate that you expect to support inhomogeneousPoisson :: (RandomGen g, MonadWidget t m) => g -> Behavior t Double -> Double -> UTCTime -> m (Event t TickInfo) inhomogeneousPoisson rnd rate maxRate t0 = inhomogeneousPoissonFrom rnd rate maxRate t0 =<< getPostBuild