use nalgebra::base::VecStorage;
use nalgebra::Dyn;
pub type SquareMatrix<T> = nalgebra::SquareMatrix<T, Dyn, VecStorage<T, Dyn, Dyn>>;
pub struct CovarianceMatrix {
cov: SquareMatrix<f64>,
eigenvectors: SquareMatrix<f64>,
sqrt_eigenvalues: SquareMatrix<f64>,
sqrt_inv: SquareMatrix<f64>,
transform: SquareMatrix<f64>,
}
impl CovarianceMatrix {
pub fn new(dim: usize) -> Self {
Self {
cov: SquareMatrix::identity(dim, dim),
eigenvectors: SquareMatrix::identity(dim, dim),
sqrt_eigenvalues: SquareMatrix::identity(dim, dim),
sqrt_inv: SquareMatrix::identity(dim, dim),
transform: SquareMatrix::identity(dim, dim),
}
}
pub fn cov(&self) -> &SquareMatrix<f64> {
&self.cov
}
pub fn set_cov(
&mut self,
new: SquareMatrix<f64>,
update_eigen: bool,
) -> Result<(), PosDefCovError> {
self.cov = new;
self.cov.fill_lower_triangle_with_upper_triangle();
if update_eigen {
self.update_eigendecomposition()?;
}
Ok(())
}
fn update_eigendecomposition(&mut self) -> Result<(), PosDefCovError> {
#[cfg(any(
feature = "openblas",
feature = "netlib",
feature = "accelerate",
feature = "intel-mkl"
))]
let mut eigen =
nalgebra_lapack::SymmetricEigen::try_new(self.cov.clone()).ok_or(PosDefCovError)?;
#[cfg(not(any(
feature = "openblas",
feature = "netlib",
feature = "accelerate",
feature = "intel-mkl"
)))]
let mut eigen =
nalgebra::SymmetricEigen::try_new(self.cov.clone(), 1e-20, 0).ok_or(PosDefCovError)?;
for mut col in eigen.eigenvectors.column_iter_mut() {
col.normalize_mut();
}
if eigen.eigenvalues.iter().any(|x| *x <= 0.0) {
return Err(PosDefCovError);
}
self.eigenvectors = eigen.eigenvectors;
self.sqrt_eigenvalues = SquareMatrix::from_diagonal(&eigen.eigenvalues.map(|x| x.sqrt()));
self.sqrt_inv = &self.eigenvectors
* self
.sqrt_eigenvalues
.map(|d| if d > 0.0 { 1.0 / d } else { d })
* self.eigenvectors.transpose();
self.transform = &self.eigenvectors * &self.sqrt_eigenvalues;
Ok(())
}
pub fn eigenvectors(&self) -> &SquareMatrix<f64> {
&self.eigenvectors
}
pub fn sqrt_eigenvalues(&self) -> &SquareMatrix<f64> {
&self.sqrt_eigenvalues
}
pub fn sqrt_inv(&self) -> &SquareMatrix<f64> {
&self.sqrt_inv
}
pub fn transform(&self) -> &SquareMatrix<f64> {
&self.transform
}
}
#[derive(Clone, Debug)]
pub struct PosDefCovError;
#[cfg(test)]
mod tests {
use assert_approx_eq::assert_approx_eq;
use super::*;
#[test]
fn test_update_eigendecomposition() {
let mut cov = CovarianceMatrix::new(2);
cov.set_cov(
SquareMatrix::from_iterator(2, 2, [3.0, 1.5, 1.5, 2.0]),
false,
)
.unwrap();
assert_eq!(cov.eigenvectors, SquareMatrix::identity(2, 2));
assert_eq!(cov.sqrt_eigenvalues, SquareMatrix::identity(2, 2));
cov.update_eigendecomposition().unwrap();
let reconstructed =
cov.eigenvectors.clone() * cov.sqrt_eigenvalues.pow(2) * cov.eigenvectors.transpose();
for x in (reconstructed - &cov.cov).iter() {
assert_approx_eq!(x, 0.0);
}
cov.set_cov(
SquareMatrix::from_iterator(2, 2, [3.0, 5.0, 5.0, 2.0]),
false,
)
.unwrap();
assert!(cov.update_eigendecomposition().is_err());
}
}