gemlib 0.9.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. gemlib/__init__.py +9 -0
  2. gemlib/distributions/__init__.py +26 -0
  3. gemlib/distributions/brownian.py +141 -0
  4. gemlib/distributions/categorical2.py +33 -0
  5. gemlib/distributions/continuous_markov.py +371 -0
  6. gemlib/distributions/continuous_time_state_transition_model.py +185 -0
  7. gemlib/distributions/continuous_time_state_transition_model_test.py +289 -0
  8. gemlib/distributions/discrete_markov.py +279 -0
  9. gemlib/distributions/discrete_rejection_sampling.py +149 -0
  10. gemlib/distributions/discrete_time_state_transition_model.py +324 -0
  11. gemlib/distributions/discrete_time_state_transition_model_examples.py +453 -0
  12. gemlib/distributions/discrete_time_state_transition_model_test.py +336 -0
  13. gemlib/distributions/experimental/__init__.py +7 -0
  14. gemlib/distributions/experimental/discrete_approx_cont_state_transition_model.py +194 -0
  15. gemlib/distributions/experimental/state_transition_marginal_model.py +352 -0
  16. gemlib/distributions/hypergeometric.py +144 -0
  17. gemlib/distributions/hypergeometric_sampler.py +103 -0
  18. gemlib/distributions/hypergeometric_test.py +46 -0
  19. gemlib/distributions/kcategorical.py +113 -0
  20. gemlib/distributions/kcategorical_test.py +52 -0
  21. gemlib/distributions/uniform_integer.py +169 -0
  22. gemlib/distributions/uniform_integer_test.py +55 -0
  23. gemlib/mcmc/__init__.py +23 -0
  24. gemlib/mcmc/adaptive_random_walk_metropolis.py +859 -0
  25. gemlib/mcmc/adaptive_random_walk_metropolis_test.py +144 -0
  26. gemlib/mcmc/bb_fixture.pkl +0 -0
  27. gemlib/mcmc/brownian_bridge_kernel.py +291 -0
  28. gemlib/mcmc/brownian_bridge_kernel_test.py +164 -0
  29. gemlib/mcmc/chain_binomial_rippler.py +524 -0
  30. gemlib/mcmc/chain_binomial_rippler_test.py +120 -0
  31. gemlib/mcmc/compound_kernel.py +156 -0
  32. gemlib/mcmc/conftest.py +4 -0
  33. gemlib/mcmc/damped_chain_binomial_rippler.py +840 -0
  34. gemlib/mcmc/discrete_time_state_transition_model/__init__.py +21 -0
  35. gemlib/mcmc/discrete_time_state_transition_model/event_time_proposal.py +275 -0
  36. gemlib/mcmc/discrete_time_state_transition_model/fixtures.py +56 -0
  37. gemlib/mcmc/discrete_time_state_transition_model/left_censored_events_mh.py +258 -0
  38. gemlib/mcmc/discrete_time_state_transition_model/left_censored_events_mh_test.py +108 -0
  39. gemlib/mcmc/discrete_time_state_transition_model/left_censored_events_proposal.py +188 -0
  40. gemlib/mcmc/discrete_time_state_transition_model/left_censored_events_proposal_test.py +33 -0
  41. gemlib/mcmc/discrete_time_state_transition_model/move_events.py +239 -0
  42. gemlib/mcmc/discrete_time_state_transition_model/move_events_test.py +63 -0
  43. gemlib/mcmc/discrete_time_state_transition_model/right_censored_events_mh.py +254 -0
  44. gemlib/mcmc/discrete_time_state_transition_model/right_censored_events_mh_test.py +28 -0
  45. gemlib/mcmc/discrete_time_state_transition_model/right_censored_events_proposal.py +150 -0
  46. gemlib/mcmc/discrete_time_state_transition_model/util.py +9 -0
  47. gemlib/mcmc/experimental/__init__.py +0 -0
  48. gemlib/mcmc/experimental/composable_kernel.py +270 -0
  49. gemlib/mcmc/experimental/discrete_time_state_transition_model/__init__.py +1 -0
  50. gemlib/mcmc/experimental/discrete_time_state_transition_model/left_censored_events_mh.py +149 -0
  51. gemlib/mcmc/experimental/discrete_time_state_transition_model/move_events.py +71 -0
  52. gemlib/mcmc/experimental/discrete_time_state_transition_model/move_events_test.py +48 -0
  53. gemlib/mcmc/experimental/discrete_time_state_transition_model/right_censored_events_mh.py +89 -0
  54. gemlib/mcmc/experimental/discrete_time_state_transition_model/right_censored_events_mh_test.py +28 -0
  55. gemlib/mcmc/experimental/hmc.py +111 -0
  56. gemlib/mcmc/experimental/hmc_test.py +61 -0
  57. gemlib/mcmc/experimental/mcmc_base.py +33 -0
  58. gemlib/mcmc/experimental/mcmc_sampler.py +94 -0
  59. gemlib/mcmc/experimental/mcmc_sampler_test.py +44 -0
  60. gemlib/mcmc/experimental/multi_scan.py +53 -0
  61. gemlib/mcmc/experimental/multi_scan_test.py +71 -0
  62. gemlib/mcmc/experimental/random_walk_metropolis.py +107 -0
  63. gemlib/mcmc/experimental/random_walk_metropolis_test.py +214 -0
  64. gemlib/mcmc/experimental/test_util.py +49 -0
  65. gemlib/mcmc/gibbs_kernel.py +505 -0
  66. gemlib/mcmc/gibbs_kernel_test.py +212 -0
  67. gemlib/mcmc/h5_posterior.py +77 -0
  68. gemlib/mcmc/multi_scan_kernel.py +59 -0
  69. gemlib/mcmc/zarr_posterior.py +132 -0
  70. gemlib/util.py +117 -0
  71. gemlib/util_test.py +75 -0
  72. gemlib-0.9.2.dist-info/LICENSE +21 -0
  73. gemlib-0.9.2.dist-info/METADATA +19 -0
  74. gemlib-0.9.2.dist-info/RECORD +75 -0
  75. gemlib-0.9.2.dist-info/WHEEL +4 -0
@@ -0,0 +1,524 @@
1
+ """Chain binomial process rippler algorithm"""
2
+
3
+ import warnings
4
+ from collections import namedtuple
5
+
6
+ import tensorflow as tf
7
+ import tensorflow_probability as tfp
8
+ from tensorflow_probability.python.internal import prefer_static, samplers
9
+ from tensorflow_probability.python.mcmc.internal import util as mcmc_util
10
+
11
+ from gemlib.distributions import Hypergeometric, UniformInteger
12
+
13
+ tfd = tfp.distributions
14
+
15
+ __all__ = ["chain_binomial_rippler", "default_initial_ripple"]
16
+
17
+
18
+ def _compute_state(initial_state, events, stoichiometry, closed=False):
19
+ """Computes a state tensor from initial state and event tensor
20
+
21
+ :param initial_state: a tensor of shape [S, M]
22
+ :param events: a tensor of shape [T, R, M]
23
+ :param stoichiometry: a stoichiometry matrix of shape [R, S] describing
24
+ how transitions update the state.
25
+ :param closed: if `True`, return state in close interval [0, T], otherwise
26
+ [0, T)
27
+ :return: a tensor of shape [T, S, M] if `closed=False` or [T+1, S, M] if
28
+ `closed=True`
29
+ describing the state of the
30
+ system for each batch M at time T.
31
+ """
32
+ if isinstance(stoichiometry, tf.Tensor):
33
+ stoichiometry = prefer_static.cast(stoichiometry, dtype=events.dtype)
34
+ else:
35
+ stoichiometry = tf.convert_to_tensor(stoichiometry, dtype=events.dtype)
36
+
37
+ increments = tf.einsum("...trm,rs->...tsm", events, stoichiometry)
38
+
39
+ if closed is False:
40
+ cum_increments = tf.cumsum(increments, axis=-3, exclusive=True)
41
+ else:
42
+ cum_increments = tf.cumsum(increments, axis=-3, exclusive=False)
43
+ cum_increments = tf.concat(
44
+ [tf.zeros_like(cum_increments[..., 0:1, :, :]), cum_increments],
45
+ axis=-2,
46
+ )
47
+ state = cum_increments + tf.expand_dims(initial_state, axis=-3)
48
+ return state
49
+
50
+
51
+ def _log_factorial(x):
52
+ """Computes x!"""
53
+ return tf.math.lgamma(x + 1.0)
54
+
55
+
56
+ def _log_choose(n, k):
57
+ """Computes nCk"""
58
+ return _log_factorial(n) - _log_factorial(k) - _log_factorial(n - k)
59
+
60
+
61
+ # Basic rippler sampling functions
62
+ def _pstep(z, x, p, ps, seed=None, validate_args=False):
63
+ """Compute the p-step of Rippler.
64
+
65
+ Since there are two possible distributions to draw from,
66
+ but both are Binomial, we compute `offset`, `total_count`, and
67
+ `prob` parameters for both branches, and select which we need
68
+ based on p <= ps.
69
+
70
+ :param z: current $z$
71
+ :param x: current $x$
72
+ :param p: current probability
73
+ :param ps: new probability
74
+ """
75
+ with tf.name_scope("_pstep"):
76
+ offset = tf.where(ps <= p, tf.zeros(1, dtype=z.dtype), z)
77
+ prob = tf.where(ps <= p, ps / p, 1 - (1 - ps) / (1 - p))
78
+ total_count = tf.where(
79
+ ps <= p, tf.cast(z, p.dtype), tf.cast(x - z, p.dtype)
80
+ )
81
+ z_prime = offset + tfd.Binomial(
82
+ total_count=total_count,
83
+ probs=prob,
84
+ validate_args=validate_args,
85
+ name="_pstep_Binomial",
86
+ ).sample(seed=seed)
87
+ return z_prime
88
+
89
+
90
+ def _xstep(z_prime, x, xs, ps, seed=None, validate_args=False):
91
+ """Computes the x-step of the Rippler algorithm.
92
+
93
+ Both xs >= x and xs < x are sampled and results selected.
94
+ """
95
+ with tf.name_scope("_xstep"):
96
+ seeds = samplers.split_seed(seed, salt="_xstep")
97
+
98
+ # xs >= x
99
+ # Switch off `validate_args` because `xs-x` may be -ve.
100
+ z_new_geq = z_prime + tfd.Binomial(
101
+ xs - x,
102
+ probs=ps,
103
+ validate_args=False,
104
+ name="_xstep_Binomial",
105
+ ).sample(seed=seeds[0])
106
+
107
+ # xs < x - explicitly vectorize
108
+ def safe_hypergeom(N, K, n): # noqa: N803
109
+ # xs is clipped to min(x, xs) to avoid errors in the Hypergeometric
110
+ # sampler these values won't be selected anyway due to the
111
+ # xs >= x condition below.
112
+ return Hypergeometric(
113
+ N=N,
114
+ K=K,
115
+ n=tf.math.minimum(N, n),
116
+ validate_args=False,
117
+ name="_xstep_Hypergeom",
118
+ )
119
+
120
+ z_new_lt = safe_hypergeom(x, z_prime, xs).sample(seed=seeds[1])
121
+
122
+ return tf.where(xs >= x, z_new_geq, z_new_lt)
123
+
124
+
125
+ def _dispatch_update(z, x, p, xs, ps, seed=None, validate_args=False):
126
+ r"""Dispatches update function based on values of
127
+ parameters.
128
+
129
+ :param z: current $z$
130
+ :param x: current $x$
131
+ :param p: $p$ current probability
132
+ :param xs: $x^\star$ new state
133
+ :param ps: $p_star$ new probability
134
+
135
+ :returns: an updated number of events
136
+ """
137
+ with tf.name_scope("dispatch_update"):
138
+ p = tf.convert_to_tensor(p)
139
+ ps = tf.convert_to_tensor(ps)
140
+ z = tf.cast(z, p.dtype)
141
+ x = tf.cast(x, p.dtype)
142
+ xs = tf.cast(xs, p.dtype)
143
+
144
+ seeds = samplers.split_seed(seed, salt="_dispatch_update")
145
+
146
+ z_prime = _pstep(z, x, p, ps, seed=seeds[0])
147
+ z_new = _xstep(
148
+ z_prime, x, xs, ps, seed=seeds[1], validate_args=validate_args
149
+ )
150
+
151
+ return z_new
152
+
153
+
154
+ # Tests
155
+ def test_dispatch():
156
+ tf.debugging.assert_scalar(
157
+ _dispatch_update(z=10, x=100, p=0.1, xs=100, ps=0.1)
158
+ )
159
+ tf.debugging.assert_scalar(
160
+ _dispatch_update(z=10, x=100, p=0.1, xs=100, ps=0.05)
161
+ )
162
+ tf.debugging.assert_scalar(
163
+ _dispatch_update(z=10, x=100, p=0.1, xs=100, ps=0.2)
164
+ )
165
+ tf.debugging.assert_scalar(
166
+ _dispatch_update(z=10, x=100, p=0.1, xs=50, ps=0.1)
167
+ )
168
+ tf.debugging.assert_scalar(
169
+ _dispatch_update(z=10, x=100, p=0.1, xs=50, ps=0.05)
170
+ )
171
+ tf.debugging.assert_scalar(
172
+ _dispatch_update(z=10, x=100, p=0.1, xs=50, ps=0.2)
173
+ )
174
+ tf.debugging.assert_scalar(
175
+ _dispatch_update(z=10, x=100, p=0.1, xs=200, ps=0.1)
176
+ )
177
+ tf.debugging.assert_scalar(
178
+ _dispatch_update(z=10, x=100, p=0.1, xs=200, ps=0.05)
179
+ )
180
+ tf.debugging.assert_scalar(
181
+ _dispatch_update(z=10, x=100, p=0.1, xs=200, ps=0.2)
182
+ )
183
+
184
+
185
+ def default_initial_ripple(model, current_events, current_state, seed):
186
+ """Produces the initial ripple.
187
+
188
+ :param model: an instance of `DiscreteTimeStateTransitionModel`
189
+ :param current_events: a tensor of events in [T, R, M] order
190
+ :param current_state: a tensor of state in [T, S, M] order
191
+ :param seed: the seed to initialise the ripple
192
+
193
+ :returns: a tuple of `(proposed_time_idx, new_events_t, current_state_t)`
194
+ """
195
+ init_time_seed, init_pop_seed, init_events_seed = samplers.split_seed(
196
+ seed, n=3, salt="_initial_ripple"
197
+ )
198
+
199
+ # Choose timepoint, t
200
+ proposed_time_idx = UniformInteger(low=0, high=model.num_steps).sample(
201
+ seed=init_time_seed
202
+ )
203
+ current_state_t = tf.gather(current_state, proposed_time_idx, axis=-3)
204
+
205
+ # Choose subpopulation - KCategorical?
206
+ proposed_pop_idx = UniformInteger(
207
+ low=0, high=current_events.shape[-1]
208
+ ).sample(seed=init_pop_seed)
209
+
210
+ # Choose new infection events at time t
211
+ proposed_transition_rates = tf.stack(
212
+ model.transition_rates(
213
+ proposed_time_idx, tf.transpose(current_state_t)
214
+ ),
215
+ axis=0,
216
+ )
217
+ prob_t = 1.0 - tf.math.exp(
218
+ -tf.gather(proposed_transition_rates[0], proposed_pop_idx, axis=-1)
219
+ * model.time_delta,
220
+ ) # First event to perturb.
221
+
222
+ required_state = tf.gather(current_state_t[0], proposed_pop_idx, axis=-1)
223
+ new_si_events_t = tfd.Binomial(
224
+ total_count=required_state,
225
+ probs=prob_t, # Perturb SI events here
226
+ ).sample(seed=init_events_seed)
227
+
228
+ new_events_t = tf.tensor_scatter_nd_update(
229
+ current_events[proposed_time_idx],
230
+ [[0, proposed_pop_idx]],
231
+ [new_si_events_t],
232
+ )
233
+
234
+ return proposed_time_idx, new_events_t, current_state_t
235
+
236
+
237
+ def chain_binomial_rippler(model, current_events, initial_ripple_fn, seed=None):
238
+ init_seed, ripple_seed = samplers.split_seed(
239
+ seed, salt="chain_binomial_rippler"
240
+ )
241
+
242
+ src_states = model.source_states
243
+ # Transpose to [T, S/R, M]
244
+ current_events = tf.transpose(current_events, perm=(1, 2, 0))
245
+
246
+ # Calculate current state
247
+ current_state = _compute_state(
248
+ initial_state=tf.transpose(model.initial_state),
249
+ events=current_events,
250
+ stoichiometry=tf.transpose(model.incidence_matrix),
251
+ )
252
+
253
+ # Begin the ripple by sampling a time point, and perturbing the
254
+ # events at that timepoint
255
+ (
256
+ proposed_time_idx,
257
+ new_events_t,
258
+ current_state_t,
259
+ ) = initial_ripple_fn(model, current_events, current_state, init_seed)
260
+ new_events = tf.tensor_scatter_nd_update(
261
+ current_events, indices=[[proposed_time_idx]], updates=[new_events_t]
262
+ )
263
+
264
+ # Propagate from t+1 up to end of the timeseries
265
+ def draw_events(time, new_state_t, current_events_t, current_state_t, seed):
266
+ with tf.name_scope("draw_events"):
267
+ # Calculate transition rates for current and new states
268
+ def transition_probs(time, state):
269
+ rates = tf.stack(
270
+ model.transition_rates(time, tf.transpose(state)), axis=-2
271
+ )
272
+ return 1.0 - tf.math.exp(-rates * model.time_delta)
273
+
274
+ current_p = transition_probs(time, current_state_t)
275
+ new_p = transition_probs(time, new_state_t)
276
+
277
+ new_events = _dispatch_update(
278
+ z=current_events_t,
279
+ x=prefer_static.gather(current_state_t, indices=src_states),
280
+ p=current_p,
281
+ xs=prefer_static.gather(new_state_t, indices=src_states),
282
+ ps=new_p,
283
+ seed=seed,
284
+ )
285
+ tf.debugging.assert_non_negative(new_events)
286
+
287
+ return new_events
288
+
289
+ def time_loop_body(t, new_events_t, new_state_t, new_events_buffer, seed):
290
+ sample_seed, next_seed = samplers.split_seed(
291
+ seed, salt="time_loop_body"
292
+ )
293
+
294
+ # Propagate new_state[t] to new_state[t+1]
295
+ new_state_t1 = new_state_t + tf.einsum(
296
+ "...ik,ji->...jk", new_events_t, model.incidence_matrix
297
+ )
298
+ # tf.debugging.assert_non_negative(new_state_t1, summarize=100)
299
+
300
+ # Gather current states and events, and draw new events
301
+ new_events_t1 = draw_events(
302
+ t + 1,
303
+ new_state_t1,
304
+ current_events[t + 1],
305
+ current_state[t + 1],
306
+ sample_seed,
307
+ )
308
+
309
+ # Update new_events_buffer
310
+ new_events_buffer = tf.tensor_scatter_nd_update(
311
+ new_events_buffer, indices=[[t + 1]], updates=[new_events_t1]
312
+ )
313
+
314
+ return t + 1, new_events_t1, new_state_t1, new_events_buffer, next_seed
315
+
316
+ def time_loop_cond(t, _1, _2, new_events_buffer, _3):
317
+ t_stop = t < (model.num_steps - 1)
318
+ delta_stop = tf.reduce_any(new_events_buffer != current_events)
319
+ return t_stop & delta_stop
320
+
321
+ _, _, _, new_events, _ = tf.while_loop(
322
+ time_loop_cond,
323
+ time_loop_body,
324
+ loop_vars=(
325
+ proposed_time_idx,
326
+ new_events_t,
327
+ current_state_t,
328
+ new_events,
329
+ ripple_seed,
330
+ ),
331
+ ) # new_events.shape = [T, R, M]
332
+
333
+ new_events = tf.transpose(new_events, perm=(2, 0, 1))
334
+
335
+ return (
336
+ new_events,
337
+ {
338
+ "delta": tf.transpose(
339
+ new_events_t - current_events[proposed_time_idx]
340
+ ),
341
+ "timepoint": proposed_time_idx,
342
+ "initial_ripple": new_events_t,
343
+ "current_state_t": tf.transpose(current_state_t),
344
+ },
345
+ )
346
+
347
+
348
+ # The Chain Binomial Rippler kernel
349
+ CBRResults = namedtuple(
350
+ "CBRResults",
351
+ [
352
+ "target_log_prob",
353
+ "is_accepted",
354
+ "delta",
355
+ "current_state_t",
356
+ "initial_ripple",
357
+ "timepoint",
358
+ "proposed_state",
359
+ "proposed_target_log_prob",
360
+ "seed",
361
+ ],
362
+ )
363
+
364
+
365
+ class CBRKernel(tfp.mcmc.TransitionKernel):
366
+ def __init__(
367
+ self,
368
+ target_log_prob_fn,
369
+ model,
370
+ initial_ripple_fn=default_initial_ripple,
371
+ ripple_decay_rate=0.1,
372
+ name=None,
373
+ ):
374
+ self._target_log_prob_fn = target_log_prob_fn
375
+ self._model = model
376
+
377
+ name = mcmc_util.make_name(name, "CBRKernel", "")
378
+
379
+ self._parameters = {
380
+ "target_log_prob_fn": target_log_prob_fn,
381
+ "model": model,
382
+ "initial_ripple_fn": initial_ripple_fn,
383
+ "ripple_decay_rate": ripple_decay_rate,
384
+ "name": name,
385
+ }
386
+
387
+ @property
388
+ def is_calibrated(self):
389
+ return True
390
+
391
+ @property
392
+ def target_log_prob(self):
393
+ return self._target_log_prob_fn
394
+
395
+ @property
396
+ def model(self):
397
+ return self._model
398
+
399
+ @property
400
+ def name(self):
401
+ return self._parameters["name"]
402
+
403
+ @property
404
+ def initial_ripple_fn(self):
405
+ return self._parameters["initial_ripple_fn"]
406
+
407
+ def one_step(self, current_state, previous_results, seed=None):
408
+ with tf.name_scope("CBRKernel/one_step"):
409
+ seed_rippler, seed_u, seed_results = samplers.split_seed(
410
+ seed, n=3, salt="cbr_kernel"
411
+ )
412
+
413
+ if mcmc_util.is_list_like(current_state):
414
+ current_state_parts = list(current_state)
415
+ else:
416
+ current_state_parts = [current_state]
417
+
418
+ if len(current_state_parts) > 1:
419
+ warnings.warn(
420
+ "CBRKernel.boostrap_results: multiple state parts detected,\
421
+ but only the first will be used",
422
+ stacklevel=2,
423
+ )
424
+
425
+ current_state_part = tf.convert_to_tensor(
426
+ current_state_parts[0], name="current_state"
427
+ )
428
+
429
+ proposed_state, proposal_trace = chain_binomial_rippler(
430
+ self.model,
431
+ current_state_part,
432
+ initial_ripple_fn=self.initial_ripple_fn,
433
+ seed=seed_rippler,
434
+ )
435
+
436
+ proposed_target_log_prob = self.target_log_prob(proposed_state)
437
+
438
+ delta_logp = (
439
+ proposed_target_log_prob - previous_results.target_log_prob
440
+ )
441
+
442
+ def accept():
443
+ return (
444
+ proposed_state,
445
+ CBRResults(
446
+ target_log_prob=proposed_target_log_prob,
447
+ is_accepted=tf.constant(True),
448
+ delta=proposal_trace["delta"],
449
+ current_state_t=proposal_trace["current_state_t"],
450
+ initial_ripple=proposal_trace["initial_ripple"],
451
+ timepoint=proposal_trace["timepoint"],
452
+ proposed_state=proposed_state,
453
+ proposed_target_log_prob=proposed_target_log_prob,
454
+ seed=seed_results,
455
+ ),
456
+ )
457
+
458
+ def reject():
459
+ return (
460
+ current_state_part,
461
+ CBRResults(
462
+ target_log_prob=previous_results.target_log_prob,
463
+ is_accepted=tf.constant(False),
464
+ delta=proposal_trace["delta"],
465
+ current_state_t=proposal_trace["current_state_t"],
466
+ initial_ripple=proposal_trace["initial_ripple"],
467
+ timepoint=proposal_trace["timepoint"],
468
+ proposed_state=proposed_state,
469
+ proposed_target_log_prob=proposed_target_log_prob,
470
+ seed=seed_results,
471
+ ),
472
+ )
473
+
474
+ u = tf.math.log(
475
+ tfd.Uniform(low=tf.zeros(1, dtype=delta_logp.dtype)).sample(
476
+ seed=seed_u
477
+ )
478
+ )
479
+ new_state, results = tf.cond(u < delta_logp, accept, reject)
480
+
481
+ def maybe_flatten(x):
482
+ if mcmc_util.is_list_like(current_state):
483
+ return type(current_state)(new_state)
484
+ return x
485
+
486
+ new_state = maybe_flatten(new_state)
487
+ return new_state, results
488
+
489
+ def bootstrap_results(self, current_state):
490
+ with tf.name_scope("CBRKernel/bootstrap_results"):
491
+ if mcmc_util.is_list_like(current_state):
492
+ current_state_parts = list(current_state)
493
+ else:
494
+ current_state_parts = [current_state]
495
+
496
+ if len(current_state_parts) > 1:
497
+ warnings.warn(
498
+ "CBRKernel.boostrap_results: multiple state parts detected,\
499
+ but only the first will be used",
500
+ stacklevel=2,
501
+ )
502
+ state_part = current_state_parts[0]
503
+
504
+ num_pop = state_part.shape[-3]
505
+ num_transitions = state_part.shape[-1]
506
+ num_states = self.model.incidence_matrix.shape[-2]
507
+
508
+ target_log_prob = self.target_log_prob(state_part)
509
+
510
+ return CBRResults(
511
+ target_log_prob=target_log_prob,
512
+ is_accepted=tf.constant(False),
513
+ delta=tf.zeros((num_pop, num_transitions), state_part.dtype),
514
+ current_state_t=tf.zeros(
515
+ [num_pop, num_states], state_part.dtype
516
+ ),
517
+ initial_ripple=tf.zeros(
518
+ (num_transitions, num_pop), state_part.dtype
519
+ ),
520
+ timepoint=tf.constant(0, dtype=tf.int32),
521
+ proposed_state=state_part,
522
+ proposed_target_log_prob=target_log_prob,
523
+ seed=samplers.zeros_seed(),
524
+ )
@@ -0,0 +1,120 @@
1
+ """Test for Chain Binomial Rippler Kernel"""
2
+
3
+ import numpy as np
4
+ import tensorflow as tf
5
+ import tensorflow_probability as tfp
6
+ from tensorflow_probability.python.internal import test_util
7
+
8
+ from gemlib.distributions import DiscreteTimeStateTransitionModel
9
+ from gemlib.mcmc import CBRKernel
10
+
11
+ tfd = tfp.distributions
12
+
13
+ MIN_EVENTS = 10
14
+
15
+
16
+ def _make_model():
17
+ incidence_matrix = np.array([[-1, 1, 0], [0, -1, 1]], dtype=np.float32)
18
+ init_state = np.array(
19
+ [[999.0, 1.0, 0.0], [1000.0, 0.0, 0.0]], dtype=np.float32
20
+ )
21
+
22
+ contact_matrix = np.array([[0, 1], [1, 0]], dtype=np.float32)
23
+
24
+ init_state = tf.convert_to_tensor(init_state)
25
+ contact_matrix = tf.convert_to_tensor(contact_matrix)
26
+
27
+ def hazard_fn(t, state):
28
+ si = (
29
+ 0.3 * state[..., 1]
30
+ + 0.1 * tf.linalg.matvec(contact_matrix, state[..., 1])
31
+ ) / tf.reduce_sum(state, axis=-1)
32
+ ir = tf.broadcast_to(tf.constant([0.14]), si.shape)
33
+ return [si, ir]
34
+
35
+ model = DiscreteTimeStateTransitionModel(
36
+ transition_rates=hazard_fn,
37
+ initial_state=init_state,
38
+ initial_step=0,
39
+ time_delta=1.0,
40
+ num_steps=70,
41
+ stoichiometry=incidence_matrix,
42
+ )
43
+
44
+ return model
45
+
46
+
47
+ @test_util.test_all_tf_execution_regimes
48
+ class CBRSIRTest(test_util.TestCase):
49
+ def setUp(self):
50
+ self.incidence_matrix = np.array(
51
+ [[-1, 0], [1, -1], [0, 1]], dtype=np.float32
52
+ )
53
+
54
+ self.init_state = np.array(
55
+ [[999.0, 1.0, 0.0], [1000.0, 0.0, 0.0]], dtype=np.float32
56
+ )
57
+
58
+ self.C = np.array([[0, 1], [1, 0]], dtype=np.float32)
59
+
60
+ model = self._make_model(self.incidence_matrix, self.init_state, self.C)
61
+
62
+ while True:
63
+ events = self.evaluate(model.sample())
64
+ if tf.reduce_sum(events[..., 1]) > MIN_EVENTS:
65
+ break
66
+
67
+ observation_process = tfd.Independent(
68
+ distribution=tfd.Binomial(total_count=events[..., 1], probs=0.5),
69
+ reinterpreted_batch_ndims=1,
70
+ )
71
+
72
+ self.sir = events
73
+ self.observed_cases = self.evaluate(
74
+ observation_process.sample(seed=[0, 0])
75
+ )
76
+
77
+ def _make_model(self, incidence_matrix, init_state, contact_matrix):
78
+ init_state = tf.convert_to_tensor(init_state)
79
+ contact_matrix = tf.convert_to_tensor(contact_matrix)
80
+
81
+ def hazard_fn(t, state):
82
+ si = (
83
+ 0.3 * state[..., 1]
84
+ + 0.1 * tf.linalg.matvec(contact_matrix, state[..., 1])
85
+ ) / tf.reduce_sum(state, axis=-1)
86
+ ir = tf.broadcast_to(tf.constant([0.14]), si.shape)
87
+ return [si, ir]
88
+
89
+ model = DiscreteTimeStateTransitionModel(
90
+ transition_rates=hazard_fn,
91
+ initial_state=init_state,
92
+ initial_step=0,
93
+ time_delta=1.0,
94
+ num_steps=70,
95
+ incidence_matrix=incidence_matrix,
96
+ )
97
+
98
+ return model
99
+
100
+ def test_cbr_kernel(self):
101
+ model = self._make_model(
102
+ self.incidence_matrix,
103
+ self.init_state,
104
+ self.C,
105
+ )
106
+
107
+ def tlp_fn(current_events):
108
+ observation_process = tfd.Binomial(
109
+ total_count=tf.gather(current_events, indices=1, axis=-1),
110
+ probs=0.5,
111
+ )
112
+ return tf.reduce_sum(
113
+ observation_process.log_prob(self.observed_cases)
114
+ )
115
+
116
+ kernel = CBRKernel(tlp_fn, model=model)
117
+ pkr = self.evaluate(
118
+ kernel.bootstrap_results(tf.convert_to_tensor(self.sir))
119
+ )
120
+ samples, results = kernel.one_step(self.sir, pkr)