Skip to content

Beta's `set_alpha` and `set_beta` violate invariants

Beta is defined as

pub struct Beta {
    alpha: f64,
    beta: f64,
    #[cfg_attr(feature = "serde1", serde(skip))]
    /// Cached ln(Beta(a, b))
    ln_beta_ab: OnceLock<f64>,
}

The OnceLock means the ln_beta_ab field can be written once, after which it's immutable. This is great, except that there are also set_alpha and set_beta methods. These can change the parameters, but are left with no way to update the cached ln_beta_ab.

Say we do

    // Start with a Beta
    let a1 = rng.gen::<f64>().inv();
    let b1 = rng.gen::<f64>().inv();
    let mut beta1 = Beta::new(a1, b1).unwrap();
    
    // Any value in the unit interval
    let x: f64 = rng.gen();

    // Evaluate the pdf to force computation of `ln_beta_ab`
    let _ = beta1.pdf(&x);

    // Next we'll `set_alpha` and `set_beta` to these, and compare this with a fresh Beta
    let a2 = rng.gen::<f64>().inv();
    let b2 = rng.gen::<f64>().inv();

    // Setting the new values
    if let Ok(_) =  beta1.set_alpha(a2) {};
    if let Ok(_) = beta1.set_beta(b2) {};

    // ... and here's the fresh version
    let beta2 = Beta::new(a2, b2).unwrap();

    // These results should match
    println!("beta1 pdf at x = {:?} is {:?}", x, beta1.pdf(&x));
    println!("beta2 pdf at x = {:?} is {:?}", x, beta2.pdf(&x));

Running this gives results like

beta1 pdf at x = 0.186023700091557 is 6.787374697139961
beta2 pdf at x = 0.186023700091557 is 1.624002035952952

We'd expect these results to match, but they don't.

To fix this we need to have ln_beta_ab under... maybe a RwLock? Whatever we do, we need to be sure it's updated when calling set_alpha or set_beta.