use nalgebra::{DMatrix, DVector};
use super::eigen::EigenDecomposition;
#[derive(Debug, Clone)]
pub struct DetoneResult {
pub matrix: DMatrix<f64>,
pub eigenvalues: DVector<f64>,
pub removed_eigenvalues: DVector<f64>,
pub k: usize,
}
#[must_use]
pub fn detone(eigen: &EigenDecomposition, k: usize) -> DetoneResult {
assert!(
k <= eigen.n,
"cannot remove {k} modes from a {n}-dimensional matrix",
n = eigen.n
);
let mut detoned_eigenvalues = eigen.eigenvalues.clone();
let mut removed_eigenvalues = DVector::zeros(eigen.n);
for idx in 0..k {
removed_eigenvalues[idx] = detoned_eigenvalues[idx];
detoned_eigenvalues[idx] = 0.0;
}
let lambda_diag = DMatrix::from_diagonal(&detoned_eigenvalues);
let matrix = &eigen.eigenvectors * lambda_diag * eigen.eigenvectors.transpose();
DetoneResult {
matrix,
eigenvalues: detoned_eigenvalues,
removed_eigenvalues,
k,
}
}
#[must_use]
pub fn readd_tones(
denoised_eigenvalues: &DVector<f64>,
detone_result: &DetoneResult,
eigenvectors: &DMatrix<f64>,
) -> DMatrix<f64> {
let combined = denoised_eigenvalues + &detone_result.removed_eigenvalues;
let lambda_diag = DMatrix::from_diagonal(&combined);
eigenvectors * lambda_diag * eigenvectors.transpose()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::math::eigen::eigendecompose;
use approx::assert_relative_eq;
fn sample_eigen() -> EigenDecomposition {
#[rustfmt::skip]
let corr = DMatrix::from_row_slice(3, 3, &[
1.0, 0.8, 0.5,
0.8, 1.0, 0.6,
0.5, 0.6, 1.0,
]);
eigendecompose(&corr).unwrap()
}
#[test]
fn test_detone_removes_largest() {
let eigen = sample_eigen();
let result = detone(&eigen, 1);
assert_relative_eq!(result.eigenvalues[0], 0.0, epsilon = 1e-14);
assert!(result.eigenvalues[1] > 0.0);
}
#[test]
fn test_detone_removes_k2() {
let eigen = sample_eigen();
let result = detone(&eigen, 2);
assert_relative_eq!(result.eigenvalues[0], 0.0, epsilon = 1e-14);
assert_relative_eq!(result.eigenvalues[1], 0.0, epsilon = 1e-14);
assert!(result.eigenvalues[2] > 0.0);
}
#[test]
fn test_detone_k0_unchanged() {
let eigen = sample_eigen();
let result = detone(&eigen, 0);
let original = eigen.reconstruct();
for row in 0..3 {
for col in 0..3 {
assert_relative_eq!(
result.matrix[(row, col)],
original[(row, col)],
epsilon = 1e-12
);
}
}
}
#[test]
fn test_readd_reconstructs_original() {
let eigen = sample_eigen();
let detone_result = detone(&eigen, 1);
let readded = readd_tones(
&detone_result.eigenvalues,
&detone_result,
&eigen.eigenvectors,
);
let original = eigen.reconstruct();
for row in 0..3 {
for col in 0..3 {
assert_relative_eq!(readded[(row, col)], original[(row, col)], epsilon = 1e-12);
}
}
}
#[test]
fn test_detoned_symmetry() {
let eigen = sample_eigen();
let result = detone(&eigen, 1);
for row in 0..3 {
for col in 0..3 {
assert_relative_eq!(
result.matrix[(row, col)],
result.matrix[(col, row)],
epsilon = 1e-14
);
}
}
}
#[test]
fn test_removed_eigenvalues_stored() {
let eigen = sample_eigen();
let original_top = eigen.eigenvalues[0];
let result = detone(&eigen, 1);
assert_relative_eq!(result.removed_eigenvalues[0], original_top, epsilon = 1e-14);
assert_relative_eq!(result.removed_eigenvalues[1], 0.0, epsilon = 1e-14);
assert_relative_eq!(result.removed_eigenvalues[2], 0.0, epsilon = 1e-14);
}
#[test]
#[should_panic(expected = "cannot remove")]
fn test_detone_k_too_large() {
let eigen = sample_eigen();
let _ = detone(&eigen, 4);
}
}