use super::{ChebyshevExpansion, ScaledLaplacian};
#[derive(Debug, Clone)]
pub struct WaveletScale {
pub scale: f64,
pub filter: ChebyshevExpansion,
}
impl WaveletScale {
pub fn mexican_hat(scale: f64, degree: usize) -> Self {
let filter = ChebyshevExpansion::from_function(
|x| {
let lambda = (x + 1.0); lambda * (-lambda * scale).exp()
},
degree,
);
Self { scale, filter }
}
pub fn heat_derivative(scale: f64, degree: usize) -> Self {
Self::mexican_hat(scale, degree)
}
pub fn scaling_function(scale: f64, degree: usize) -> Self {
let filter = ChebyshevExpansion::from_function(
|x| {
let lambda = (x + 1.0);
(-lambda * scale).exp()
},
degree,
);
Self { scale, filter }
}
}
#[derive(Debug, Clone)]
pub struct GraphWavelet {
pub scale: WaveletScale,
pub center: usize,
pub coefficients: Vec<f64>,
}
impl GraphWavelet {
pub fn at_vertex(laplacian: &ScaledLaplacian, scale: &WaveletScale, center: usize) -> Self {
let n = laplacian.n;
let mut delta = vec![0.0; n];
if center < n {
delta[center] = 1.0;
}
let coefficients = apply_filter(laplacian, &scale.filter, &delta);
Self {
scale: scale.clone(),
center,
coefficients,
}
}
pub fn inner_product(&self, signal: &[f64]) -> f64 {
self.coefficients
.iter()
.zip(signal.iter())
.map(|(&w, &s)| w * s)
.sum()
}
pub fn norm(&self) -> f64 {
self.coefficients.iter().map(|x| x * x).sum::<f64>().sqrt()
}
}
#[derive(Debug, Clone)]
pub struct SpectralWaveletTransform {
laplacian: ScaledLaplacian,
scales: Vec<WaveletScale>,
scaling: WaveletScale,
degree: usize,
}
impl SpectralWaveletTransform {
pub fn new(laplacian: ScaledLaplacian, num_scales: usize, degree: usize) -> Self {
let min_scale = 0.1;
let max_scale = 2.0 / laplacian.lambda_max;
let scales: Vec<WaveletScale> = (0..num_scales)
.map(|i| {
let t = if num_scales > 1 {
min_scale * (max_scale / min_scale).powf(i as f64 / (num_scales - 1) as f64)
} else {
min_scale
};
WaveletScale::mexican_hat(t, degree)
})
.collect();
let scaling = WaveletScale::scaling_function(max_scale, degree);
Self {
laplacian,
scales,
scaling,
degree,
}
}
pub fn forward(&self, signal: &[f64]) -> (Vec<f64>, Vec<Vec<f64>>) {
let scaling_coeffs = apply_filter(&self.laplacian, &self.scaling.filter, signal);
let wavelet_coeffs: Vec<Vec<f64>> = self
.scales
.iter()
.map(|s| apply_filter(&self.laplacian, &s.filter, signal))
.collect();
(scaling_coeffs, wavelet_coeffs)
}
pub fn inverse(&self, scaling_coeffs: &[f64], wavelet_coeffs: &[Vec<f64>]) -> Vec<f64> {
let n = self.laplacian.n;
let mut signal = vec![0.0; n];
let scaled_scaling = apply_filter(&self.laplacian, &self.scaling.filter, scaling_coeffs);
for i in 0..n {
signal[i] += scaled_scaling[i];
}
for (scale, coeffs) in self.scales.iter().zip(wavelet_coeffs.iter()) {
let scaled_wavelet = apply_filter(&self.laplacian, &scale.filter, coeffs);
for i in 0..n {
signal[i] += scaled_wavelet[i];
}
}
signal
}
pub fn scale_energies(&self, signal: &[f64]) -> Vec<f64> {
let (_, wavelet_coeffs) = self.forward(signal);
wavelet_coeffs
.iter()
.map(|coeffs| coeffs.iter().map(|x| x * x).sum::<f64>())
.collect()
}
pub fn wavelets_at(&self, vertex: usize) -> Vec<GraphWavelet> {
self.scales
.iter()
.map(|s| GraphWavelet::at_vertex(&self.laplacian, s, vertex))
.collect()
}
pub fn num_scales(&self) -> usize {
self.scales.len()
}
pub fn scale_values(&self) -> Vec<f64> {
self.scales.iter().map(|s| s.scale).collect()
}
}
fn apply_filter(
laplacian: &ScaledLaplacian,
filter: &ChebyshevExpansion,
signal: &[f64],
) -> Vec<f64> {
let n = laplacian.n;
let coeffs = &filter.coefficients;
if coeffs.is_empty() || signal.len() != n {
return vec![0.0; n];
}
let k = coeffs.len() - 1;
let mut t_prev: Vec<f64> = signal.to_vec();
let mut t_curr: Vec<f64> = laplacian.apply(signal);
let mut output = vec![0.0; n];
for i in 0..n {
output[i] += coeffs[0] * t_prev[i];
}
if coeffs.len() > 1 {
for i in 0..n {
output[i] += coeffs[1] * t_curr[i];
}
}
for ki in 2..=k {
let lt_curr = laplacian.apply(&t_curr);
let mut t_next = vec![0.0; n];
for i in 0..n {
t_next[i] = 2.0 * lt_curr[i] - t_prev[i];
}
for i in 0..n {
output[i] += coeffs[ki] * t_next[i];
}
t_prev = t_curr;
t_curr = t_next;
}
output
}
#[cfg(test)]
mod tests {
use super::*;
fn path_graph_laplacian(n: usize) -> ScaledLaplacian {
let edges: Vec<(usize, usize, f64)> = (0..n - 1).map(|i| (i, i + 1, 1.0)).collect();
ScaledLaplacian::from_sparse_adjacency(&edges, n)
}
#[test]
fn test_wavelet_scale() {
let scale = WaveletScale::mexican_hat(0.5, 10);
assert_eq!(scale.scale, 0.5);
assert!(!scale.filter.coefficients.is_empty());
}
#[test]
fn test_graph_wavelet() {
let laplacian = path_graph_laplacian(10);
let scale = WaveletScale::mexican_hat(0.5, 10);
let wavelet = GraphWavelet::at_vertex(&laplacian, &scale, 5);
assert_eq!(wavelet.center, 5);
assert_eq!(wavelet.coefficients.len(), 10);
assert!(wavelet.coefficients[5].abs() > 0.0);
}
#[test]
fn test_wavelet_transform() {
let laplacian = path_graph_laplacian(20);
let transform = SpectralWaveletTransform::new(laplacian, 4, 10);
assert_eq!(transform.num_scales(), 4);
let signal: Vec<f64> = (0..20).map(|i| (i as f64 * 0.3).sin()).collect();
let (scaling, wavelets) = transform.forward(&signal);
assert_eq!(scaling.len(), 20);
assert_eq!(wavelets.len(), 4);
for w in &wavelets {
assert_eq!(w.len(), 20);
}
}
#[test]
fn test_scale_energies() {
let laplacian = path_graph_laplacian(20);
let transform = SpectralWaveletTransform::new(laplacian, 4, 10);
let signal: Vec<f64> = (0..20).map(|i| (i as f64 * 0.3).sin()).collect();
let energies = transform.scale_energies(&signal);
assert_eq!(energies.len(), 4);
for e in energies {
assert!(e >= 0.0);
}
}
#[test]
fn test_wavelets_at_vertex() {
let laplacian = path_graph_laplacian(10);
let transform = SpectralWaveletTransform::new(laplacian, 3, 8);
let wavelets = transform.wavelets_at(5);
assert_eq!(wavelets.len(), 3);
for w in &wavelets {
assert_eq!(w.center, 5);
}
}
}