gspx 0.1.2

Sparse graph signal processing and spectral graph wavelets in Rust
Documentation
mod support;

use gspx::kernel::{ChebyKernel, VfKernel, estimate_spectral_bound, impulse, impulse_1d};
use ndarray::Array1;

#[test]
fn vfkernel_parsing() {
    let d = r#"{
      "poles":[
        {"q":1.0, "r":[0.1,0.2]},
        {"q":2.0, "r":[0.3,0.4]}
      ],
      "d":[0.5,0.6]
    }"#;
    let k = VfKernel::from_json_str(d).expect("vfkernel json should parse");
    assert_eq!(k.poles.to_vec(), vec![1.0, 2.0]);
    assert_eq!(k.residues.shape(), &[2, 2]);
    assert_eq!(k.direct.to_vec(), vec![0.5, 0.6]);
}

#[test]
fn chebykernel_parsing_and_eval() {
    let d = r#"{
      "spectrum_bound":2.0,
      "approximations":[
        {"coeffs":[1.0,2.0,3.0]},
        {"coeffs":[4.0,5.0,6.0]}
      ]
    }"#;
    let k = ChebyKernel::from_json_str(d).expect("chebykernel json should parse");
    assert_eq!(k.coefficients.shape(), &[3, 2]);
    let y = k
        .evaluate(Array1::from(vec![0.0, 1.0, 2.0]).view())
        .expect("cheby evaluate should work");
    assert_eq!(y.shape(), &[3, 2]);
}

#[test]
fn impulse_defaults_and_shape() {
    let l = support::path_laplacian(10);
    let x = impulse(&l, 3, 1).expect("impulse should build");
    assert_eq!(x.shape(), &[10, 1]);
    assert_eq!(x[[3, 0]], 1.0);

    let x2 = impulse(&l, 3, 2).expect("multi-step impulse should build");
    assert_eq!(x2.shape(), &[10, 2]);
    assert_eq!(x2[[3, 0]], 1.0);
    assert_eq!(x2[[3, 1]], 1.0);
}

#[test]
fn impulse_1d_shape_and_value() {
    let l = support::path_laplacian(10);
    let x = impulse_1d(&l, 4).expect("1d impulse should build");
    assert_eq!(x.len(), 10);
    assert_eq!(x[4], 1.0);
    assert_eq!(x.sum(), 1.0);
}

#[test]
fn spectral_bound_positive() {
    let l = support::path_laplacian(40);
    let ub = estimate_spectral_bound(&l).expect("spectral bound estimation should work");
    assert!(ub > 0.01);
}

#[test]
fn chebykernel_mismatch_coeffs_fails() {
    let d = r#"{
      "spectrum_bound":1.0,
      "approximations":[{"coeffs":[1.0,2.0]},{"coeffs":[3.0,4.0,5.0]}]
    }"#;
    let err = ChebyKernel::from_json_str(d).expect_err("mismatch coeff lengths should fail");
    assert!(format!("{err}").contains("same length"));
}

#[test]
fn chebykernel_empty_approximations() {
    let d = r#"{"spectrum_bound":2.0,"approximations":[]}"#;
    let k = ChebyKernel::from_json_str(d).expect("empty approximations should parse");
    assert_eq!(k.coefficients.shape(), &[0, 0]);
    let y = k
        .evaluate(Array1::from_vec(vec![0.5]).view())
        .expect("evaluation should succeed for empty kernel");
    assert_eq!(y.shape(), &[1, 0]);
}

#[test]
fn impulse_out_of_bounds_fails() {
    let l = support::path_laplacian(10);
    assert!(impulse(&l, 100, 1).is_err());
}