use crate::{MattenError, Tensor};
fn population_variance(data: &[f64]) -> f64 {
let n = data.len() as f64;
let mean = data.iter().sum::<f64>() / n;
data.iter()
.map(|x| {
let d = x - mean;
d * d
})
.sum::<f64>()
/ n
}
fn reject_dynamic_stat(t: &Tensor, operation: &'static str) -> Result<(), MattenError> {
#[cfg(feature = "dynamic")]
if t.is_dynamic() {
return Err(MattenError::Unsupported {
operation,
message: format!(
"{operation} is not supported on dynamic tensors; call try_numeric() first"
),
});
}
#[cfg(not(feature = "dynamic"))]
let _ = (t, operation);
Ok(())
}
fn variance_axis_impl(
t: &Tensor,
axis: usize,
operation: &'static str,
) -> Result<Tensor, MattenError> {
reject_dynamic_stat(t, operation)?;
let rank = t.shape.len();
if axis >= rank {
return Err(MattenError::Shape {
operation,
message: format!("axis {axis} is out of range for a rank-{rank} tensor"),
});
}
let axis_len = t.shape[axis];
let outer: usize = t.shape[..axis].iter().product();
let inner: usize = t.shape[axis + 1..].iter().product();
let mut data = Vec::with_capacity(outer * inner);
for o in 0..outer {
let base = o * axis_len * inner;
for i in 0..inner {
let mut sum = 0.0;
for a in 0..axis_len {
sum += t.data[base + a * inner + i];
}
let mean = sum / axis_len as f64;
let mut acc = 0.0;
for a in 0..axis_len {
let d = t.data[base + a * inner + i] - mean;
acc += d * d;
}
data.push(acc / axis_len as f64);
}
}
let out_shape: Vec<usize> = t.shape[..axis]
.iter()
.chain(&t.shape[axis + 1..])
.copied()
.collect();
Ok(Tensor {
data,
shape: out_shape,
#[cfg(feature = "dynamic")]
dynamic: None,
})
}
impl Tensor {
#[must_use]
pub fn var(&self) -> f64 {
self.try_var().unwrap_or_else(|e| panic!("{e}"))
}
pub fn try_var(&self) -> Result<f64, MattenError> {
reject_dynamic_stat(self, "var")?;
if self.data.is_empty() {
return Err(MattenError::InvalidArgument {
operation: "var",
argument: "self",
message: "variance is undefined for an empty tensor".to_string(),
});
}
Ok(population_variance(&self.data))
}
#[must_use]
pub fn std(&self) -> f64 {
self.try_std().unwrap_or_else(|e| panic!("{e}"))
}
pub fn try_std(&self) -> Result<f64, MattenError> {
reject_dynamic_stat(self, "std")?;
if self.data.is_empty() {
return Err(MattenError::InvalidArgument {
operation: "std",
argument: "self",
message: "standard deviation is undefined for an empty tensor".to_string(),
});
}
Ok(population_variance(&self.data).sqrt())
}
#[must_use]
pub fn var_axis(&self, axis: usize) -> Tensor {
self.try_var_axis(axis).unwrap_or_else(|e| panic!("{e}"))
}
pub fn try_var_axis(&self, axis: usize) -> Result<Tensor, MattenError> {
variance_axis_impl(self, axis, "var_axis")
}
#[must_use]
pub fn std_axis(&self, axis: usize) -> Tensor {
self.try_std_axis(axis).unwrap_or_else(|e| panic!("{e}"))
}
pub fn try_std_axis(&self, axis: usize) -> Result<Tensor, MattenError> {
let mut v = variance_axis_impl(self, axis, "std_axis")?;
for x in &mut v.data {
*x = x.sqrt();
}
Ok(v)
}
}