modewise_mvmd/
decomposer.rs1use modewise_decomposition::{Decomposition, ModeDecomposer};
2use modewise_fourier::{from_half_spectrum, to_half_spectrum};
3use modewise_method::{MethodDescriptor, MethodError};
4use modewise_signals::MultichannelSeries;
5use ndarray::{Array1, Array2, Array3, Axis, Zip};
6use rustfft::num_complex::Complex64;
7use tracing::debug;
8
9use crate::params::MvmdParams;
10
11#[derive(Debug, Clone)]
12pub struct Mvmd {
13 params: MvmdParams,
14}
15
16struct Solution {
17 modes_hat: Array3<Complex64>,
18 omegas: Vec<f64>,
19}
20
21impl Mvmd {
22 pub fn new(params: MvmdParams) -> Result<Self, MethodError> {
23 params.validate()?;
24 Ok(Self { params })
25 }
26
27 pub fn params(&self) -> &MvmdParams {
28 &self.params
29 }
30
31 fn solve(&self, signal_hat: &Array2<Complex64>, n_samples: usize) -> Solution {
32 let p = &self.params;
33 let (n_channels, n_bins) = signal_hat.dim();
34 let freqs = Array1::linspace(0.0, 0.5, n_bins);
35 let mut modes_hat = Array3::<Complex64>::zeros((p.num_modes, n_channels, n_bins));
36 let mut modes_sum = Array2::<Complex64>::zeros((n_channels, n_bins));
37 let mut lambda = Array2::<Complex64>::zeros((n_channels, n_bins));
38 let mut omegas = p.init.omegas(p.num_modes);
39 let mut used_omegas = omegas.clone();
41
42 let mut change = p.tolerance + f64::EPSILON;
43 let mut iteration = 0;
44 while iteration < p.max_iterations && change > p.tolerance {
45 change = 0.0;
46 let mut next_omegas = omegas.clone();
47 for (k, mut mode) in modes_hat.axis_iter_mut(Axis(0)).enumerate() {
48 let (mut power, mut weighted) = (0.0, 0.0);
49 Zip::indexed(&mut mode)
50 .and(&mut modes_sum)
51 .and(signal_hat)
52 .and(&lambda)
53 .for_each(|(_, j), m, sum, s, l| {
54 let others = *sum - *m;
55 let filter = 1.0 + p.alpha * (freqs[j] - omegas[k]).powi(2);
56 let updated = (*s - others - *l * 0.5) / filter;
57 change += (updated - *m).norm_sqr();
58 *m = updated;
59 *sum = others + updated;
60 power += updated.norm_sqr();
61 weighted += updated.norm_sqr() * freqs[j];
62 });
63 if power > 0.0 {
64 next_omegas[k] = weighted / power;
65 }
66 }
67 Zip::from(&mut lambda)
68 .and(&modes_sum)
69 .and(signal_hat)
70 .for_each(|l, sum, s| *l += (*sum - *s) * p.tau);
71 used_omegas = std::mem::replace(&mut omegas, next_omegas);
72 iteration += 1;
73 change /= n_samples as f64;
74 }
75 debug!(
76 iteration,
77 change,
78 converged = change <= p.tolerance,
79 "mvmd finished"
80 );
81 Solution {
82 modes_hat,
83 omegas: used_omegas,
84 }
85 }
86}
87
88impl ModeDecomposer for Mvmd {
89 fn descriptor(&self) -> MethodDescriptor {
90 MethodDescriptor {
91 name: "mvmd",
92 version: env!("CARGO_PKG_VERSION"),
93 multivariate: true,
94 }
95 }
96
97 fn decompose(&self, signal: &MultichannelSeries) -> Result<Decomposition, MethodError> {
98 let n_samples = signal.n_samples();
99 let signal_hat = to_half_spectrum(signal.data());
100 let Solution { modes_hat, omegas } = self.solve(&signal_hat, n_samples);
101
102 let mut order: Vec<usize> = (0..omegas.len()).collect();
103 order.sort_by(|&a, &b| omegas[a].total_cmp(&omegas[b]));
104
105 let mut modes = Array3::zeros((order.len(), signal.n_channels(), n_samples));
106 for (mut out, &k) in modes.axis_iter_mut(Axis(0)).zip(&order) {
107 out.assign(&from_half_spectrum(modes_hat.index_axis(Axis(0), k)));
108 }
109 let fs = signal.sampling_rate();
110 let centre_frequencies_hz = order.iter().map(|&k| omegas[k] * fs.hz()).collect();
111
112 Decomposition::new(
113 modes,
114 centre_frequencies_hz,
115 fs,
116 signal.labels().to_vec(),
117 true,
118 )
119 }
120}