use ndarray::{Array1, Array2};
use serde::{Deserialize, Serialize};
use std::ops::{Deref, DerefMut};
pub use crate::Dispersion;
pub fn se_from_covariance(cov: &Array2<f64>) -> Array1<f64> {
Array1::from_iter(cov.diag().iter().map(|&v| v.max(0.0).sqrt()))
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default)]
#[serde(transparent)]
pub struct PhiScaledCovariance(pub Array2<f64>);
impl PhiScaledCovariance {
#[inline]
pub fn wrap(cov: Array2<f64>) -> Self {
Self(cov)
}
#[inline]
pub fn as_array(&self) -> &Array2<f64> {
&self.0
}
#[inline]
pub fn into_array(self) -> Array2<f64> {
self.0
}
}
impl From<Array2<f64>> for PhiScaledCovariance {
#[inline]
fn from(cov: Array2<f64>) -> Self {
Self(cov)
}
}
impl From<PhiScaledCovariance> for Array2<f64> {
#[inline]
fn from(cov: PhiScaledCovariance) -> Self {
cov.0
}
}
impl Deref for PhiScaledCovariance {
type Target = Array2<f64>;
#[inline]
fn deref(&self) -> &Array2<f64> {
&self.0
}
}
impl DerefMut for PhiScaledCovariance {
#[inline]
fn deref_mut(&mut self) -> &mut Array2<f64> {
&mut self.0
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default)]
#[serde(transparent)]
pub struct UnscaledPrecision(pub Array2<f64>);
impl UnscaledPrecision {
#[inline]
pub fn wrap(hessian: Array2<f64>) -> Self {
Self(hessian)
}
#[inline]
pub fn as_array(&self) -> &Array2<f64> {
&self.0
}
#[inline]
pub fn into_array(self) -> Array2<f64> {
self.0
}
}
impl From<Array2<f64>> for UnscaledPrecision {
#[inline]
fn from(h: Array2<f64>) -> Self {
Self(h)
}
}
impl From<UnscaledPrecision> for Array2<f64> {
#[inline]
fn from(h: UnscaledPrecision) -> Self {
h.0
}
}
impl Deref for UnscaledPrecision {
type Target = Array2<f64>;
#[inline]
fn deref(&self) -> &Array2<f64> {
&self.0
}
}
impl DerefMut for UnscaledPrecision {
#[inline]
fn deref_mut(&mut self) -> &mut Array2<f64> {
&mut self.0
}
}
pub trait DispersionExt {
fn inv_phi(self) -> f64;
fn sqrt_phi(self) -> f64;
}
impl DispersionExt for Dispersion {
#[inline]
fn inv_phi(self) -> f64 {
1.0 / self.phi().max(1e-300)
}
#[inline]
fn sqrt_phi(self) -> f64 {
self.phi().max(0.0).sqrt()
}
}
#[cfg(test)]
mod tests {
use super::*;
use ndarray::array;
#[test]
fn se_from_diagonal_matrix_is_sqrt_of_diagonal() {
let cov = array![[4.0_f64, 0.0], [0.0, 9.0]];
let se = se_from_covariance(&cov);
assert_eq!(se.len(), 2);
assert!((se[0] - 2.0).abs() < 1e-14);
assert!((se[1] - 3.0).abs() < 1e-14);
}
#[test]
fn se_clamps_negative_diagonal_to_zero() {
let cov = array![[1.0_f64, 0.0], [0.0, -1e-15]];
let se = se_from_covariance(&cov);
assert!(se[1].is_finite());
assert_eq!(se[1], 0.0);
}
#[test]
fn phi_scaled_covariance_wrap_and_as_array_round_trip() {
let m = array![[1.0_f64, 2.0], [3.0, 4.0]];
let wrapped = PhiScaledCovariance::wrap(m.clone());
assert_eq!(*wrapped.as_array(), m);
}
#[test]
fn phi_scaled_covariance_deref_gives_array2() {
let m = array![[5.0_f64]];
let wrapped = PhiScaledCovariance::wrap(m.clone());
assert_eq!(wrapped.nrows(), 1);
assert_eq!(wrapped[[0, 0]], 5.0);
}
#[test]
fn phi_scaled_covariance_into_array_consumes() {
let m = array![[7.0_f64]];
let wrapped = PhiScaledCovariance::wrap(m.clone());
assert_eq!(wrapped.into_array(), m);
}
#[test]
fn unscaled_precision_wrap_and_as_array_round_trip() {
let h = array![[2.0_f64, 0.0], [0.0, 3.0]];
let wrapped = UnscaledPrecision::wrap(h.clone());
assert_eq!(*wrapped.as_array(), h);
}
#[test]
fn inv_phi_known_dispersion() {
assert!((Dispersion::Known(4.0).inv_phi() - 0.25).abs() < 1e-14);
}
#[test]
fn inv_phi_floors_at_one_over_1e_minus_300() {
let r = Dispersion::Known(0.0).inv_phi();
assert!(r.is_finite());
assert!((r - 1.0 / 1e-300).abs() < 1.0);
}
#[test]
fn sqrt_phi_returns_sqrt() {
assert!((Dispersion::Estimated(9.0).sqrt_phi() - 3.0).abs() < 1e-14);
}
#[test]
fn sqrt_phi_clamps_negative_to_zero() {
assert_eq!(Dispersion::Known(-1.0).sqrt_phi(), 0.0);
}
}