extern crate alloc;
use alloc::vec::Vec;
use crate::analysis::{compute_effect_estimate, compute_effect_estimate_analytical};
use crate::math::sqrt;
use crate::result::{EffectEstimate, MeasurementQuality};
use crate::types::{Matrix9, Vector9};
#[derive(Clone, Debug)]
pub struct Posterior {
pub delta_post: Vector9,
pub lambda_post: Matrix9,
pub delta_draws: Vec<Vector9>,
pub leak_probability: f64,
pub theta: f64,
pub n: usize,
pub lambda_mean: Option<f64>,
pub lambda_mixing_ok: Option<bool>,
pub kappa_mean: Option<f64>,
pub kappa_cv: Option<f64>,
pub kappa_ess: Option<f64>,
pub kappa_mixing_ok: Option<bool>,
}
impl Posterior {
#[allow(clippy::too_many_arguments)]
pub fn new(
delta_post: Vector9,
lambda_post: Matrix9,
delta_draws: Vec<Vector9>,
leak_probability: f64,
theta: f64,
n: usize,
) -> Self {
Self {
delta_post,
lambda_post,
delta_draws,
leak_probability,
theta,
n,
lambda_mean: None, lambda_mixing_ok: None, kappa_mean: None, kappa_cv: None, kappa_ess: None, kappa_mixing_ok: None, }
}
#[allow(clippy::too_many_arguments)]
pub fn new_with_gibbs(
delta_post: Vector9,
lambda_post: Matrix9,
delta_draws: Vec<Vector9>,
leak_probability: f64,
theta: f64,
n: usize,
lambda_mean: f64,
lambda_mixing_ok: bool,
kappa_mean: f64,
kappa_cv: f64,
kappa_ess: f64,
kappa_mixing_ok: bool,
) -> Self {
Self {
delta_post,
lambda_post,
delta_draws,
leak_probability,
theta,
n,
lambda_mean: Some(lambda_mean),
lambda_mixing_ok: Some(lambda_mixing_ok),
kappa_mean: Some(kappa_mean),
kappa_cv: Some(kappa_cv),
kappa_ess: Some(kappa_ess),
kappa_mixing_ok: Some(kappa_mixing_ok),
}
}
pub fn max_effect_ns(&self) -> f64 {
self.delta_post
.iter()
.map(|x| x.abs())
.fold(0.0_f64, f64::max)
}
pub fn to_effect_estimate(&self) -> EffectEstimate {
if !self.delta_draws.is_empty() {
compute_effect_estimate(&self.delta_draws, self.theta)
} else {
compute_effect_estimate_analytical(&self.delta_post, &self.lambda_post, self.theta)
}
}
pub fn measurement_quality(&self) -> MeasurementQuality {
let max_se = (0..9)
.map(|k| sqrt(self.lambda_post[(k, k)].max(1e-12)))
.fold(0.0_f64, f64::max);
MeasurementQuality::from_mde_ns(max_se * 2.0)
}
pub fn to_summary(&self) -> crate::ffi_summary::PosteriorSummary {
let effect = self.to_effect_estimate();
crate::ffi_summary::PosteriorSummary {
max_effect_ns: effect.max_effect_ns,
ci_low_ns: effect.credible_interval_ns.0,
ci_high_ns: effect.credible_interval_ns.1,
leak_probability: self.leak_probability,
n: self.n,
lambda_mean: self.lambda_mean.unwrap_or(1.0),
lambda_mixing_ok: self.lambda_mixing_ok.unwrap_or(true),
kappa_mean: self.kappa_mean.unwrap_or(1.0),
kappa_cv: self.kappa_cv.unwrap_or(0.0),
kappa_ess: self.kappa_ess.unwrap_or(0.0),
kappa_mixing_ok: self.kappa_mixing_ok.unwrap_or(true),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_posterior_accessors() {
let delta_post =
Vector9::from_row_slice(&[10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0]);
let lambda_post = Matrix9::identity();
let posterior = Posterior::new(
delta_post,
lambda_post,
Vec::new(), 0.75,
5.0, 1000,
);
assert_eq!(posterior.leak_probability, 0.75);
assert_eq!(posterior.n, 1000);
assert!((posterior.max_effect_ns() - 10.0).abs() < 1e-10);
}
#[test]
fn test_posterior_clone() {
let delta_post = Vector9::from_row_slice(&[5.0; 9]);
let lambda_post = Matrix9::identity();
let posterior = Posterior::new(
delta_post,
lambda_post,
Vec::new(), 0.5,
5.0, 500,
);
let cloned = posterior.clone();
assert_eq!(cloned.leak_probability, posterior.leak_probability);
assert_eq!(cloned.max_effect_ns(), posterior.max_effect_ns());
}
#[test]
fn test_effect_estimate_from_draws() {
let delta_post = Vector9::from_row_slice(&[10.0; 9]);
let lambda_post = Matrix9::identity();
let delta_draws: Vec<Vector9> = (0..100)
.map(|_| {
Vector9::from_row_slice(&[10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0])
})
.collect();
let posterior = Posterior::new(delta_post, lambda_post, delta_draws, 0.99, 5.0, 1000);
let effect = posterior.to_effect_estimate();
assert!(effect.max_effect_ns > 9.0, "max effect should be around 10");
}
}