#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/docs/runtime/interpreters.md"))]
use crate::core::address::Address;
use crate::core::distribution::Distribution;
use crate::core::model::Model;
use crate::error::{ErrorCode, FugueError, FugueResult};
use crate::runtime::handler::{run, Handler};
use crate::runtime::trace::{Choice, ChoiceValue, Trace};
use rand::RngCore;
#[inline]
fn assert_no_duplicate_sample(trace: &Trace, addr: &Address, handler: &str) {
if trace.choices.contains_key(addr) {
panic!(
"{handler}: address {addr} was sampled twice in one execution \
(AddressConflict, ErrorCode::{:?}={}). Every sample site must have \
a unique address.",
ErrorCode::AddressConflict,
ErrorCode::AddressConflict as u32
);
}
}
fn address_conflict_error(addr: &Address, handler: &str) -> FugueError {
FugueError::ModelError {
address: Some(addr.clone()),
reason: format!("{handler}: address sampled twice in one execution"),
code: ErrorCode::AddressConflict,
context: crate::error::ErrorContext::new(),
}
}
macro_rules! for_each_value_type {
($m:ident) => {
$m! {
(on_sample_f64, on_observe_f64, f64, F64, "f64", get_f64, get_f64_result),
(on_sample_bool, on_observe_bool, bool, Bool, "bool", get_bool, get_bool_result),
(on_sample_u64, on_observe_u64, u64, U64, "u64", get_u64, get_u64_result),
(on_sample_usize, on_observe_usize, usize, Usize, "usize", get_usize, get_usize_result),
(on_sample_i64, on_observe_i64, i64, I64, "i64", get_i64, get_i64_result),
}
};
}
macro_rules! impl_observe_methods {
($(($sample:ident, $observe:ident, $ty:ty, $variant:ident, $tyname:literal, $get:ident, $get_res:ident)),* $(,)?) => {
$(
fn $observe(&mut self, _addr: &Address, dist: &dyn Distribution<$ty>, value: $ty) {
self.trace.log_likelihood += dist.log_prob(&value);
}
)*
};
}
macro_rules! impl_prior_sample_methods {
($(($sample:ident, $observe:ident, $ty:ty, $variant:ident, $tyname:literal, $get:ident, $get_res:ident)),* $(,)?) => {
$(
fn $sample(&mut self, addr: &Address, dist: &dyn Distribution<$ty>) -> $ty {
assert_no_duplicate_sample(&self.trace, addr, "PriorHandler");
let x = dist.sample(self.rng);
let lp = dist.log_prob(&x);
self.trace.log_prior += lp;
self.trace.choices.insert(
addr.clone(),
Choice { addr: addr.clone(), value: ChoiceValue::$variant(x), logp: lp },
);
x
}
)*
};
}
macro_rules! impl_replay_sample_methods {
($(($sample:ident, $observe:ident, $ty:ty, $variant:ident, $tyname:literal, $get:ident, $get_res:ident)),* $(,)?) => {
$(
fn $sample(&mut self, addr: &Address, dist: &dyn Distribution<$ty>) -> $ty {
assert_no_duplicate_sample(&self.trace, addr, "ReplayHandler");
let x = if let Some(c) = self.base.choices.get(addr) {
match c.value {
ChoiceValue::$variant(v) => v,
_ => panic!("expected {} at {}", $tyname, addr),
}
} else {
dist.sample(self.rng)
};
let lp = dist.log_prob(&x);
self.trace.log_prior += lp;
self.trace.choices.insert(
addr.clone(),
Choice { addr: addr.clone(), value: ChoiceValue::$variant(x), logp: lp },
);
x
}
)*
};
}
macro_rules! impl_score_sample_methods {
($(($sample:ident, $observe:ident, $ty:ty, $variant:ident, $tyname:literal, $get:ident, $get_res:ident)),* $(,)?) => {
$(
fn $sample(&mut self, addr: &Address, dist: &dyn Distribution<$ty>) -> $ty {
assert_no_duplicate_sample(&self.trace, addr, "ScoreGivenTrace");
let c = self
.base
.choices
.get(addr)
.unwrap_or_else(|| panic!("missing value for site {} in base trace", addr));
let x = match c.value {
ChoiceValue::$variant(v) => v,
_ => panic!("expected {} at {}", $tyname, addr),
};
let lp = dist.log_prob(&x);
self.trace.log_prior += lp;
self.trace.choices.insert(
addr.clone(),
Choice { addr: addr.clone(), value: ChoiceValue::$variant(x), logp: lp },
);
x
}
)*
};
}
macro_rules! impl_safe_replay_sample_methods {
($(($sample:ident, $observe:ident, $ty:ty, $variant:ident, $tyname:literal, $get:ident, $get_res:ident)),* $(,)?) => {
$(
fn $sample(&mut self, addr: &Address, dist: &dyn Distribution<$ty>) -> $ty {
if self.trace.choices.contains_key(addr) {
if self.warn_on_mismatch {
eprintln!("Warning: {}", address_conflict_error(addr, "SafeReplayHandler"));
}
self.trace.log_prior += f64::NEG_INFINITY;
return dist.sample(self.rng);
}
let x = match self.base.$get(addr) {
Some(v) => v,
None => {
if self.warn_on_mismatch && self.base.choices.contains_key(addr) {
if let Some(choice) = self.base.choices.get(addr) {
eprintln!(
"Warning: Type mismatch at {}: expected {}, found {}",
addr, $tyname, choice.value.type_name()
);
}
}
dist.sample(self.rng)
}
};
let lp = dist.log_prob(&x);
self.trace.log_prior += lp;
self.trace.choices.insert(
addr.clone(),
Choice { addr: addr.clone(), value: ChoiceValue::$variant(x), logp: lp },
);
x
}
)*
};
}
macro_rules! impl_safe_score_sample_methods {
($(($sample:ident, $observe:ident, $ty:ty, $variant:ident, $tyname:literal, $get:ident, $get_res:ident)),* $(,)?) => {
$(
fn $sample(&mut self, addr: &Address, dist: &dyn Distribution<$ty>) -> $ty {
if self.trace.choices.contains_key(addr) {
if self.warn_on_error {
eprintln!("Warning: {}", address_conflict_error(addr, "SafeScoreGivenTrace"));
}
self.trace.log_prior += f64::NEG_INFINITY;
return <$ty as Default>::default();
}
match self.base.$get_res(addr) {
Ok(x) => {
let lp = dist.log_prob(&x);
self.trace.log_prior += lp;
self.trace.choices.insert(
addr.clone(),
Choice { addr: addr.clone(), value: ChoiceValue::$variant(x), logp: lp },
);
x
}
Err(e) => {
if self.warn_on_error {
eprintln!("Warning: Failed to get {} at {}: {}", $tyname, addr, e);
}
self.trace.log_prior += f64::NEG_INFINITY;
<$ty as Default>::default()
}
}
}
)*
};
}
macro_rules! impl_strict_score_sample_methods {
($(($sample:ident, $observe:ident, $ty:ty, $variant:ident, $tyname:literal, $get:ident, $get_res:ident)),* $(,)?) => {
$(
fn $sample(&mut self, addr: &Address, dist: &dyn Distribution<$ty>) -> $ty {
if self.trace.choices.contains_key(addr) {
if self.error.is_none() {
*self.error = Some(address_conflict_error(addr, "StrictScoreGivenTrace"));
}
return <$ty as Default>::default();
}
match self.base.$get_res(addr) {
Ok(x) => {
let lp = dist.log_prob(&x);
self.trace.log_prior += lp;
self.trace.choices.insert(
addr.clone(),
Choice { addr: addr.clone(), value: ChoiceValue::$variant(x), logp: lp },
);
x
}
Err(cause) => {
if self.error.is_none() {
*self.error = Some(FugueError::ModelError {
address: Some(addr.clone()),
reason: format!(
"model visited address {} not present (as {}) in the base \
trace; structure varies between the base trace and this model",
addr, $tyname
),
code: ErrorCode::UnexpectedModelStructure,
context: crate::error::ErrorContext::new().with_cause(cause),
});
}
<$ty as Default>::default()
}
}
}
)*
};
}
macro_rules! impl_reconciling_score_sample_methods {
($(($sample:ident, $observe:ident, $ty:ty, $variant:ident, $tyname:literal, $get:ident, $get_res:ident)),* $(,)?) => {
$(
fn $sample(&mut self, addr: &Address, dist: &dyn Distribution<$ty>) -> $ty {
if self.trace.choices.contains_key(addr) {
if self.error.is_none() {
*self.error =
Some(address_conflict_error(addr, "ReconcilingScoreGivenTrace"));
}
return <$ty as Default>::default();
}
let x = match self.base.$get(addr) {
Some(v) => v,
None => {
self.fresh.push(addr.clone());
dist.sample(self.rng)
}
};
let lp = dist.log_prob(&x);
self.trace.log_prior += lp;
self.trace.choices.insert(
addr.clone(),
Choice { addr: addr.clone(), value: ChoiceValue::$variant(x), logp: lp },
);
x
}
)*
};
}
pub struct PriorHandler<'r, R: RngCore> {
pub rng: &'r mut R,
pub trace: Trace,
}
impl<'r, R: RngCore> Handler for PriorHandler<'r, R> {
for_each_value_type!(impl_prior_sample_methods);
for_each_value_type!(impl_observe_methods);
fn on_factor(&mut self, logw: f64) {
self.trace.log_factors += logw;
}
fn finish(self) -> Trace {
self.trace
}
}
pub struct ReplayHandler<'r, R: RngCore> {
pub rng: &'r mut R,
pub base: Trace,
pub trace: Trace,
}
impl<'r, R: RngCore> Handler for ReplayHandler<'r, R> {
for_each_value_type!(impl_replay_sample_methods);
for_each_value_type!(impl_observe_methods);
fn on_factor(&mut self, logw: f64) {
self.trace.log_factors += logw;
}
fn finish(self) -> Trace {
self.trace
}
}
pub struct ScoreGivenTrace {
pub base: Trace,
pub trace: Trace,
}
impl Handler for ScoreGivenTrace {
for_each_value_type!(impl_score_sample_methods);
for_each_value_type!(impl_observe_methods);
fn on_factor(&mut self, logw: f64) {
self.trace.log_factors += logw;
}
fn finish(self) -> Trace {
self.trace
}
}
pub struct SafeReplayHandler<'r, R: RngCore> {
pub rng: &'r mut R,
pub base: Trace,
pub trace: Trace,
pub warn_on_mismatch: bool,
}
impl<'r, R: RngCore> Handler for SafeReplayHandler<'r, R> {
for_each_value_type!(impl_safe_replay_sample_methods);
for_each_value_type!(impl_observe_methods);
fn on_factor(&mut self, logw: f64) {
self.trace.log_factors += logw;
}
fn finish(self) -> Trace {
self.trace
}
}
pub struct SafeScoreGivenTrace {
pub base: Trace,
pub trace: Trace,
pub warn_on_error: bool,
}
impl Handler for SafeScoreGivenTrace {
for_each_value_type!(impl_safe_score_sample_methods);
for_each_value_type!(impl_observe_methods);
fn on_factor(&mut self, logw: f64) {
self.trace.log_factors += logw;
}
fn finish(self) -> Trace {
self.trace
}
}
pub struct StrictScoreGivenTrace<'e> {
pub base: Trace,
pub trace: Trace,
error: &'e mut Option<FugueError>,
}
impl<'e> Handler for StrictScoreGivenTrace<'e> {
for_each_value_type!(impl_strict_score_sample_methods);
for_each_value_type!(impl_observe_methods);
fn on_factor(&mut self, logw: f64) {
self.trace.log_factors += logw;
}
fn finish(self) -> Trace {
self.trace
}
}
pub fn score_given_trace_strict<A>(base: Trace, model: Model<A>) -> FugueResult<(A, Trace)> {
let mut error: Option<FugueError> = None;
let handler = StrictScoreGivenTrace {
base,
trace: Trace::default(),
error: &mut error,
};
let (a, trace) = run(handler, model);
match error {
Some(e) => Err(e),
None => Ok((a, trace)),
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct ReconcileReport {
pub fresh_addresses: Vec<Address>,
pub vanished_addresses: Vec<Address>,
}
pub struct ReconcilingScoreGivenTrace<'r, 'f, 'e, R: RngCore> {
pub rng: &'r mut R,
pub base: Trace,
pub trace: Trace,
fresh: &'f mut Vec<Address>,
error: &'e mut Option<FugueError>,
}
impl<'r, 'f, 'e, R: RngCore> Handler for ReconcilingScoreGivenTrace<'r, 'f, 'e, R> {
for_each_value_type!(impl_reconciling_score_sample_methods);
for_each_value_type!(impl_observe_methods);
fn on_factor(&mut self, logw: f64) {
self.trace.log_factors += logw;
}
fn finish(self) -> Trace {
self.trace
}
}
pub fn score_given_trace_reconciled<A, R: RngCore>(
base: Trace,
rng: &mut R,
model: Model<A>,
) -> FugueResult<(A, Trace, ReconcileReport)> {
let mut error: Option<FugueError> = None;
let mut fresh_addresses: Vec<Address> = Vec::new();
let base_addresses: Vec<Address> = base.choices.keys().cloned().collect();
let handler = ReconcilingScoreGivenTrace {
rng,
base,
trace: Trace::default(),
fresh: &mut fresh_addresses,
error: &mut error,
};
let (a, trace) = run(handler, model);
if let Some(e) = error {
return Err(e);
}
let vanished_addresses: Vec<Address> = base_addresses
.into_iter()
.filter(|addr| !trace.choices.contains_key(addr))
.collect();
Ok((
a,
trace,
ReconcileReport {
fresh_addresses,
vanished_addresses,
},
))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::addr;
use crate::core::distribution::*;
use crate::core::model::{observe, sample, ModelExt};
use rand::rngs::StdRng;
use rand::SeedableRng;
#[test]
fn prior_handler_samples_and_accumulates() {
let mut rng = StdRng::seed_from_u64(7);
let (_val, trace) = crate::runtime::handler::run(
PriorHandler {
rng: &mut rng,
trace: Trace::default(),
},
sample(addr!("x"), Normal::new(0.0, 1.0).unwrap())
.and_then(|x| observe(addr!("y"), Normal::new(x, 1.0).unwrap(), 0.5)),
);
assert!(trace.choices.contains_key(&addr!("x")));
assert!(trace.log_prior.is_finite());
assert!(trace.log_likelihood.is_finite());
}
#[test]
fn replay_handler_reuses_values() {
let mut rng = StdRng::seed_from_u64(8);
let ((), base) = crate::runtime::handler::run(
PriorHandler {
rng: &mut rng,
trace: Trace::default(),
},
sample(addr!("x"), Normal::new(0.0, 1.0).unwrap()).map(|_| ()),
);
let ((), replayed) = crate::runtime::handler::run(
ReplayHandler {
rng: &mut rng,
base: base.clone(),
trace: Trace::default(),
},
sample(addr!("x"), Normal::new(0.0, 1.0).unwrap()).map(|_| ()),
);
let x_base = base.get_f64(&addr!("x")).unwrap();
let x_replay = replayed.get_f64(&addr!("x")).unwrap();
assert_eq!(x_base, x_replay);
}
#[test]
fn score_given_trace_scores_fixed_values() {
let mut rng = StdRng::seed_from_u64(9);
let (_a, base) = crate::runtime::handler::run(
PriorHandler {
rng: &mut rng,
trace: Trace::default(),
},
sample(addr!("x"), Normal::new(0.0, 1.0).unwrap()),
);
let (_a2, scored) = crate::runtime::handler::run(
ScoreGivenTrace {
base: base.clone(),
trace: Trace::default(),
},
sample(addr!("x"), Normal::new(0.0, 1.0).unwrap()),
);
assert_eq!(scored.get_f64(&addr!("x")), base.get_f64(&addr!("x")));
assert!(scored.log_prior.is_finite());
}
#[test]
fn safe_variants_handle_mismatches() {
let mut rng = StdRng::seed_from_u64(10);
let (_a, base) = crate::runtime::handler::run(
PriorHandler {
rng: &mut rng,
trace: Trace::default(),
},
sample(addr!("x"), Normal::new(0.0, 1.0).unwrap()),
);
let (_b, t1) = crate::runtime::handler::run(
SafeReplayHandler {
rng: &mut rng,
base: base.clone(),
trace: Trace::default(),
warn_on_mismatch: true,
},
sample(addr!("x"), Bernoulli::new(0.5).unwrap()),
);
assert!(t1.log_prior.is_finite());
let (_c, t2) = crate::runtime::handler::run(
SafeScoreGivenTrace {
base: base.clone(),
trace: Trace::default(),
warn_on_error: true,
},
sample(addr!("x"), Bernoulli::new(0.5).unwrap()),
);
assert!(t2.log_prior.is_infinite());
}
#[test]
fn handlers_cover_all_types_sample_and_observe() {
let model = sample(addr!("f"), Normal::new(0.0, 1.0).unwrap())
.and_then(|_| sample(addr!("b"), Bernoulli::new(0.6).unwrap()))
.and_then(|_| sample(addr!("u64"), Poisson::new(3.0).unwrap()))
.and_then(|_| sample(addr!("usz"), Categorical::new(vec![0.3, 0.7]).unwrap()))
.and_then(|_| observe(addr!("f_obs"), Normal::new(0.0, 1.0).unwrap(), 0.1))
.and_then(|_| observe(addr!("b_obs"), Bernoulli::new(0.4).unwrap(), true))
.and_then(|_| observe(addr!("u64_obs"), Poisson::new(2.0).unwrap(), 1))
.and_then(|_| {
observe(
addr!("usz_obs"),
Categorical::new(vec![0.5, 0.5]).unwrap(),
1,
)
});
let (_a, t) = crate::runtime::handler::run(
PriorHandler {
rng: &mut StdRng::seed_from_u64(100),
trace: Trace::default(),
},
model,
);
assert!(t.get_f64(&addr!("f")).is_some());
assert!(t.get_bool(&addr!("b")).is_some());
assert!(t.get_u64(&addr!("u64")).is_some());
assert!(t.get_usize(&addr!("usz")).is_some());
assert!(t.log_likelihood.is_finite());
let base = t.clone();
let (_sv, scored) = crate::runtime::handler::run(
ScoreGivenTrace {
base: base.clone(),
trace: Trace::default(),
},
sample(addr!("f"), Normal::new(0.0, 1.0).unwrap())
.and_then(|_| sample(addr!("b"), Bernoulli::new(0.6).unwrap()))
.and_then(|_| sample(addr!("u64"), Poisson::new(3.0).unwrap()))
.and_then(|_| sample(addr!("usz"), Categorical::new(vec![0.3, 0.7]).unwrap())),
);
assert!(scored.log_prior.is_finite());
let (_sv2, safe) = crate::runtime::handler::run(
SafeReplayHandler {
rng: &mut StdRng::seed_from_u64(101),
base: base.clone(),
trace: Trace::default(),
warn_on_mismatch: true,
},
sample(addr!("u64"), Bernoulli::new(0.5).unwrap()),
);
assert!(safe.log_prior.is_finite());
}
#[test]
fn safe_score_given_trace_warn_flag_branches() {
let mut rng = StdRng::seed_from_u64(102);
let (_a, base) = crate::runtime::handler::run(
PriorHandler {
rng: &mut rng,
trace: Trace::default(),
},
sample(addr!("x"), Normal::new(0.0, 1.0).unwrap()),
);
let (_b, t_false) = crate::runtime::handler::run(
SafeScoreGivenTrace {
base: base.clone(),
trace: Trace::default(),
warn_on_error: false,
},
sample(addr!("x"), Bernoulli::new(0.5).unwrap()),
);
assert!(t_false.log_prior.is_infinite());
let (_c, t_true) = crate::runtime::handler::run(
SafeScoreGivenTrace {
base: base.clone(),
trace: Trace::default(),
warn_on_error: true,
},
sample(addr!("x"), Bernoulli::new(0.5).unwrap()),
);
assert!(t_true.log_prior.is_infinite());
}
#[test]
#[should_panic]
fn replay_handler_panics_on_type_mismatch() {
let mut rng = StdRng::seed_from_u64(103);
let (_a, base) = crate::runtime::handler::run(
PriorHandler {
rng: &mut rng,
trace: Trace::default(),
},
sample(addr!("x"), Normal::new(0.0, 1.0).unwrap()),
);
let (_b, _t) = crate::runtime::handler::run(
ReplayHandler {
rng: &mut rng,
base: base.clone(),
trace: Trace::default(),
},
sample(addr!("x"), Bernoulli::new(0.5).unwrap()),
);
}
#[test]
fn safe_replay_handler_samples_fresh_for_missing_address() {
let mut rng = StdRng::seed_from_u64(104);
let base = Trace::default();
let (_a, t) = crate::runtime::handler::run(
SafeReplayHandler {
rng: &mut rng,
base,
trace: Trace::default(),
warn_on_mismatch: true,
},
sample(addr!("z"), Normal::new(0.0, 1.0).unwrap()),
);
assert!(t.get_f64(&addr!("z")).is_some());
}
}