use dry::macro_for;
use itertools::Itertools;
use ndarray::prelude::*;
use rayon::prelude::*;
use crate::{
datasets::{CatTrj, CatTrjs, CatWtdTrj, CatWtdTrjs, Dataset},
estimators::{CSSEstimator, ParCSSEstimator, SSE},
models::CatCIMS,
types::{Error, Result, Set},
utils::MI,
};
impl CSSEstimator<CatCIMS> for SSE<'_, CatTrj> {
fn fit(&self, x: &Set<usize>, z: &Set<usize>) -> Result<CatCIMS> {
if !x.is_disjoint(z) {
return Err(Error::InvalidParameter(
"x,z",
"Variables and conditioning variables must be disjoint.",
));
}
let shape = self.dataset.shape();
let m_idx_x = MI::new(x.iter().map(|&i| shape[i]));
let m_idx_z = MI::new(z.iter().map(|&i| shape[i]));
let s_x = m_idx_x.shape().product();
let s_z = m_idx_z.shape().product();
let mut n_xz: Array3<usize> = Array::zeros((s_z, s_x, s_x));
let mut t_xz: Array2<f64> = Array::zeros((s_z, s_x));
self.dataset
.values()
.rows()
.into_iter()
.zip(self.dataset.times())
.tuple_windows()
.for_each(|((e_i, t_i), (e_j, t_j))| {
let idx_x_i = m_idx_x.ravel(x.iter().map(|&i| e_i[i] as usize));
let idx_x_j = m_idx_x.ravel(x.iter().map(|&i| e_j[i] as usize));
let idx_z = m_idx_z.ravel(z.iter().map(|&i| e_i[i] as usize));
n_xz[[idx_z, idx_x_i, idx_x_j]] += (idx_x_i != idx_x_j) as usize;
t_xz[[idx_z, idx_x_i]] += t_j - t_i;
});
let n_xz = n_xz.mapv(|x| x as f64);
let n = n_xz.sum();
CatCIMS::new(n_xz, t_xz, n)
}
}
impl CSSEstimator<CatCIMS> for SSE<'_, CatWtdTrj> {
fn fit(&self, x: &Set<usize>, z: &Set<usize>) -> Result<CatCIMS> {
let w = self.dataset.weight();
let s = SSE::new(self.dataset.trajectory()).fit(x, z)?;
let n_xz = s.fitted_conditional_counts();
let t_xz = s.fitted_conditional_times();
let n = s.fitted_size();
CatCIMS::new(n_xz * w, t_xz * w, n * w)
}
}
macro_for!($type in [CatTrjs, CatWtdTrjs] {
impl CSSEstimator<CatCIMS> for SSE<'_, $type> {
fn fit(&self, x: &Set<usize>, z: &Set<usize>) -> Result<CatCIMS> {
let shape = self.dataset.shape();
let s_x = x.iter().map(|&i| shape[i]).product();
let s_z = z.iter().map(|&i| shape[i]).product();
let s = CatCIMS::new(
Array3::zeros((s_z, s_x, s_x)),
Array2::zeros((s_z, s_x)),
0.,
)?;
self.dataset
.into_iter()
.try_fold(s, |s_a, trj_b| Ok(s_a + SSE::new(trj_b).fit(x, z)?))
}
}
impl ParCSSEstimator<CatCIMS> for SSE<'_, $type> {
fn par_fit(&self, x: &Set<usize>, z: &Set<usize>) -> Result<CatCIMS> {
let shape = self.dataset.shape();
let s_x = x.iter().map(|&i| shape[i]).product();
let s_z = z.iter().map(|&i| shape[i]).product();
let s = CatCIMS::new(
Array3::zeros((s_z, s_x, s_x)),
Array2::zeros((s_z, s_x)),
0.,
)?;
self.dataset
.par_iter()
.try_fold(
|| s.clone(),
|s_a, trj_b| Ok(s_a + SSE::new(trj_b).fit(x, z)?),
)
.try_reduce(
|| s.clone(),
|s_a, s_b| Ok(s_a + s_b)
)
}
}
});