1use std::f64::consts::PI;
12
13use modewise_method::{MethodDescriptor, MethodError};
14use modewise_signals::MultichannelSeries;
15use modewise_spectral::{FrequencyGrid, TimeFrequencyMap, TimeFrequencyTransform};
16use ndarray::{Array2, Array3};
17use rustfft::FftPlanner;
18use rustfft::num_complex::Complex64;
19
20#[derive(Debug, Clone)]
21pub struct Cwt {
22 grid: FrequencyGrid,
23 omega0: f64,
24 rectify: bool,
25}
26
27impl Cwt {
28 pub fn new(grid: FrequencyGrid, omega0: f64, rectify: bool) -> Result<Self, MethodError> {
32 if !(omega0.is_finite() && omega0 >= 5.0) {
33 return Err(MethodError::invalid(
34 "omega0",
35 "must be finite and >= 5 for the Morlet to be admissible in practice",
36 ));
37 }
38 Ok(Self {
39 grid,
40 omega0,
41 rectify,
42 })
43 }
44
45 pub fn fourier_factor(&self) -> f64 {
47 4.0 * PI / (self.omega0 + (2.0 + self.omega0 * self.omega0).sqrt())
48 }
49}
50
51impl TimeFrequencyTransform for Cwt {
52 fn descriptor(&self) -> MethodDescriptor {
53 MethodDescriptor {
54 name: "cwt",
55 version: env!("CARGO_PKG_VERSION"),
56 multivariate: false,
57 }
58 }
59
60 fn transform(&self, signal: &MultichannelSeries) -> Result<TimeFrequencyMap, MethodError> {
61 let fs = signal.sampling_rate();
62 let dt = fs.period_s();
63 let n = signal.n_samples();
64 let padded = n.next_power_of_two();
65 let lambda = self.fourier_factor();
66 let freqs = self.grid.centres();
67 let nyquist = fs.hz() / 2.0;
68 if let Some(f) = freqs.iter().find(|&&f| f <= 0.0 || f > nyquist) {
69 return Err(MethodError::invalid(
70 "frequencies",
71 format!("{f} Hz is outside (0, {nyquist}] Hz"),
72 ));
73 }
74 let scales: Vec<f64> = freqs.iter().map(|f| 1.0 / (lambda * f)).collect();
75 let omegas: Vec<f64> = (0..padded)
76 .map(|k| {
77 let k = if k < padded / 2 {
79 k as f64
80 } else {
81 k as f64 - padded as f64
82 };
83 2.0 * PI * k / (padded as f64 * dt)
84 })
85 .collect();
86
87 let mut planner = FftPlanner::new();
88 let forward = planner.plan_fft_forward(padded);
89 let inverse = planner.plan_fft_inverse(padded);
90 let mut power = Array3::zeros((signal.n_channels(), scales.len(), n));
91 for (c, channel) in signal.data().rows().into_iter().enumerate() {
92 let mut spectrum: Vec<Complex64> =
93 channel.iter().map(|&v| Complex64::new(v, 0.0)).collect();
94 spectrum.resize(padded, Complex64::new(0.0, 0.0));
95 forward.process(&mut spectrum);
96 for (j, &s) in scales.iter().enumerate() {
97 let norm = (2.0 * PI * s / dt).sqrt() * PI.powf(-0.25);
98 let mut w: Vec<Complex64> = spectrum
99 .iter()
100 .zip(&omegas)
101 .map(|(x, &om)| x * norm * (-0.5 * (s * om - self.omega0).powi(2)).exp())
102 .collect();
103 inverse.process(&mut w);
104 let rectify = if self.rectify { s } else { 1.0 };
105 for t in 0..n {
106 power[[c, j, t]] = (w[t] / padded as f64).norm_sqr() / rectify;
107 }
108 }
109 }
110
111 let coi_factor = lambda / 2f64.sqrt() * dt;
112 let reliable = Array2::from_shape_fn((freqs.len(), n), |(j, t)| {
113 1.0 / freqs[j] <= coi_factor * (t.min(n - 1 - t) as f64 + 0.5)
114 });
115 let times = (0..n).map(|t| t as f64 * dt).collect();
116 TimeFrequencyMap::new(power, freqs, times, signal.labels().to_vec())?
117 .with_sampling_rate(fs)?
118 .with_reliable(reliable)
119 }
120}