use crate::{
ConditionDifferentiableDistribution, ContinuousUniform, Distribution, DistributionError,
RandomVariable, SampleableDistribution, ValueDifferentiableDistribution,
};
use rand::prelude::*;
pub struct HamiltonianSampler<'a, L, P, A, B>
where
L: Distribution<Value = A, Condition = B> + ConditionDifferentiableDistribution,
P: Distribution<Value = B, Condition = ()> + ValueDifferentiableDistribution,
A: RandomVariable,
B: RandomVariable,
{
value: &'a A,
likelihood: &'a L,
prior: &'a P,
}
#[derive(thiserror::Error, Debug)]
pub enum HamiltonianSamplingError {
#[error("out of range")]
OutOfRange,
#[error("Unknown error")]
Unknown,
}
impl<'a, L, P, A, B> HamiltonianSampler<'a, L, P, A, B>
where
L: Distribution<Value = A, Condition = B> + ConditionDifferentiableDistribution,
P: Distribution<Value = B, Condition = ()> + ValueDifferentiableDistribution,
A: RandomVariable,
B: RandomVariable,
{
pub fn new(value: &'a A, likelihood: &'a L, prior: &'a P) -> Self {
Self {
value,
likelihood,
prior,
}
}
pub fn sample(&self, rng: &mut dyn RngCore) -> Result<B, DistributionError> {
todo!()
}
}
fn hamiltonian(p: f64, theta: f64) -> Result<f64, DistributionError> {
let lambda = 1.0;
let alpha = 2.0;
Ok(lambda * theta - (alpha - 1.0) * theta.ln() + 0.5 * p.powf(2.0))
}
fn leapfrog_next_half_p(p: f64, theta: f64) -> Result<f64, DistributionError> {
let lambda = 1.0;
let alpha = 2.0;
let eps = 0.01;
Ok(p - 0.5 * eps * (lambda - (alpha - 1.0) / theta))
}
fn leapfrog_next_theta(p: f64, theta: f64) -> Result<f64, DistributionError> {
let eps = 0.01;
Ok(theta + eps * p)
}
fn sample(
theta: f64, rng: &mut dyn RngCore,
) -> Result<Vec<(f64, f64)>, DistributionError> {
const L: usize = 100;
const T: usize = 10000;
let mut kinetic_p = 0.0;
let mut kinetic_theta = theta;
let mut sim_result = vec![];
let mut sim_result_hamiltonian = vec![];
let prev_hamiltonian = hamiltonian(kinetic_p, theta)?;
println!("{}", prev_hamiltonian);
sim_result_hamiltonian.append(&mut vec![prev_hamiltonian]);
for _t in 0..T {
for _i in 0..L {
kinetic_p = leapfrog_next_half_p(kinetic_p, kinetic_theta)?;
kinetic_theta = leapfrog_next_theta(kinetic_p, kinetic_theta)?;
kinetic_p = leapfrog_next_half_p(kinetic_p, kinetic_theta)?;
}
let hamiltonian = hamiltonian(kinetic_p, kinetic_theta)?;
let r = (prev_hamiltonian - hamiltonian).exp();
if r > 1.0 {
sim_result.append(&mut vec![(kinetic_p, kinetic_theta)]);
sim_result_hamiltonian.append(&mut vec![prev_hamiltonian]);
} else if r > 0.0 && r > ContinuousUniform.sample(&(0.0..1.0), rng)? {
sim_result.append(&mut vec![(kinetic_p, kinetic_theta)]);
sim_result_hamiltonian.append(&mut vec![prev_hamiltonian]);
}
}
Ok(sim_result)
}
#[cfg(test)]
mod test {
use crate::hamiltonian::sample;
use plotters::prelude::*;
use rand::{prelude::StdRng, SeedableRng};
#[test]
fn it_works() -> Result<(), Box<dyn std::error::Error>> {
let theta = 2.5;
let mut rng = StdRng::from_seed([1; 32]);
let hamiltonian = sample(theta, &mut rng).unwrap();
let xs: Vec<f64> = hamiltonian.iter().map(|(x, _)| *x).collect();
let ys: Vec<f64> = hamiltonian.iter().map(|(_, y)| *y).collect();
let image_width = 1080;
let image_height = 720;
let root =
BitMapBackend::new("hamiltonian.png", (image_width, image_height)).into_drawing_area();
root.fill(&WHITE)?;
let (x_min, x_max) = xs
.iter()
.fold((0.0 / 0.0, 0.0 / 0.0), |(m, n), v| (v.min(m), v.max(n)));
let (y_min, y_max) = ys
.iter()
.fold((0.0 / 0.0, 0.0 / 0.0), |(m, n), v| (v.min(m), v.max(n)));
let caption = "Hamiltonian Sampling";
let font = ("sans-serif", 20);
let mut chart = ChartBuilder::on(&root)
.caption(caption, font.into_font())
.margin(10)
.x_label_area_size(16)
.y_label_area_size(42)
.build_cartesian_2d(x_min..x_max, y_min..y_max)?;
chart.configure_mesh().draw()?;
let line_series = LineSeries::new(xs.iter().zip(ys.iter()).map(|(x, y)| (*x, *y)), &RED);
chart.draw_series(line_series)?;
Ok(())
}
}