use dry::macro_for;
use ndarray::prelude::*;
use ndarray_linalg::Determinant;
use crate::{
datasets::{GaussIncTable, GaussTable, GaussWtdTable},
estimators::{BE, CPDEstimator, CSSEstimator, ParCPDEstimator, ParCSSEstimator, SSE},
models::{GaussCPD, GaussCPDP, GaussCPDS, Labelled},
types::{EPSILON, Error, LN_2_PI, Labels, Result, Set},
utils::PseudoInverse,
};
impl BE<'_, GaussTable, f64> {
fn fit(
labels: &Labels,
x: &Set<usize>,
z: &Set<usize>,
sample_statistics: GaussCPDS,
prior: f64,
) -> Result<GaussCPD> {
if prior < 0.0 {
return Err(Error::InvalidParameter("prior", "must be non-negative"));
}
let (mu_x, mu_z, s_xx, s_xz, s_zz, n) = (
sample_statistics.fitted_response_mean(),
sample_statistics.fitted_design_mean(),
sample_statistics.fitted_response_covariance(),
sample_statistics.fitted_cross_covariance(),
sample_statistics.fitted_design_covariance(),
sample_statistics.fitted_size(),
);
let nu = prior;
let n_post = n + nu;
let mu_x_post = mu_x * (n / n_post);
let mu_z_post = mu_z * (n / n_post);
let f = n * nu / n_post;
let mut s_xx_post = s_xx.clone();
s_xx_post.diag_mut().iter_mut().for_each(|d| *d += nu);
s_xx_post = s_xx_post
+ f * &mu_x
.view()
.insert_axis(Axis(1))
.dot(&mu_x.view().insert_axis(Axis(0)));
let mut s_zz_post = s_zz.clone();
s_zz_post.diag_mut().iter_mut().for_each(|d| *d += nu);
s_zz_post = s_zz_post
+ f * &mu_z
.view()
.insert_axis(Axis(1))
.dot(&mu_z.view().insert_axis(Axis(0)));
let s_xz_post = s_xz
+ f * &mu_x
.view()
.insert_axis(Axis(1))
.dot(&mu_z.view().insert_axis(Axis(0)));
let (a, b, s) = if z.is_empty() {
let a = Array2::zeros((x.len(), 0));
let b = mu_x_post.clone();
let s = s_xx_post / n_post;
(a, b, s)
} else {
let s_zz_pinv = s_zz_post.pinv()?;
let a = s_xz_post.dot(&s_zz_pinv);
let b = mu_x_post - &a.dot(&mu_z_post);
let s = (s_xx_post - &a.dot(&s_xz_post.t())) / n_post;
(a, b, s)
};
let mut s = s;
s.diag_mut().mapv_inplace(|x| x.max(0.));
let t = s.diag().sum() / s.nrows() as f64;
*s.diag_mut() += f64::max(EPSILON, t * EPSILON);
s = (&s + &s.t()) / 2.;
let p = x.len() as f64;
let (_, ln_det) = s
.sln_det()
.map_err(|e| Error::Linalg(&format!("Failed to compute determinant of S: {e}")))?;
let sample_log_likelihood = -0.5 * n_post * (p * LN_2_PI + ln_det + p);
let parameters = GaussCPDP::new(a, b, s)?;
let conditioning_labels = z.iter().map(|&i| labels[i].clone()).collect();
let labels = x.iter().map(|&i| labels[i].clone()).collect();
let sample_statistics = Some(sample_statistics);
let sample_log_likelihood = Some(sample_log_likelihood);
GaussCPD::with_optionals(
labels,
conditioning_labels,
parameters,
sample_statistics,
sample_log_likelihood,
)
}
}
macro_for!($type in [GaussTable, GaussIncTable, GaussWtdTable] {
impl CPDEstimator<GaussCPD> for BE<'_, $type, f64> {
fn fit(&self, x: &Set<usize>, z: &Set<usize>) -> Result<GaussCPD> {
let labels = self.dataset.labels();
let prior = self.prior;
let sample_statistics = SSE::new(self.dataset);
let sample_statistics = sample_statistics.with_missing_method(
self.missing_method,
self.missing_mechanism.clone()
)?;
let sample_statistics = sample_statistics.fit(x, z)?;
BE::<'_, GaussTable, f64>::fit(labels, x, z, sample_statistics, prior)
}
}
impl ParCPDEstimator<GaussCPD> for BE<'_, $type, f64> {
fn par_fit(&self, x: &Set<usize>, z: &Set<usize>) -> Result<GaussCPD> {
let labels = self.dataset.labels();
let prior = self.prior;
let sample_statistics = SSE::new(self.dataset);
let sample_statistics = sample_statistics.with_missing_method(
self.missing_method,
self.missing_mechanism.clone()
)?;
let sample_statistics = sample_statistics.par_fit(x, z)?;
BE::<'_, GaussTable, f64>::fit(labels, x, z, sample_statistics, prior)
}
}
impl CPDEstimator<GaussCPD> for BE<'_, $type, ()> {
#[inline]
fn fit(&self, x: &Set<usize>, z: &Set<usize>) -> Result<GaussCPD> {
self.clone().with_prior(1.0).fit(x, z)
}
}
impl ParCPDEstimator<GaussCPD> for BE<'_, $type, ()> {
#[inline]
fn par_fit(&self, x: &Set<usize>, z: &Set<usize>) -> Result<GaussCPD> {
self.clone().with_prior(1.0).par_fit(x, z)
}
}
});