#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/docs/core/model.md"))]
use crate::core::address::Address;
use crate::core::distribution::{Distribution, LogF64};
pub enum Model<A> {
Pure(A),
SampleF64 {
addr: Address,
dist: Box<dyn Distribution<f64>>,
k: Box<dyn FnOnce(f64) -> Model<A> + Send + 'static>,
},
SampleBool {
addr: Address,
dist: Box<dyn Distribution<bool>>,
k: Box<dyn FnOnce(bool) -> Model<A> + Send + 'static>,
},
SampleU64 {
addr: Address,
dist: Box<dyn Distribution<u64>>,
k: Box<dyn FnOnce(u64) -> Model<A> + Send + 'static>,
},
SampleUsize {
addr: Address,
dist: Box<dyn Distribution<usize>>,
k: Box<dyn FnOnce(usize) -> Model<A> + Send + 'static>,
},
SampleI64 {
addr: Address,
dist: Box<dyn Distribution<i64>>,
k: Box<dyn FnOnce(i64) -> Model<A> + Send + 'static>,
},
ObserveF64 {
addr: Address,
dist: Box<dyn Distribution<f64>>,
value: f64,
k: Box<dyn FnOnce(()) -> Model<A> + Send + 'static>,
},
ObserveBool {
addr: Address,
dist: Box<dyn Distribution<bool>>,
value: bool,
k: Box<dyn FnOnce(()) -> Model<A> + Send + 'static>,
},
ObserveU64 {
addr: Address,
dist: Box<dyn Distribution<u64>>,
value: u64,
k: Box<dyn FnOnce(()) -> Model<A> + Send + 'static>,
},
ObserveUsize {
addr: Address,
dist: Box<dyn Distribution<usize>>,
value: usize,
k: Box<dyn FnOnce(()) -> Model<A> + Send + 'static>,
},
ObserveI64 {
addr: Address,
dist: Box<dyn Distribution<i64>>,
value: i64,
k: Box<dyn FnOnce(()) -> Model<A> + Send + 'static>,
},
Factor {
logw: LogF64,
k: Box<dyn FnOnce(()) -> Model<A> + Send + 'static>,
},
}
pub fn pure<A>(a: A) -> Model<A> {
Model::Pure(a)
}
pub fn sample_f64(addr: Address, dist: impl Distribution<f64> + 'static) -> Model<f64> {
Model::SampleF64 {
addr,
dist: Box::new(dist),
k: Box::new(pure),
}
}
pub fn sample_bool(addr: Address, dist: impl Distribution<bool> + 'static) -> Model<bool> {
Model::SampleBool {
addr,
dist: Box::new(dist),
k: Box::new(pure),
}
}
pub fn sample_u64(addr: Address, dist: impl Distribution<u64> + 'static) -> Model<u64> {
Model::SampleU64 {
addr,
dist: Box::new(dist),
k: Box::new(pure),
}
}
pub fn sample_usize(addr: Address, dist: impl Distribution<usize> + 'static) -> Model<usize> {
Model::SampleUsize {
addr,
dist: Box::new(dist),
k: Box::new(pure),
}
}
pub fn sample_i64(addr: Address, dist: impl Distribution<i64> + 'static) -> Model<i64> {
Model::SampleI64 {
addr,
dist: Box::new(dist),
k: Box::new(pure),
}
}
pub fn sample<T>(addr: Address, dist: impl Distribution<T> + 'static) -> Model<T>
where
T: SampleType,
{
T::make_sample_model(addr, Box::new(dist))
}
pub trait SampleType: 'static + Send + Sync + Sized {
fn make_sample_model(addr: Address, dist: Box<dyn Distribution<Self>>) -> Model<Self>;
fn make_observe_model(
addr: Address,
dist: Box<dyn Distribution<Self>>,
value: Self,
) -> Model<()>;
}
impl SampleType for f64 {
fn make_sample_model(addr: Address, dist: Box<dyn Distribution<f64>>) -> Model<f64> {
Model::SampleF64 {
addr,
dist,
k: Box::new(pure),
}
}
fn make_observe_model(
addr: Address,
dist: Box<dyn Distribution<f64>>,
value: f64,
) -> Model<()> {
Model::ObserveF64 {
addr,
dist,
value,
k: Box::new(pure),
}
}
}
impl SampleType for bool {
fn make_sample_model(addr: Address, dist: Box<dyn Distribution<bool>>) -> Model<bool> {
Model::SampleBool {
addr,
dist,
k: Box::new(pure),
}
}
fn make_observe_model(
addr: Address,
dist: Box<dyn Distribution<bool>>,
value: bool,
) -> Model<()> {
Model::ObserveBool {
addr,
dist,
value,
k: Box::new(pure),
}
}
}
impl SampleType for u64 {
fn make_sample_model(addr: Address, dist: Box<dyn Distribution<u64>>) -> Model<u64> {
Model::SampleU64 {
addr,
dist,
k: Box::new(pure),
}
}
fn make_observe_model(
addr: Address,
dist: Box<dyn Distribution<u64>>,
value: u64,
) -> Model<()> {
Model::ObserveU64 {
addr,
dist,
value,
k: Box::new(pure),
}
}
}
impl SampleType for usize {
fn make_sample_model(addr: Address, dist: Box<dyn Distribution<usize>>) -> Model<usize> {
Model::SampleUsize {
addr,
dist,
k: Box::new(pure),
}
}
fn make_observe_model(
addr: Address,
dist: Box<dyn Distribution<usize>>,
value: usize,
) -> Model<()> {
Model::ObserveUsize {
addr,
dist,
value,
k: Box::new(pure),
}
}
}
impl SampleType for i64 {
fn make_sample_model(addr: Address, dist: Box<dyn Distribution<i64>>) -> Model<i64> {
Model::SampleI64 {
addr,
dist,
k: Box::new(pure),
}
}
fn make_observe_model(
addr: Address,
dist: Box<dyn Distribution<i64>>,
value: i64,
) -> Model<()> {
Model::ObserveI64 {
addr,
dist,
value,
k: Box::new(pure),
}
}
}
pub fn observe<T>(addr: Address, dist: impl Distribution<T> + 'static, value: T) -> Model<()>
where
T: SampleType,
{
T::make_observe_model(addr, Box::new(dist), value)
}
pub fn factor(logw: LogF64) -> Model<()> {
Model::Factor {
logw,
k: Box::new(pure),
}
}
pub trait ModelExt<A>: Sized {
fn bind<B>(self, k: impl FnOnce(A) -> Model<B> + Send + 'static) -> Model<B>;
fn map<B>(self, f: impl FnOnce(A) -> B + Send + 'static) -> Model<B> {
self.bind(|a| pure(f(a)))
}
fn and_then<B>(self, k: impl FnOnce(A) -> Model<B> + Send + 'static) -> Model<B> {
self.bind(k)
}
}
impl<A: 'static> ModelExt<A> for Model<A> {
fn bind<B>(self, k: impl FnOnce(A) -> Model<B> + Send + 'static) -> Model<B> {
match self {
Model::Pure(a) => k(a),
Model::SampleF64 { addr, dist, k: k1 } => Model::SampleF64 {
addr,
dist,
k: Box::new(move |x| k1(x).bind(k)),
},
Model::SampleBool { addr, dist, k: k1 } => Model::SampleBool {
addr,
dist,
k: Box::new(move |x| k1(x).bind(k)),
},
Model::SampleU64 { addr, dist, k: k1 } => Model::SampleU64 {
addr,
dist,
k: Box::new(move |x| k1(x).bind(k)),
},
Model::SampleUsize { addr, dist, k: k1 } => Model::SampleUsize {
addr,
dist,
k: Box::new(move |x| k1(x).bind(k)),
},
Model::SampleI64 { addr, dist, k: k1 } => Model::SampleI64 {
addr,
dist,
k: Box::new(move |x| k1(x).bind(k)),
},
Model::ObserveF64 {
addr,
dist,
value,
k: k1,
} => Model::ObserveF64 {
addr,
dist,
value,
k: Box::new(move |()| k1(()).bind(k)),
},
Model::ObserveBool {
addr,
dist,
value,
k: k1,
} => Model::ObserveBool {
addr,
dist,
value,
k: Box::new(move |()| k1(()).bind(k)),
},
Model::ObserveU64 {
addr,
dist,
value,
k: k1,
} => Model::ObserveU64 {
addr,
dist,
value,
k: Box::new(move |()| k1(()).bind(k)),
},
Model::ObserveUsize {
addr,
dist,
value,
k: k1,
} => Model::ObserveUsize {
addr,
dist,
value,
k: Box::new(move |()| k1(()).bind(k)),
},
Model::ObserveI64 {
addr,
dist,
value,
k: k1,
} => Model::ObserveI64 {
addr,
dist,
value,
k: Box::new(move |()| k1(()).bind(k)),
},
Model::Factor { logw, k: k1 } => Model::Factor {
logw,
k: Box::new(move |()| k1(()).bind(k)),
},
}
}
}
pub fn zip<A: Send + 'static, B: Send + 'static>(ma: Model<A>, mb: Model<B>) -> Model<(A, B)> {
ma.bind(|a| mb.map(move |b| (a, b)))
}
pub fn sequence_vec<A: Send + 'static>(models: Vec<Model<A>>) -> Model<Vec<A>> {
let n = models.len();
let mut cont: Box<dyn FnOnce(Vec<A>) -> Model<Vec<A>> + Send> = Box::new(pure);
for m in models.into_iter().rev() {
let next = cont;
cont = Box::new(move |mut acc: Vec<A>| {
m.bind(move |a| {
acc.push(a);
next(acc)
})
});
}
cont(Vec::with_capacity(n))
}
pub fn traverse_vec<T, A: Send + 'static>(
items: Vec<T>,
f: impl Fn(T) -> Model<A> + Send + Sync + 'static,
) -> Model<Vec<A>> {
sequence_vec(items.into_iter().map(f).collect())
}
pub fn guard(pred: bool) -> Model<()> {
if pred {
pure(())
} else {
factor(f64::NEG_INFINITY)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::addr;
use crate::core::distribution::*;
use crate::runtime::handler::run;
use crate::runtime::interpreters::PriorHandler;
use crate::runtime::trace::Trace;
use rand::rngs::StdRng;
use rand::SeedableRng;
#[test]
fn pure_and_map_work() {
let m = pure(2).map(|x| x + 3);
let (val, t) = run(
PriorHandler {
rng: &mut StdRng::seed_from_u64(1),
trace: Trace::default(),
},
m,
);
assert_eq!(val, 5);
assert_eq!(t.choices.len(), 0);
}
#[test]
fn sample_and_observe_sites() {
let m = sample(addr!("x"), Normal::new(0.0, 1.0).unwrap())
.and_then(|x| observe(addr!("y"), Normal::new(x, 1.0).unwrap(), 0.5).map(move |_| x));
let mut rng = StdRng::seed_from_u64(42);
let (_val, trace) = run(
PriorHandler {
rng: &mut rng,
trace: Trace::default(),
},
m,
);
assert!(trace.choices.contains_key(&addr!("x")));
assert!((trace.log_likelihood.is_finite()));
}
#[test]
fn factor_and_guard_affect_weight() {
let m_ok = factor(-1.23);
let ((), t_ok) = run(
PriorHandler {
rng: &mut StdRng::seed_from_u64(2),
trace: Trace::default(),
},
m_ok,
);
assert!((t_ok.total_log_weight() + 1.23).abs() < 1e-12);
let m_bad = guard(false);
let ((), t_bad) = run(
PriorHandler {
rng: &mut StdRng::seed_from_u64(3),
trace: Trace::default(),
},
m_bad,
);
assert!(
t_bad.total_log_weight().is_infinite() && t_bad.total_log_weight().is_sign_negative()
);
}
#[test]
fn sequence_and_traverse_vec() {
let models: Vec<Model<i32>> = (0..5).map(pure).collect();
let seq = sequence_vec(models);
let (vals, t) = run(
PriorHandler {
rng: &mut StdRng::seed_from_u64(4),
trace: Trace::default(),
},
seq,
);
assert_eq!(vals, vec![0, 1, 2, 3, 4]);
assert_eq!(t.choices.len(), 0);
let trav = traverse_vec(vec![1, 2, 3], |i| pure(i * 2));
let (v2, _t2) = run(
PriorHandler {
rng: &mut StdRng::seed_from_u64(5),
trace: Trace::default(),
},
trav,
);
assert_eq!(v2, vec![2, 4, 6]);
}
#[test]
fn zip_and_sequence_empty_and_bind_chaining() {
let m1 = pure(1);
let m2 = pure(2);
let (pair, _t) = run(
PriorHandler {
rng: &mut StdRng::seed_from_u64(6),
trace: Trace::default(),
},
zip(m1, m2),
);
assert_eq!(pair, (1, 2));
let empty: Vec<Model<i32>> = vec![];
let (vals, _t2) = run(
PriorHandler {
rng: &mut StdRng::seed_from_u64(7),
trace: Trace::default(),
},
sequence_vec(empty),
);
assert!(vals.is_empty());
let model = sample(addr!("x"), Normal::new(0.0, 1.0).unwrap())
.bind(|x| pure(x > 0.0))
.bind(|b| if b { pure(1u64) } else { pure(0u64) });
let (_val, _t3) = run(
PriorHandler {
rng: &mut StdRng::seed_from_u64(8),
trace: Trace::default(),
},
model,
);
}
}