use crate::CausalTensor;
use crate::CausalTensorError;
use alloc::format;
use alloc::vec::Vec;
use deep_causality_algebra::RealField;
use deep_causality_num::FromPrimitive;
use deep_causality_stats::{
StatsError, StatsErrorEnum, column_means, conditional_variance, covariance_matrix,
gaussian_log_density, log_sum_exp,
};
pub trait CausalTensorStatsExt<T> {
fn sample_mean(&self) -> Result<CausalTensor<T>, CausalTensorError>;
fn sample_covariance(&self) -> Result<CausalTensor<T>, CausalTensorError>;
fn logsumexp(&self) -> T;
fn gaussian_log_density(
&self,
mean: T,
variance: T,
) -> Result<CausalTensor<T>, CausalTensorError>;
fn conditional_variance(
&self,
target: usize,
parents: &[usize],
ridge: T,
) -> Result<T, CausalTensorError>;
}
impl<T> CausalTensorStatsExt<T> for CausalTensor<T>
where
T: RealField + FromPrimitive,
{
fn sample_mean(&self) -> Result<CausalTensor<T>, CausalTensorError> {
let (n, k) = observations_shape(self)?;
let means = column_means(self.as_slice(), n, k).map_err(stats_error)?;
Ok(CausalTensor::from_slice(&means, &[k]))
}
fn sample_covariance(&self) -> Result<CausalTensor<T>, CausalTensorError> {
let (n, k) = observations_shape(self)?;
let cov = covariance_matrix(self.as_slice(), n, k).map_err(stats_error)?;
Ok(CausalTensor::from_slice(&cov, &[k, k]))
}
fn logsumexp(&self) -> T {
log_sum_exp(self.as_slice())
}
fn gaussian_log_density(
&self,
mean: T,
variance: T,
) -> Result<CausalTensor<T>, CausalTensorError> {
if self.is_empty() {
return Ok(CausalTensor::from_slice(&[], self.shape()));
}
let var = if variance > T::zero() {
variance
} else {
variance_floor::<T>()
};
let new_data: Vec<T> = self
.as_slice()
.iter()
.map(|&x| gaussian_log_density(x, mean, var).map_err(stats_error))
.collect::<Result<Vec<T>, CausalTensorError>>()?;
Ok(CausalTensor::from_slice(&new_data, self.shape()))
}
fn conditional_variance(
&self,
target: usize,
parents: &[usize],
ridge: T,
) -> Result<T, CausalTensorError> {
let shape = self.shape();
if shape.len() != 2 || shape[0] != shape[1] {
return Err(CausalTensorError::DimensionMismatch);
}
let m = shape[0];
if target >= m || !parents.iter().all(|&p| p < m) {
return Err(CausalTensorError::IndexOutOfBounds);
}
conditional_variance(self.as_slice(), m, target, parents, ridge).map_err(stats_error)
}
}
fn stats_error(error: StatsError) -> CausalTensorError {
match error.kind() {
StatsErrorEnum::EmptyInput(_) => CausalTensorError::EmptyTensor,
StatsErrorEnum::DimensionMismatch(_) => CausalTensorError::DimensionMismatch,
other => CausalTensorError::InvalidParameter(format!("{other:?}")),
}
}
fn variance_floor<T>() -> T
where
T: RealField + FromPrimitive,
{
<T as FromPrimitive>::from_f64(1e-12).expect("1e-12 is representable in every RealField")
}
fn observations_shape<T>(tensor: &CausalTensor<T>) -> Result<(usize, usize), CausalTensorError> {
let shape = tensor.shape();
if shape.len() != 2 {
return Err(CausalTensorError::DimensionMismatch);
}
let (n, k) = (shape[0], shape[1]);
if n == 0 || k == 0 {
return Err(CausalTensorError::EmptyTensor);
}
Ok((n, k))
}