Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Recording #354

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 116 additions & 57 deletions rhine-bayes/app/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ In this example, you will find the following:
* A more scalable, modular, interactive architecture, where all these three systems run on separate clocks,
and the user can interactively change the temperature of the heat bath
-}
{-# LANGUAGE LambdaCase #-}
module Main where

-- base
import Control.Monad (replicateM, void)
import Control.Monad (replicateM, void, guard)
import Data.Maybe (fromMaybe)
import Data.Monoid (Product (Product, getProduct))
import GHC.Float (double2Float, float2Double)
Expand Down Expand Up @@ -51,6 +52,10 @@ import FRP.Rhine.Gloss.IO
-- rhine-bayes
import FRP.Rhine.Bayes
import FRP.Rhine.Gloss.Common
import FRP.Rhine.Gloss (GlossEventClock)
import Control.Monad.Schedule.Class (MonadSchedule (..))
import Unsafe.Coerce (unsafeCoerce)
import Control.Monad.Trans.Reader (ReaderT(ReaderT))

type Temperature = Double
type Pos = (Double, Double)
Expand All @@ -70,7 +75,8 @@ prior1d ::
StochasticProcessF td Temperature Double
prior1d initialPosition initialVelocity = feedback 0 $ proc (temperature, position') -> do
impulse <- whiteNoiseVarying -< temperature
let acceleration = (-3) * position' + impulse
let springConstant = 3
acceleration = (- springConstant) * position' + impulse
-- Integral over roughly the last 10 seconds, dying off exponentially, as to model a small friction term
velocity <- arr (+ initialVelocity) <<< decayIntegral 10 -< acceleration
position <- integralFrom initialPosition -< velocity
Expand Down Expand Up @@ -267,7 +273,7 @@ glossSettings =
mains :: [(String, IO ())]
mains =
[ ("single rate", mainSingleRate)
, ("single rate, parameter collapse", mainSingleRateCollapse)
, ("multi rate, parameter collapse", mainMultiRateCollapse)
, ("multi rate, temperature process", mainMultiRate)
]

Expand All @@ -289,51 +295,6 @@ glossClock =
, rescale = float2Double
}

-- *** Poor attempt at temperature inference: Particle collapse

-- | Choose an exponential distribution as prior for the temperature
temperaturePrior :: (MonadDistribution m) => m Temperature
temperaturePrior = gamma 1 7

{- | On startup, sample values from the temperature prior.
Then keep sampling from the position prior and condition by the likelihood of the measured sensor position.
-}
posteriorTemperatureCollapse :: (MonadMeasure m, Diff td ~ Double) => BehaviourF m td Sensor (Temperature, Pos)
posteriorTemperatureCollapse = proc sensor -> do
temperature <- performOnFirstSample (arr_ <$> temperaturePrior) -< ()
latent <- prior -< temperature
arrM score -< sensorLikelihood latent sensor
returnA -< (temperature, latent)

{- | Given an actual temperature, simulate a latent position and measured sensor position,
and based on the sensor data infer the latent position and the temperature.
-}
filteredCollapse :: (Diff td ~ Double) => BehaviourF App td Temperature Result
filteredCollapse = proc temperature -> do
(measured, latent) <- genModelWithoutTemperature -< temperature
particlesAndTemperature <- runPopulationCl nParticles resampleSystematic posteriorTemperatureCollapse -< measured
returnA
-<
Result
{ temperature
, measured
, latent
, particlesPosition = first snd <$> particlesAndTemperature
, particlesTemperature = first fst <$> particlesAndTemperature
}

-- | Run simulation, inference, and visualization synchronously
mainClSFCollapse :: (Diff td ~ Double) => BehaviourF App td () ()
mainClSFCollapse = proc () -> do
output <- filteredCollapse -< initialTemperature
visualisation -< output

mainSingleRateCollapse =
void $
sampleIO $
launchInGlossThread glossSettings $
reactimateCl glossClock mainClSFCollapse

-- *** Infer temperature with a stochastic process

{- | Given an actual temperature, simulate a latent position and measured sensor position,
Expand Down Expand Up @@ -379,20 +340,109 @@ glossClockUTC cl =
return (arr $ \(timePassed, event) -> (addUTCTime (realToFrac timePassed) now, event), now)
}

type ModelClock = LiftClock IO GlossConcT (Millisecond 100)

-- | The model is sampled at 100 FPS.
modelClock :: ModelClock
modelClock = liftClock waitClock

{- | The part of the program which simulates latent position and sensor,
running 100 times a second.
-}
modelRhine :: Rhine (GlossConcT IO) (LiftClock IO GlossConcT (Millisecond 100)) Temperature (Temperature, (Sensor, Pos))
modelRhine = hoistClSF sampleIOGloss (clId &&& genModelWithoutTemperature) @@ liftClock waitClock
model :: ClSF (GlossConcT IO) ModelClock Temperature (Temperature, (Sensor, Pos))
model = hoistClSF sampleIOGloss (clId &&& genModelWithoutTemperature)

-- | The user can change the temperature by pressing the up and down arrow keys,
-- or press the "r" key.
data UserInput
= TemperatureFactor (Product Double)
| ResetKey
deriving Eq

type UserClock = SelectClock (GlossClockUTC GlossEventClockIO) UserInput

userClock :: UserClock
userClock = SelectClock
{ mainClock = glossClockUTC GlossEventClockIO
, select = \case
EventKey (SpecialKey KeyUp) Down _ _ -> Just $ TemperatureFactor $ Product 1.2
EventKey (SpecialKey KeyDown) Down _ _ -> Just $ TemperatureFactor $ Product $ 1 / 1.2
EventKey (Char 'r') Down _ _ -> Just ResetKey
_ -> Nothing
}

-- | The user can change the temperature by pressing the up and down arrow keys.
userTemperature :: ClSF (GlossConcT IO) (GlossClockUTC GlossEventClockIO) () Temperature
userTemperature = tagS >>> arr (selector >>> fmap Product) >>> mappendS >>> arr (fmap getProduct >>> fromMaybe 1 >>> (* initialTemperature))
userTemperature :: ClSF (GlossConcT IO) UserClock () Temperature
userTemperature = tagS >>> arr selector >>> mappendS >>> arr (fmap getProduct >>> fromMaybe 1 >>> (* initialTemperature))
where
selector (EventKey (SpecialKey KeyUp) Down _ _) = Just 1.2
selector (EventKey (SpecialKey KeyDown) Down _ _) = Just (1 / 1.2)
selector (TemperatureFactor factor) = Just factor
selector _ = Nothing

-- *** Poor attempt at temperature inference: Particle collapse

-- | Choose an exponential distribution as prior for the temperature
temperaturePrior :: (MonadDistribution m) => m Temperature
temperaturePrior = gamma 1 7


{- | On startup, sample values from the temperature prior.
Then keep sampling from the position prior and condition by the likelihood of the measured sensor position.
-}
posteriorTemperatureCollapse :: (MonadMeasure m, Diff td ~ Double) => BehaviourF m td (Bool, Sensor) (Temperature, Pos)
posteriorTemperatureCollapse = proc (resetTemperature, sensor) -> do
temperature <- cache temperaturePrior -< resetTemperature
latent <- prior -< temperature
arrM score -< sensorLikelihood latent sensor
returnA -< (temperature, latent)
where
cache :: Monad m => m b -> BehaviourF m td Bool b
cache action = feedback Nothing $ proc (invalidateCache, bLast) -> do
let bCached = guard (not invalidateCache) >> bLast
bNew <- case bCached of
Nothing -> do
constMCl action -< ()
Just b -> do
returnA -< b
returnA -< (bNew, Just bNew)

{- | Given an actual temperature, simulate a latent position and measured sensor position,
and based on the sensor data infer the latent position and the temperature.
-}
inferenceCollapse :: Rhine (GlossConcT IO) (LiftClock IO GlossConcT Busy) (Bool, (Temperature, (Sensor, Pos))) Result
inferenceCollapse = hoistClSF sampleIOGloss inferenceBehaviour @@ liftClock Busy
where
inferenceBehaviour :: (MonadDistribution m, Diff td ~ Double, MonadIO m) => BehaviourF m td (Bool, (Temperature, (Sensor, Pos))) Result
inferenceBehaviour = proc (reset, (temperature, (measured, latent))) -> do
positionsAndTemperatures <- runPopulationCl nParticles resampleSystematic posteriorTemperatureCollapse -< (reset, measured)
returnA
-<
Result
{ temperature
, measured
, latent
, particlesPosition = first snd <$> positionsAndTemperatures
, particlesTemperature = first fst <$> positionsAndTemperatures
}

mainRhineMultiRateCollapse =
(tagS &&& userTemperature)
@@ userClock
>-- collect *-* keepLast initialTemperature -->
(clId *** model) @@ modelClock
>-- reset *-* keepLast (initialTemperature, (zeroVector, zeroVector)) -->
inferenceCollapse
>-- keepLast emptyResult -->
visualisationRhine
where
-- | Whether the user pressed the reset key
reset :: Monad m => ResamplingBuffer m cl1 cl2 [UserInput] Bool
reset = collect >>-^ arr (concat >>> filter (== ResetKey) >>> not . null)

mainMultiRateCollapse = void $
launchInGlossThread glossSettings $
flow mainRhineMultiRateCollapse

-- *** Multi rate with stochastic process

{- | This part performs the inference (and passes along temperature, sensor and position simulations).
It runs as fast as possible, so this will potentially drain the CPU.
-}
Expand Down Expand Up @@ -420,9 +470,9 @@ visualisationRhine = hoistClSF sampleIOGloss visualisation @@ glossClockUTC Glos
-- | Compose all four asynchronous components to a single 'Rhine'.
mainRhineMultiRate =
userTemperature
@@ glossClockUTC GlossEventClockIO
@@ userClock
>-- keepLast initialTemperature -->
modelRhine
model @@ modelClock
>-- keepLast (initialTemperature, (zeroVector, zeroVector)) -->
inference
>-- keepLast emptyResult -->
Expand All @@ -447,3 +497,12 @@ instance (MonadMeasure m) => MonadMeasure (GlossConcT m)

sampleIOGloss :: App a -> GlossConcT IO a
sampleIOGloss = hoist sampleIO

unsafeMkSampler :: ReaderT g m a -> Sampler g m a
unsafeMkSampler = unsafeCoerce

runSampler :: Sampler g m a -> ReaderT g m a
runSampler = ReaderT . sampleWith

instance (Monad m, MonadSchedule m) => MonadSchedule (Sampler g m) where
schedule = unsafeMkSampler . fmap (second (map unsafeMkSampler)) . schedule . fmap runSampler
1 change: 1 addition & 0 deletions rhine-bayes/rhine-bayes.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ executable rhine-bayes-gloss
, log-domain
, mmorph
, time
, monad-schedule
default-language: Haskell2010
default-extensions:
Arrows
Expand Down