File: Random.hs

package info (click to toggle)
bali-phy 4.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 15,392 kB
  • sloc: cpp: 120,442; xml: 13,966; haskell: 9,975; python: 2,936; yacc: 1,328; perl: 1,169; lex: 912; sh: 343; makefile: 26
file content (426 lines) | stat: -rw-r--r-- 16,873 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
module Probability.Random (module Probability.Random,
                           module Range,
                           module Numeric.LogDouble,
                           module Numeric.Prob,
                           module Probability.Dist,
                           modifiable,
                           liftIO,
                           withEffect)
    where

import Range
import Parameters
import MCMC
import Data.JSON as J
import Effect
import Control.Monad.IO.Class -- for liftIO
import Data.IntMap (IntMap)
import Data.Array (Array)
import Numeric.LogDouble
import Numeric.Prob
import Control.Monad.Fix

import           System.IO
import qualified Data.Text.IO as TIO

import Probability.Dist

-------------------------- AnnotatedDensity ------------------------

data AnnotatedDensity a where
    InEdge :: String -> b -> AnnotatedDensity ()
    ADBind :: AnnotatedDensity b -> (b -> AnnotatedDensity a) -> AnnotatedDensity a
    ADReturn :: a -> AnnotatedDensity a
    ProbFactor :: Double -> AnnotatedDensity ()

in_edge name node = InEdge name node


instance Functor AnnotatedDensity where
    fmap f x = ADBind x (\result -> ADReturn (f result))

instance Applicative AnnotatedDensity where
    pure  x = ADReturn x
    f <*> x = ADBind x (\x' -> ADBind f (\f' -> pure (f' x')))

instance Monad AnnotatedDensity where
    f >>= g = ADBind f g


-- Just get the densities out
-- No ProbFactor events yet.
get_densities :: AnnotatedDensity a -> a
get_densities (ADReturn x) = x
get_densities (ADBind f g) = let x = get_densities f in get_densities (g x)
get_densities (InEdge _ x) = ()


makeEdges :: Effect -> AnnotatedDensity a -> IO a
makeEdges event (ADReturn x) = return x
makeEdges event (ADBind f g) = do x <- makeEdges event f
                                  makeEdges event (g x)
makeEdges event (InEdge name node) = do register_in_edge node event name
                                        return ()

-- We can observe from these.
-- PROBLEM: What if observing from a distribution is supposed to update
--          a summary statistic?  Can we then register a properties object for each
--          observation?
class Dist d => HasAnnotatedPdf d where
    type DistProperties d :: Type
    type DistProperties d = ()
    annotated_densities :: d -> Result d -> AnnotatedDensity ([LogDouble], DistProperties d)

densities dist x = fst $ get_densities $ annotated_densities dist x
density dist x = balancedProduct (densities dist x)


---------------------------- TKEffects --------------------------

data TKEffects a = SamplingRate Double (TKEffects a)
                 | TKLiftIO (Double -> IO a)
                 | TKReturn a
                 | forall b . TKBind (TKEffects b) (b -> TKEffects a)

instance MonadIO TKEffects where
    liftIO io = TKLiftIO (\_ -> io)

instance Functor TKEffects where
    fmap f x = TKBind x (\result -> TKReturn (f result))

instance Applicative TKEffects where
    pure  x = TKReturn x
    f <*> x = TKBind x (\x' -> TKBind f (\f' -> pure (f' x')))

instance Monad TKEffects where
    return x = TKReturn x
    f >>= g  = TKBind f g

--------------------------- Random a ----------------------------

data Random a where
    RanOp :: ((forall b.Random b -> IO b) -> IO a) -> Random a
    RanBind :: Random b -> (b -> Random a) -> Random a
    Lazy :: Random a -> Random a
    WithTKEffect :: Random a -> (a -> TKEffects b) -> Random a
    PerformTKEffect :: TKEffects a -> Random a
    RanDistribution2 :: (IOSampleable d, HasAnnotatedPdf d) => d -> (Result d -> TKEffects b) -> Random (Result d)
    RanDistribution3 :: HasAnnotatedPdf d => d -> ((Result d)->TKEffects b) -> ((Result d) -> ((Result d) -> IO ()) -> (Result d)) -> Random (Result d) -> Random (Result d)
    RanSamplingRate :: Double -> Random a -> Random a
    RanInterchangeable :: Random b -> Random (Random b)

{-
We need the non-RanOp constructors because they behave differently in the different interpreters.
- RanOp: does the same thing in all interpreters.
- RanBind: adds unsafeInterleaveIO in the lazy interpreters, but not the strict ones.
- Lazy: doesn't just add unsafeInterleaveIO --> also forwards to a different interpreter.
- WithTKEffect: ignores the effect in the simple interpreters, runs the effect in the MCMC interpreters.
- PerformTKEffect: runs the effect at rate 1.0 in the simple interpreters, but at rate `rate` in the MCMC interpreters.
- RanDistribution2 just runs sampleIO in the simple interpreters, but creates a modifiable that runs sampleIO in the MCMC interpreters.
- RanDistribution3 just runs the sampling action in the simple interpreters, but creates a random structure and
   runs the associate tk_effects in the mcmc interpreter.
- RanSamplingRate: does nothing in the simple interpreters, but affects the rate of TKs in the MCMC interpreters
- RanInterchangeable: errors out in the strict interpeters, does nothing in the lazy simple interpreter, and
  creates an interchangable random dist in the lazy MCMC interpreter.
-}


instance Functor Random where
    fmap f r = RanBind r (return . f)

instance Applicative Random where
    pure  x = RanOp (\interp -> return x)
    f <*> x = RanBind x (\x' -> RanBind f (\f' -> pure (f' x')))

instance Monad Random where
    f >>= g  = RanBind f g
--    f >>= g  = RanOp (\i -> (i f) >>= (i . g))
--    ^^ This doesn't work for the lazy interpreters because its strict.

instance MonadFix Random where
    mfix f   = RanOp (\interp -> mfix $ interp . f)



instance Dist (Random a) where
    type Result (Random a) = a

instance IOSampleable (Random a) where
    sampleIO dist = runRandomLazy dist

--------------------------- class Sampleable -----------------------------

-- These objects have a corresponding Random action.
class Dist d => Sampleable d where
    sample :: d -> Random (Result d)

prior = sample

instance Sampleable (Random a) where
    sample r = r

--------------------------- class SampleableWithProps -------------------

class (Sampleable d, HasAnnotatedPdf d) => SampleableWithProps d where
    sampleWithProps :: d -> Random (Result d, DistProperties d)

-------------------------------------------------------------------------

registerDistProperties event props = register_dist_property event props "properties"

{- Note: sampleEffectProbs is NEARLY the same as observe.

Differences include:
* observe starts with liftIO .. this allows it to run in the Random monad.
* observe uses register_likelihood vs register_prior
* observe user register_dist_observe vs register_dist_sample
* observe does NOT run tk_effects

We could actually factor out the common parts if we could pass a constant SampleType = Sample | Observe

-}

{- This runs as a SEQUENCE of operations in the IO monad.
 - The sequence can in theory be changeable, if it depends (for example) on the number of density terms.
 - It would seem that the coalescent probability, which has a fixed number of terms but gets them by sorting,
   appears to have a changeable list of density terms.
 -}

sampleEffectProps rate dist tk_effect x = do
  runTkEffects rate $ tk_effect x
  s <- register_dist_sample (dist_name dist)
  register_out_edge s x
  (densityTerms, props) <- makeEdges s $ annotated_densities dist x
  registerDistProperties s props
  sequence_ [register_prior s term | term <- densityTerms]
  return props

sampleEffect rate dist tk_effect x = do
  sampleEffectProps rate dist tk_effect x
  return () 

infix 0 `observe`

observe datum dist = liftIO $ do
  s <- register_dist_observe (dist_name dist)
  register_out_edge s datum
  (densityTerms, props) <- makeEdges s $ annotated_densities dist datum
  registerDistProperties s props
  sequence_ [register_likelihood s term | term <- densityTerms]
  return props

possible = 1
impossible = 0
require p = if p then possible else impossible

condition cond = liftIO $ do
  s <- register_dist_observe "condition"
  register_likelihood s (require cond)
  return ()

{- NOTE: Problems with lists of probability factors.

In order to determine whether a PDF term corresponds to a newly sampled variable,
we check if the term has been newly registered.  However, if the list of factors
is itself changeable  (as in the coalescent pdf which does an internal sort), we
can end up registering new terms (I think), which are then ignored in the ratio
(I think).

An alternative would be to look not at the individual terms, but at the
distribution identifier created in `s <- register_dist_*`.  This shouldn't get
invalidated if the PDF terms change.

However, this leaves the question of what to do if the coalescent tree itself
changes dimension.

 -}

instance MonadIO Random where
    liftIO io = RanOp (\interp -> io)

lazy = Lazy
interchangeable = RanInterchangeable
infixl 2 `withTKEffect`
withTKEffect = WithTKEffect

addLogger logger = liftIO $ registerLogger logger

do_nothing _ = return ()

runRandomStrict :: Random a -> IO a
runRandomStrict (RanBind f g) = do
  x <- runRandomStrict f
  runRandomStrict $ g x
runRandomStrict (RanSamplingRate _ a) = runRandomStrict a
runRandomStrict (RanInterchangeable r) = return r
-- These are the lazily executed parts of the strict monad.
runRandomStrict dist@(RanDistribution2 _ _) = runRandomLazy dist
runRandomStrict dist@(RanDistribution3 _ _ _ _) = runRandomLazy dist
runRandomStrict e@(WithTKEffect _ _) = runRandomLazy e
runRandomStrict (Lazy r) = unsafeInterleaveIO $ runRandomLazy r
runRandomStrict (RanInterchangeable _) = error "runRandomStrict: RanInterchangeable"
runRandomStrict (PerformTKEffect _) = error "runRandomStrict: PerformTKEffect"
runRandomStrict (RanOp op) = op runRandomStrict

class Monad m => AddMove m where
    addMove :: Double -> TransitionKernel -> m Effect

instance AddMove Random where
    addMove rate move = RanSamplingRate rate $ PerformTKEffect $ TKLiftIO $ (\rate -> registerTransitionKernel rate move)

add_move m = TKLiftIO $ (\rate -> registerTransitionKernel rate m)

instance AddMove TKEffects where
    addMove rate' move = TKLiftIO $ (\rate -> registerTransitionKernel (rate*rate') move)

runTkEffects :: Double -> TKEffects a -> IO a
runTkEffects rate (TKBind f g) = do x <- runTkEffects rate f
                                    runTkEffects rate $ g x
runTkEffects rate (TKReturn v) = return v
runTkEffects rate (TKLiftIO action) = action rate
runTkEffects rate (SamplingRate rate2 a) = runTkEffects (rate*rate2) a
-- LiftIO and Print are only here for debugging purposes:
--  runTkEffects alpha rate (LiftIO a) = a
--  runTkEffects alpha rate (Print s) = putStrLn (show s)

runRandomLazy :: Random a -> IO a
runRandomLazy (RanBind f g) = do
  x <- unsafeInterleaveIO $ runRandomLazy f
  runRandomLazy $ g x
runRandomLazy (RanSamplingRate _ a) = runRandomLazy a
-- Problem: distributions aren't part of the Random monad!
runRandomLazy (RanDistribution2 dist _) = unsafeInterleaveIO $ sampleIO dist
runRandomLazy (RanDistribution3 _ _ _ do_sample) = unsafeInterleaveIO $ runRandomLazy do_sample
runRandomLazy (PerformTKEffect e) = runTkEffects 1.0 e
runRandomLazy (WithTKEffect action _) = runRandomLazy action
runRandomLazy (Lazy a) = runRandomLazy a
runRandomLazy (RanInterchangeable a) = return a
runRandomLazy (RanOp op) = op runRandomLazy

-- Also, shouldn't the modifiable function actually be some kind of monad, to prevent let x=modifiable 0;y=modifiable 0 from merging x and y?

runMCMCStrict :: Double -> Random a -> IO a
runMCMCStrict rate (RanBind f g) = do
  x <- runMCMCStrict rate f
  runMCMCStrict rate $ g x
runMCMCStrict rate (PerformTKEffect e) = runTkEffects rate e
runMCMCStrict rate (RanSamplingRate rate2 a) = runMCMCStrict (rate*rate2) a
-- These are the lazily executed parts of the strict monad.
runMCMCStrict rate ix@(RanInterchangeable r) = runMCMCLazy rate ix
runMCMCStrict rate dist@(RanDistribution2 _ _) = runMCMCLazy rate dist
runMCMCStrict rate dist@(RanDistribution3 _ _ _ _) = runMCMCLazy rate dist
runMCMCStrict rate e@(WithTKEffect _ _) = runMCMCLazy rate e
runMCMCStrict rate (Lazy r) = unsafeInterleaveIO $ runMCMCLazy rate r -- See Note below.
runMCMCStrict rate (RanOp op) = op (runMCMCStrict rate)

{- NOTE: unsafeInterleaveIO $ runMCMCLazy

In order for (runMCMCLazy) to actually be lazy, we need to avoid returning
SOMETHING `seq` result.  And this means that we need to frequently
intersperse unsafeInterleaveIO to avoid `seq`-ing on previous statements.
-}

-- Note on unsafeInterleaveIO:
--       Simply using runRandomLazy does not guarantee that the result of runRandomLazy
--       will not be demanded.  (It guarantees that f >>= g that are INSIDE runRandomLazy
--       won't demand the result of the f).
--       We need to guard any IO operations with unsafeInterleaveIO if we
--       want to prevent their results from being demanded.
--
-- QUESTION: So, do we need to guard the execution of Distributions with unsafeInterleaveIO?
-- ANSWER: No.  If its not the last entry in a sequence, it will get unsafeInterleaveIO from
--         runMCMCLazy _ (IOAndPass _ _).
--         If it is run from runMCMCStrict directly, then it is run with
--         unsafeInterleaveIO $ runMCMCLazy, so we get an unsafeInterleaveIO from there.
--
runMCMCLazy :: Double -> Random a -> IO a
runMCMCLazy rate (RanBind f g) = do
  x <- unsafeInterleaveIO $ runMCMCLazy rate f
  runMCMCLazy rate $ g x
runMCMCLazy rate (RanDistribution2 dist tk_effect) = do
  x <- modifiableIO $ sampleIO dist
  effect <- sampleEffect rate dist tk_effect x
  return (withEffect effect x)
runMCMCLazy rate (RanDistribution3 dist tk_effect structure do_sample) = do
 -- Note: unsafeInterleaveIO means that we will only execute this line if `value` is accessed.
  value <- unsafeInterleaveIO $ runRandomLazy do_sample
  return $ structure value (sampleEffect rate dist tk_effect)
runMCMCLazy rate (RanSamplingRate rate2 a) = runMCMCLazy (rate*rate2) a
runMCMCLazy rate (Lazy r) = runMCMCLazy rate r
runMCMCLazy rate (WithTKEffect action tk_effect) = unsafeInterleaveIO $ do
  result <- unsafeInterleaveIO $ runMCMCLazy rate action
  effect <- unsafeInterleaveIO $ runTkEffects rate $ tk_effect result
  return (withEffect effect result)
runMCMCLazy rate (RanInterchangeable r) = do
  id <- unsafeInterleaveIO $ getInterchangeableId
  registerTransitionKernel rate $ interchangeEntries id
  return $ liftIO $ IO (\s -> (s, interchangeableIO id (runMCMCLazy rate r) s))
runMCMCLazy rate (RanOp op) = op (runMCMCLazy rate)

mcmc = runMCMCStrict 1.0

makeMCMCModel m = makeModel $ runMCMCStrict 1.0 m

foreign import bpcall "MCMC:" createContext :: [(Key,JSON)] -> CJSON -> IO ContextIndex
makeModel m = createContext prog log where
    prog = (unsafePerformIO m)
    log = c_json $ log_to_json $ prog

foreign import bpcall "MCMC:" writeTraceGraph :: ContextIndex -> IO ()

-- Loggers: we can only log things with the ToJSON property
infix 1 %=%, %>%
name %=% value = (toJSONKey name, toJSON value)
prefix %>% subvalue = (toJSONKey $ prefix ++ "/", log_to_json subvalue)

log_to_json loggers = J.Object $ loggers

-- Define some helper functions
make_densities density x = return ([density x],())
make_densities' densities x = return (densities x,())
pair_apply f (x:y:t) = f x y : pair_apply f t
pair_apply _ t       = t

foldt f z []  = z
foldt f _ [x] = x
foldt f z xs  = foldt f z (pair_apply f xs)

balancedProduct xs = foldt (*) 1 xs

-- maybe I should rename this to (modifiableList_n n f value) or something.
mapn n f xs = go 0 where
    go i | i==n      = []
         | otherwise = f (xs!!i):go (i+1)


------------------------ Modifiable structures --------------------------
                       
triggeredModifiableStructure :: ((forall a.a -> a) -> b -> b) -> b -> (b -> IO ()) -> b
triggeredModifiableStructure modStructure value effect = triggered_x
    where raw_x       = modStructure modifiable value
          effect'     = unsafePerformIO $ effect raw_x
          triggered_x = modStructure (withEffect effect') raw_x

applyModifier :: (forall a.a -> a) -> b -> b
applyModifier x y = x y

modifiableStructure :: b -> (b -> IO ()) -> b
modifiableStructure = triggeredModifiableStructure applyModifier

-- It seems like we could return raw_x in most cases, except the case of a tree.
-- But in the tree case, we could return triggered_x.

------------------------- Interchangeables ---------------------------
                       
foreign import bpcall "MCMC:" getInterchangeableId :: IO Int

foreign import bpcall "MCMC:" interchangeEntriesRaw :: Int -> ContextIndex -> IO ()
interchangeEntries id = TransitionKernel $ interchangeEntriesRaw id

foreign import bpcall "MCMC:" registerInterchangeable :: Int -> a -> Effect

foreign import bpcall "Modifiables:interchangeable" interchangeableRaw :: (a->b) -> a -> c -> b

interchangeableIO id x s = let e = interchangeableRaw unsafePerformIO x s
                           in registerInterchangeable id e `seq` e