use rand::rngs::SmallRng;
use rand::SeedableRng;
use rayon::prelude::*;
use super::model::McmcModel;
#[derive(Clone, Debug)]
pub struct McmcConfig {
pub n_samples: usize,
pub warmup: usize,
pub thin: usize,
pub seed: u64,
}
pub(super) fn keep_flags(
warmup: usize,
n_samples: usize,
thin: usize,
) -> impl Iterator<Item = bool> {
let thin = thin.max(1);
let total = match n_samples {
0 => Some(warmup),
n => (n - 1)
.checked_mul(thin)
.and_then(|t| t.checked_add(warmup + 1)),
}
.expect("warmup + n_samples * thin overflows usize");
(0..total).map(move |i| i >= warmup && (i - warmup).is_multiple_of(thin))
}
pub fn run_mcmc<M: McmcModel>(model: &M, config: &McmcConfig) -> M::Result {
let mut rng = SmallRng::seed_from_u64(config.seed);
let mut state = model.init(&mut rng);
let mut samples = Vec::with_capacity(config.n_samples);
for keep in keep_flags(config.warmup, config.n_samples, config.thin) {
model.sweep(&mut state, &mut rng);
if keep {
samples.push(model.collect(&state));
}
}
model.summarize(samples)
}
pub fn run_mcmc_parallel<M: McmcModel + Sync>(
model: &M,
config: &McmcConfig,
n_chains: usize,
) -> Vec<M::Result>
where
M::State: Send,
M::Result: Send,
{
(0..n_chains)
.into_par_iter()
.map(|i| {
let chain_config = McmcConfig {
seed: config.seed.wrapping_add(i as u64),
..config.clone()
};
run_mcmc(model, &chain_config)
})
.collect()
}
#[cfg(test)]
mod tests {
use super::keep_flags;
#[test]
fn keeps_every_thin_th_draw_after_warmup() {
let kept: Vec<bool> = keep_flags(2, 3, 2).collect();
let (f, t) = (false, true);
assert_eq!(kept, [f, f, t, f, t, f, t]);
}
#[test]
fn a_zero_thin_is_read_as_one() {
assert!(keep_flags(3, 4, 0).eq(keep_flags(3, 4, 1)));
assert_eq!(keep_flags(3, 4, 0).filter(|&k| k).count(), 4);
}
#[test]
#[should_panic(expected = "overflows usize")]
fn an_overflowing_schedule_panics_rather_than_wrapping() {
let _ = keep_flags(0, usize::MAX, 2);
}
}