use rand::rngs::SmallRng;
use rand::{Rng, RngExt, SeedableRng};
use rayon::prelude::*;
use std::f32::consts::PI;
use super::chain::McmcChain;
use super::runner::keep_flags;
use super::traits::EssParam;
pub(crate) const MAX_BRACKET_ITERS: usize = 64;
pub(crate) const BRACKET_MIN_WIDTH: f32 = 1e-6;
pub fn elliptical_slice_step<P: EssParam>(
current: &P,
prior_sample: &P,
lnpdf: &impl Fn(&P) -> f32,
cur_lnpdf: f32,
rng: &mut impl Rng,
) -> (P, f32) {
let u: f32 = rng.random();
let hh = u.ln() + cur_lnpdf;
let phi: f32 = rng.random_range(0.0..2.0 * PI);
let mut phi_min = phi - 2.0 * PI;
let mut phi_max = phi;
let mut angle = phi;
for _ in 0..MAX_BRACKET_ITERS {
let proposal = current.linear_combine(angle.cos(), prior_sample, angle.sin());
let new_lnpdf = lnpdf(&proposal);
if new_lnpdf > hh {
return (proposal, new_lnpdf);
}
if angle < 0.0 {
phi_min = angle;
} else {
phi_max = angle;
}
if phi_max - phi_min < BRACKET_MIN_WIDTH {
break;
}
angle = rng.random_range(phi_min..phi_max);
}
(current.clone(), cur_lnpdf)
}
#[derive(Clone, Debug)]
pub struct EssSampler {
pub n_samples: usize,
pub warmup: usize,
pub thin: usize,
pub seed: u64,
}
impl EssSampler {
pub fn new(n_samples: usize, warmup: usize) -> Self {
Self {
n_samples,
warmup,
thin: 1,
seed: 42,
}
}
pub fn run<P: EssParam>(
&self,
lnpdf: &impl Fn(&P) -> f32,
prior_draw: &impl Fn(&mut SmallRng) -> P,
init: &P,
) -> McmcChain<P> {
let mut rng = SmallRng::seed_from_u64(self.seed);
let mut current = init.clone();
let mut cur_lnpdf = lnpdf(¤t);
let mut samples = Vec::with_capacity(self.n_samples);
let mut log_likelihoods = Vec::with_capacity(self.n_samples);
for keep in keep_flags(self.warmup, self.n_samples, self.thin) {
let nu = prior_draw(&mut rng);
let (new, new_ll) = elliptical_slice_step(¤t, &nu, lnpdf, cur_lnpdf, &mut rng);
current = new;
cur_lnpdf = new_ll;
if keep {
samples.push(current.clone());
log_likelihoods.push(cur_lnpdf);
}
}
McmcChain {
samples,
log_likelihoods,
}
}
pub fn run_parallel<P: EssParam + Send + Sync>(
&self,
n_chains: usize,
lnpdf: &(impl Fn(&P) -> f32 + Sync),
prior_draw: &(impl Fn(&mut SmallRng) -> P + Sync),
init: &P,
) -> Vec<McmcChain<P>> {
(0..n_chains)
.into_par_iter()
.map(|i| {
let sampler = EssSampler {
seed: self.seed.wrapping_add(i as u64),
..self.clone()
};
sampler.run(lnpdf, prior_draw, init)
})
.collect()
}
}
#[cfg(test)]
#[path = "elliptical_slice_tests.rs"]
mod elliptical_slice_tests;