use fugue::*;
use rand::{rngs::StdRng, SeedableRng};
#[test]
fn test_basic_prior_sampling() {
let mut rng = StdRng::seed_from_u64(42);
let model = sample(addr!("x"), Normal::new(0.0, 1.0).unwrap());
let handler = runtime::interpreters::PriorHandler {
rng: &mut rng,
trace: runtime::trace::Trace::default(),
};
let (value, trace) = runtime::handler::run(handler, model);
let x_value = trace.get_f64(&addr!("x"));
assert!(x_value.is_some());
assert_eq!(value, x_value.unwrap());
let log_weight = trace.total_log_weight();
assert!(log_weight.is_finite());
}
#[test]
fn test_model_with_observation_and_factor() {
let mut rng = StdRng::seed_from_u64(42);
let model = sample(addr!("x"), Normal::new(0.0, 1.0).unwrap())
.bind(|x| observe(addr!("y"), Normal::new(x, 1.0).unwrap(), 0.5))
.bind(|_| factor(-1.0));
let handler = runtime::interpreters::PriorHandler {
rng: &mut rng,
trace: runtime::trace::Trace::default(),
};
let ((), trace) = runtime::handler::run(handler, model);
let x_value = trace.get_f64(&addr!("x"));
assert!(x_value.is_some());
assert!(trace.log_prior.is_finite());
assert!(trace.log_likelihood.is_finite());
assert!(trace.log_factors.is_finite());
assert!((trace.log_factors + 1.0).abs() < 1e-12);
let log_weight = trace.total_log_weight();
assert!(log_weight.is_finite());
}
#[test]
fn test_replay_and_score_handlers() {
let mut rng = StdRng::seed_from_u64(42);
let model = || {
sample(addr!("x"), Normal::new(0.0, 1.0).unwrap())
.bind(|x| observe(addr!("y"), Normal::new(x, 1.0).unwrap(), 0.5).map(move |_| x))
};
let prior_handler = runtime::interpreters::PriorHandler {
rng: &mut rng,
trace: runtime::trace::Trace::default(),
};
let (original_value, original_trace) = runtime::handler::run(prior_handler, model());
let replay_handler = runtime::interpreters::ReplayHandler {
rng: &mut rng,
base: original_trace.clone(),
trace: runtime::trace::Trace::default(),
};
let (replayed_value, replayed_trace) = runtime::handler::run(replay_handler, model());
assert_eq!(original_value, replayed_value);
let original_x = original_trace.get_f64(&addr!("x")).unwrap();
let replayed_x = replayed_trace.get_f64(&addr!("x")).unwrap();
assert_eq!(original_x, replayed_x);
let score_handler = runtime::interpreters::ScoreGivenTrace {
base: original_trace.clone(),
trace: runtime::trace::Trace::default(),
};
let (scored_value, scored_trace) = runtime::handler::run(score_handler, model());
assert_eq!(scored_value, original_value);
assert!(scored_trace.total_log_weight().is_finite());
assert_eq!(scored_trace.get_f64(&addr!("x")).unwrap(), original_x);
}
#[test]
fn test_model_composition() {
let mut rng = StdRng::seed_from_u64(42);
let model1 = sample(addr!("x"), Normal::new(0.0, 1.0).unwrap())
.bind(|x| sample(addr!("y"), Normal::new(x, 0.5).unwrap()))
.map(|y| y * 2.0);
let handler1 = runtime::interpreters::PriorHandler {
rng: &mut rng,
trace: runtime::trace::Trace::default(),
};
let (result1, trace1) = runtime::handler::run(handler1, model1);
let _x_val = trace1.get_f64(&addr!("x")).unwrap();
let y_val = trace1.get_f64(&addr!("y")).unwrap();
assert_eq!(result1, y_val * 2.0);
let model_a = sample(addr!("a"), Normal::new(0.0, 1.0).unwrap());
let model_b = sample(addr!("b"), Normal::new(1.0, 1.0).unwrap());
let zipped_model = zip(model_a, model_b);
let handler2 = runtime::interpreters::PriorHandler {
rng: &mut rng,
trace: runtime::trace::Trace::default(),
};
let ((a_result, b_result), trace2) = runtime::handler::run(handler2, zipped_model);
let a_val = trace2.get_f64(&addr!("a")).unwrap();
let b_val = trace2.get_f64(&addr!("b")).unwrap();
assert_eq!(a_result, a_val);
assert_eq!(b_result, b_val);
let models = vec![
sample(addr!("seq_0"), Normal::new(0.0, 1.0).unwrap()),
sample(addr!("seq_1"), Normal::new(1.0, 1.0).unwrap()),
sample(addr!("seq_2"), Normal::new(2.0, 1.0).unwrap()),
];
let sequence_model = sequence_vec(models);
let handler3 = runtime::interpreters::PriorHandler {
rng: &mut rng,
trace: runtime::trace::Trace::default(),
};
let (seq_results, trace3) = runtime::handler::run(handler3, sequence_model);
assert_eq!(seq_results.len(), 3);
assert_eq!(seq_results[0], trace3.get_f64(&addr!("seq_0")).unwrap());
assert_eq!(seq_results[1], trace3.get_f64(&addr!("seq_1")).unwrap());
assert_eq!(seq_results[2], trace3.get_f64(&addr!("seq_2")).unwrap());
}
#[test]
fn test_mixed_types() {
let mut rng = StdRng::seed_from_u64(42);
let model = sample(addr!("f64_val"), Normal::new(0.0, 1.0).unwrap())
.bind(|_| sample(addr!("bool_val"), Bernoulli::new(0.6).unwrap()))
.bind(|_| sample(addr!("u64_val"), Poisson::new(3.0).unwrap()))
.bind(|_| {
sample(
addr!("usize_val"),
Categorical::new(vec![0.3, 0.4, 0.3]).unwrap(),
)
})
.map(|usize_val| (usize_val, "mixed_types_result"));
let handler = runtime::interpreters::PriorHandler {
rng: &mut rng,
trace: runtime::trace::Trace::default(),
};
let ((usize_result, string_result), trace) = runtime::handler::run(handler, model);
let f64_val = trace.get_f64(&addr!("f64_val"));
assert!(f64_val.is_some());
let bool_val = trace.get_bool(&addr!("bool_val"));
assert!(bool_val.is_some());
let u64_val = trace.get_u64(&addr!("u64_val"));
assert!(u64_val.is_some());
let usize_val = trace.get_usize(&addr!("usize_val"));
assert!(usize_val.is_some());
assert_eq!(usize_result, usize_val.unwrap());
assert_eq!(string_result, "mixed_types_result");
assert!(trace.get_f64(&addr!("bool_val")).is_none());
assert!(trace.get_bool(&addr!("f64_val")).is_none());
assert!(trace.get_u64(&addr!("usize_val")).is_none());
assert!(trace.get_usize(&addr!("u64_val")).is_none());
assert!(trace.get_f64_result(&addr!("bool_val")).is_err());
assert!(trace.get_bool_result(&addr!("f64_val")).is_err());
assert!(trace.get_u64_result(&addr!("usize_val")).is_err());
assert!(trace.get_usize_result(&addr!("u64_val")).is_err());
assert!(trace.get_f64(&addr!("missing")).is_none());
assert!(trace.get_f64_result(&addr!("missing")).is_err());
}
#[test]
fn test_macro_integration() {
let mut rng = StdRng::seed_from_u64(42);
let model = prob!(
let x <- sample(addr!("x"), Normal::new(0.0, 1.0).unwrap());
let y <- sample(addr!("y"), Normal::new(x, 0.5).unwrap());
observe(addr!("obs"), Normal::new(x, 0.5).unwrap(), 0.3);
factor(-0.5);
pure(x + y)
);
let handler = runtime::interpreters::PriorHandler {
rng: &mut rng,
trace: runtime::trace::Trace::default(),
};
let (result, trace) = runtime::handler::run(handler, model);
let x_val = trace.get_f64(&addr!("x")).unwrap();
let y_val = trace.get_f64(&addr!("y")).unwrap();
assert_eq!(result, x_val + y_val);
assert!((trace.log_factors + 0.5).abs() < 1e-12);
let plate_model = plate! { i in 0..3 =>
sample(scoped_addr!("plate", "item", "{}", i), Normal::new(i as f64, 1.0).unwrap())
};
let handler2 = runtime::interpreters::PriorHandler {
rng: &mut rng,
trace: runtime::trace::Trace::default(),
};
let (plate_results, trace2) = runtime::handler::run(handler2, plate_model);
assert_eq!(plate_results.len(), 3);
for (i, &expected_val) in plate_results.iter().enumerate().take(3) {
let addr = scoped_addr!("plate", "item", "{}", i);
let val = trace2.get_f64(&addr);
assert!(val.is_some());
assert_eq!(expected_val, val.unwrap());
}
}
#[test]
fn test_factor_guard_integration() {
let mut rng = StdRng::seed_from_u64(42);
let guard_model = sample(addr!("x"), Normal::new(0.0, 1.0).unwrap())
.bind(|x| guard(x > -2.0 && x < 2.0)) .bind(|_| factor(-0.5))
.bind(|_| pure("guard_passed"));
let handler = runtime::interpreters::PriorHandler {
rng: &mut rng,
trace: runtime::trace::Trace::default(),
};
let (result, trace) = runtime::handler::run(handler, guard_model);
assert_eq!(result, "guard_passed");
assert!(trace.log_factors.is_finite());
assert!((trace.log_factors + 0.5).abs() < 1e-12);
let x_val = trace.get_f64(&addr!("x")).unwrap();
assert!(x_val > -2.0 && x_val < 2.0);
let complex_model = sample(addr!("mu"), Normal::new(0.0, 2.0).unwrap()).bind(|mu| {
guard(mu.abs() < 5.0) .bind(move |_| {
sample(addr!("sigma"), Exponential::new(1.0).unwrap()).bind(move |sigma| {
guard(sigma > 0.1 && sigma < 10.0) .bind(move |_| observe(addr!("y"), Normal::new(mu, sigma).unwrap(), 1.5))
.bind(move |_| factor(mu * 0.1)) .map(move |_| (mu, sigma))
})
})
});
let handler2 = runtime::interpreters::PriorHandler {
rng: &mut rng,
trace: runtime::trace::Trace::default(),
};
let ((mu_result, sigma_result), trace2) = runtime::handler::run(handler2, complex_model);
assert!(mu_result.abs() < 5.0);
assert!(sigma_result > 0.1 && sigma_result < 10.0);
assert!(trace2.log_prior.is_finite());
assert!(trace2.log_likelihood.is_finite());
assert!(trace2.log_factors.is_finite());
assert!(trace2.total_log_weight().is_finite());
assert!((trace2.log_factors - mu_result * 0.1).abs() < 1e-12);
}
#[test]
fn test_distribution_coverage() {
let mut rng = StdRng::seed_from_u64(42);
let continuous_model = sample(addr!("normal"), Normal::new(0.0, 1.0).unwrap())
.bind(|_| sample(addr!("uniform"), Uniform::new(0.0, 1.0).unwrap()))
.bind(|_| sample(addr!("exponential"), Exponential::new(1.0).unwrap()))
.bind(|_| sample(addr!("beta"), Beta::new(2.0, 3.0).unwrap()))
.bind(|_| sample(addr!("gamma"), Gamma::new(2.0, 1.0).unwrap()))
.bind(|_| sample(addr!("lognormal"), LogNormal::new(0.0, 1.0).unwrap()));
let handler1 = runtime::interpreters::PriorHandler {
rng: &mut rng,
trace: runtime::trace::Trace::default(),
};
let (lognormal_val, trace1) = runtime::handler::run(handler1, continuous_model);
assert!(trace1.get_f64(&addr!("normal")).unwrap().is_finite());
assert!(trace1.get_f64(&addr!("uniform")).unwrap().is_finite());
assert!(trace1.get_f64(&addr!("exponential")).unwrap().is_finite());
assert!(trace1.get_f64(&addr!("beta")).unwrap().is_finite());
assert!(trace1.get_f64(&addr!("gamma")).unwrap().is_finite());
assert!(trace1.get_f64(&addr!("lognormal")).unwrap().is_finite());
assert_eq!(lognormal_val, trace1.get_f64(&addr!("lognormal")).unwrap());
let discrete_model = sample(addr!("bernoulli"), Bernoulli::new(0.7).unwrap())
.bind(|_| sample(addr!("poisson"), Poisson::new(3.0).unwrap()))
.bind(|_| sample(addr!("binomial"), Binomial::new(10, 0.4).unwrap()))
.bind(|_| {
sample(
addr!("categorical"),
Categorical::new(vec![0.2, 0.3, 0.5]).unwrap(),
)
});
let handler2 = runtime::interpreters::PriorHandler {
rng: &mut rng,
trace: runtime::trace::Trace::default(),
};
let (categorical_val, trace2) = runtime::handler::run(handler2, discrete_model);
let bernoulli_val = trace2.get_bool(&addr!("bernoulli")).unwrap();
let poisson_val = trace2.get_u64(&addr!("poisson")).unwrap();
let binomial_val = trace2.get_u64(&addr!("binomial")).unwrap();
let categorical_result = trace2.get_usize(&addr!("categorical")).unwrap();
let _ = bernoulli_val; let _ = poisson_val; assert!((0..=10).contains(&binomial_val));
assert!(categorical_result < 3); assert_eq!(categorical_val, categorical_result);
let mixed_model = sample(addr!("mu"), Normal::new(0.0, 1.0).unwrap()).bind(|mu| {
sample(addr!("success"), Bernoulli::new(0.6).unwrap()).bind(move |success| {
if success {
observe(addr!("obs"), Normal::new(mu, 0.5).unwrap(), 0.8)
.map(move |_| (mu, success))
} else {
observe(addr!("obs"), Normal::new(mu, 0.5).unwrap(), -0.3)
.map(move |_| (mu, success))
}
})
});
let handler3 = runtime::interpreters::PriorHandler {
rng: &mut rng,
trace: runtime::trace::Trace::default(),
};
let ((mu_mixed, success_mixed), trace3) = runtime::handler::run(handler3, mixed_model);
assert!(mu_mixed.is_finite());
let _ = success_mixed; assert!(trace3.log_likelihood.is_finite());
assert_eq!(mu_mixed, trace3.get_f64(&addr!("mu")).unwrap());
assert_eq!(success_mixed, trace3.get_bool(&addr!("success")).unwrap());
}
#[test]
fn test_handler_compatibility_complete() {
let mut rng = StdRng::seed_from_u64(42);
let model = || {
sample(addr!("x"), Normal::new(0.0, 1.0).unwrap()).bind(|x| {
sample(addr!("y"), Bernoulli::new(0.6).unwrap()).bind(move |y| {
observe(addr!("obs"), Normal::new(x, 0.5).unwrap(), 0.5)
.bind(move |_| factor(if y { 0.1 } else { -0.1 }))
.map(move |_| (x, y))
})
})
};
let prior_handler = runtime::interpreters::PriorHandler {
rng: &mut rng,
trace: runtime::trace::Trace::default(),
};
let (baseline_result, baseline_trace) = runtime::handler::run(prior_handler, model());
let replay_handler = runtime::interpreters::ReplayHandler {
rng: &mut rng,
base: baseline_trace.clone(),
trace: runtime::trace::Trace::default(),
};
let (replay_result, replay_trace) = runtime::handler::run(replay_handler, model());
assert_eq!(baseline_result, replay_result);
assert_eq!(
baseline_trace.get_f64(&addr!("x")),
replay_trace.get_f64(&addr!("x"))
);
assert_eq!(
baseline_trace.get_bool(&addr!("y")),
replay_trace.get_bool(&addr!("y"))
);
let safe_replay_handler = runtime::interpreters::SafeReplayHandler {
rng: &mut rng,
base: baseline_trace.clone(),
trace: runtime::trace::Trace::default(),
warn_on_mismatch: false,
};
let (safe_replay_result, safe_replay_trace) =
runtime::handler::run(safe_replay_handler, model());
assert_eq!(baseline_result, safe_replay_result);
assert_eq!(
baseline_trace.get_f64(&addr!("x")),
safe_replay_trace.get_f64(&addr!("x"))
);
assert_eq!(
baseline_trace.get_bool(&addr!("y")),
safe_replay_trace.get_bool(&addr!("y"))
);
let score_handler = runtime::interpreters::ScoreGivenTrace {
base: baseline_trace.clone(),
trace: runtime::trace::Trace::default(),
};
let (score_result, score_trace) = runtime::handler::run(score_handler, model());
assert_eq!(baseline_result, score_result);
assert_eq!(
baseline_trace.get_f64(&addr!("x")),
score_trace.get_f64(&addr!("x"))
);
assert_eq!(
baseline_trace.get_bool(&addr!("y")),
score_trace.get_bool(&addr!("y"))
);
let safe_score_handler = runtime::interpreters::SafeScoreGivenTrace {
base: baseline_trace.clone(),
trace: runtime::trace::Trace::default(),
warn_on_error: false,
};
let (safe_score_result, safe_score_trace) = runtime::handler::run(safe_score_handler, model());
assert_eq!(baseline_result, safe_score_result);
assert_eq!(
baseline_trace.get_f64(&addr!("x")),
safe_score_trace.get_f64(&addr!("x"))
);
assert_eq!(
baseline_trace.get_bool(&addr!("y")),
safe_score_trace.get_bool(&addr!("y"))
);
assert!(baseline_trace.total_log_weight().is_finite());
assert!(replay_trace.total_log_weight().is_finite());
assert!(safe_replay_trace.total_log_weight().is_finite());
assert!(score_trace.total_log_weight().is_finite());
assert!(safe_score_trace.total_log_weight().is_finite());
}
#[test]
fn test_model_composition_complete() {
let mut rng = StdRng::seed_from_u64(42);
let and_then_model = sample(addr!("x"), Normal::new(0.0, 1.0).unwrap())
.and_then(|x| sample(addr!("y"), Normal::new(x, 0.5).unwrap()))
.map(|y| y * 2.0);
let handler1 = runtime::interpreters::PriorHandler {
rng: &mut rng,
trace: runtime::trace::Trace::default(),
};
let (and_then_result, trace1) = runtime::handler::run(handler1, and_then_model);
let _x_val = trace1.get_f64(&addr!("x")).unwrap();
let y_val = trace1.get_f64(&addr!("y")).unwrap();
assert_eq!(and_then_result, y_val * 2.0);
let data = vec![1.0, 2.0, 3.0];
let traverse_model = traverse_vec(data.clone(), |x| {
let idx = (x as usize).saturating_sub(1); sample(addr!("traverse", idx), Normal::new(x, 0.5).unwrap())
});
let handler2 = runtime::interpreters::PriorHandler {
rng: &mut rng,
trace: runtime::trace::Trace::default(),
};
let (traverse_results, trace2) = runtime::handler::run(handler2, traverse_model);
assert_eq!(traverse_results.len(), 3);
for (i, _original_val) in data.iter().enumerate() {
let addr = addr!("traverse", i);
let sampled_val = trace2.get_f64(&addr).unwrap();
assert_eq!(traverse_results[i], sampled_val);
assert!(sampled_val.is_finite());
}
let complex_composition = sample(addr!("a"), Normal::new(0.0, 1.0).unwrap())
.bind(|a| sample(addr!("b"), Normal::new(a, 0.5).unwrap()).map(move |b| (a, b)))
.and_then(|(a, b)| zip(pure(a + b), pure(a - b)))
.bind(|(sum, diff)| sequence_vec(vec![pure(sum), pure(diff), pure(sum * diff)]))
.map(|results| results.iter().sum::<f64>());
let handler3 = runtime::interpreters::PriorHandler {
rng: &mut rng,
trace: runtime::trace::Trace::default(),
};
let (complex_result, trace3) = runtime::handler::run(handler3, complex_composition);
let a_val = trace3.get_f64(&addr!("a")).unwrap();
let b_val = trace3.get_f64(&addr!("b")).unwrap();
let sum = a_val + b_val;
let diff = a_val - b_val;
let expected_result = sum + diff + (sum * diff);
assert!((complex_result - expected_result).abs() < 1e-12);
assert!(complex_result.is_finite());
}