Chapter 17: Bayesian Methods

Contract: apr-book-ch17

Run: cargo run -p aprender-core --example ch17_bayesian

#![allow(clippy::disallowed_methods)]
//! Chapter 17: Bayesian Methods
//!
//! Demonstrates conjugate prior updates with NormalInverseGamma.
//! Citation: Efron, "Bayesians, Frequentists, and Scientists," arXiv:math/0504499
//! Contract: contracts/apr-book-ch17-v1.yaml (v2 — api_calls enforced)

use aprender::bayesian::{BetaBinomial, NormalInverseGamma};

fn main() {
    // --- BetaBinomial conjugate model ---
    let bb = BetaBinomial::new(2.0, 2.0).expect("valid Beta prior");
    println!("BetaBinomial prior: Beta(2, 2)");
    let _ = bb; // Instantiation contract

    // --- NormalInverseGamma conjugate model ---
    // Prior: mu=0, kappa=1, alpha=3, beta=2
    let mut nig = NormalInverseGamma::new(0.0, 1.0, 3.0, 2.0)
        .expect("valid NIG prior parameters");

    let prior_mu = nig.posterior_mean_mu();
    let prior_var = nig.posterior_mean_variance();
    println!("\nNormalInverseGamma prior:");
    println!("  mu (location):  {prior_mu:.3}");
    println!("  E[variance]:    {:.3}", prior_var.unwrap_or(f32::NAN));

    // Observe data centered around 5.0
    let data: Vec<f32> = vec![4.8, 5.1, 5.3, 4.9, 5.0, 5.2, 4.7, 5.1];
    let data_mean: f32 = data.iter().sum::<f32>() / data.len() as f32;
    println!("\nObserved data: {} samples, mean={data_mean:.2}", data.len());

    // Update posterior with data
    nig.update(&data);

    let post_mu = nig.posterior_mean_mu();
    let post_var = nig.posterior_mean_variance();
    println!("\nPosterior after update:");
    println!("  mu (location):  {post_mu:.3}");
    println!("  E[variance]:    {:.3}", post_var.unwrap_or(f32::NAN));

    // Posterior mean should shift toward data mean (5.0)
    let prior_distance = (prior_mu - data_mean).abs();
    let post_distance = (post_mu - data_mean).abs();
    println!("\n  Prior distance to data mean: {prior_distance:.3}");
    println!("  Posterior distance to data mean: {post_distance:.3}");
    assert!(
        post_distance < prior_distance,
        "Posterior must move toward data: {post_distance:.3} < {prior_distance:.3}"
    );

    // Predictive value
    let predictive = nig.posterior_predictive();
    println!("  Posterior predictive: {predictive:.3}");

    println!("\nChapter 17 contracts: PASSED");
}