use ndarray::prelude::*;
use ndarray_linalg::{Cholesky, SVD, UPLO};
use rand::{Rng, RngExt};
use rand_distr::Normal;
use crate::{
models::{GaussCPD, GaussCPDP},
random::Random,
types::{Error, Labels, Result},
};
struct RngGaussCPDP<'a, R>
where
R: Rng,
{
rng: &'a mut R,
x: usize,
z: usize,
s_a: f64,
s_b: f64,
e: f64,
}
impl<'a, R> RngGaussCPDP<'a, R>
where
R: Rng,
{
fn new(rng: &'a mut R, x: usize, z: usize, s_a: f64, s_b: f64, e: f64) -> Result<Self> {
if s_a <= 0.0 {
return Err(Error::InvalidParameter("s_a", "must be positive"));
}
if s_b <= 0.0 {
return Err(Error::InvalidParameter("s_b", "must be positive"));
}
if e <= 0.0 {
return Err(Error::InvalidParameter("e", "must be positive"));
}
Ok(Self {
rng,
x,
z,
s_a,
s_b,
e,
})
}
}
impl<R> Random for RngGaussCPDP<'_, R>
where
R: Rng,
{
type Output = Result<GaussCPDP>;
fn random(&mut self) -> Self::Output {
let mut a = if self.x > 0 && self.z > 0 {
let dist_a = Normal::new(0.0, self.s_a)
.map_err(|e| Error::InvalidParameter("s_a", &e.to_string()))?;
Array2::from_shape_fn((self.x, self.z), |_| self.rng.sample(dist_a))
} else {
Array2::zeros((self.x, self.z))
};
if self.x > 0 && self.z > 0 {
let (_, s, _) = a
.svd(false, false)
.map_err(|e| Error::Linalg(&format!("Failed to compute SVD: {e}")))?;
let spectral_norm = s[0];
if spectral_norm > 1.0 {
a /= spectral_norm;
}
}
let b = if self.x > 0 {
let dist_b = Normal::new(0.0, self.s_b)
.map_err(|e| Error::InvalidParameter("s_b", &e.to_string()))?;
Array1::from_shape_fn(self.x, |_| self.rng.sample(dist_b))
} else {
Array1::zeros(self.x)
};
let s = if self.x > 0 {
let mut s;
let dist_m = Normal::new(0.0, 1.0)
.map_err(|e| Error::InvalidParameter("sigma", &e.to_string()))?;
loop {
let m = Array2::from_shape_fn((self.x, self.x), |_| self.rng.sample(dist_m));
s = m.dot(&m.t());
for i in 0..self.x {
s[[i, i]] += self.e;
}
if s.cholesky(UPLO::Lower).is_ok() {
break;
}
}
s
} else {
Array2::zeros((self.x, self.x))
};
GaussCPDP::new(a, b, s)
}
}
pub struct RngGaussCPD<'a, R>
where
R: Rng,
{
rng: &'a mut R,
labels: &'a Labels,
conditioning_labels: &'a Labels,
s_a: f64,
s_b: f64,
e: f64,
}
impl<'a, R> RngGaussCPD<'a, R>
where
R: Rng,
{
pub fn new(
rng: &'a mut R,
labels: &'a Labels,
conditioning_labels: &'a Labels,
s_a: f64,
s_b: f64,
e: f64,
) -> Result<Self> {
Ok(Self {
rng,
labels,
conditioning_labels,
s_a,
s_b,
e,
})
}
}
impl<R> Random for RngGaussCPD<'_, R>
where
R: Rng,
{
type Output = Result<GaussCPD>;
fn random(&mut self) -> Self::Output {
let mut rng_params = RngGaussCPDP::new(
self.rng,
self.labels.len(),
self.conditioning_labels.len(),
self.s_a,
self.s_b,
self.e,
)?;
let parameters = rng_params.random()?;
GaussCPD::new(
self.labels.clone(),
self.conditioning_labels.clone(),
parameters,
)
}
}