1#![allow(clippy::needless_range_loop, clippy::ptr_arg)]
2#![allow(dead_code)]
13
14pub struct HaarWavelet;
23
24impl HaarWavelet {
25 pub fn forward(signal: &[f64]) -> (Vec<f64>, Vec<f64>) {
30 let n = signal.len() / 2;
31 let mut approx = Vec::with_capacity(n);
32 let mut detail = Vec::with_capacity(n);
33 let sqrt2_inv = 1.0 / std::f64::consts::SQRT_2;
34 for i in 0..n {
35 let a = signal[2 * i];
36 let b = signal[2 * i + 1];
37 approx.push((a + b) * sqrt2_inv);
38 detail.push((a - b) * sqrt2_inv);
39 }
40 (approx, detail)
41 }
42
43 pub fn inverse(approx: &[f64], detail: &[f64]) -> Vec<f64> {
47 let n = approx.len().min(detail.len());
48 let mut out = vec![0.0; n * 2];
49 let sqrt2_inv = 1.0 / std::f64::consts::SQRT_2;
50 for i in 0..n {
51 let a = approx[i];
52 let d = detail[i];
53 out[2 * i] = (a + d) * sqrt2_inv;
54 out[2 * i + 1] = (a - d) * sqrt2_inv;
55 }
56 out
57 }
58
59 pub fn decompose_level(signal: &[f64], levels: usize) -> Vec<Vec<f64>> {
65 let mut result = Vec::with_capacity(levels + 1);
66 let mut approx = signal.to_vec();
67 for _ in 0..levels {
68 if approx.len() < 2 {
69 break;
70 }
71 let (a, d) = Self::forward(&approx);
72 result.push(d);
73 approx = a;
74 }
75 result.push(approx);
76 result.reverse();
77 result
78 }
79}
80
81pub struct Daubechies4;
90
91impl Daubechies4 {
92 pub const H: [f64; 4] = [
94 0.482962913_14503_f64,
95 0.836516303_73787_f64,
96 0.224143868_04186_f64,
97 -0.12940952_25523_f64,
98 ];
99
100 pub const G: [f64; 4] = [
102 -0.12940952_25523_f64,
103 -0.224143868_04186_f64,
104 0.836516303_73787_f64,
105 -0.482962913_14503_f64,
106 ];
107
108 pub fn forward(signal: &[f64]) -> (Vec<f64>, Vec<f64>) {
113 let n = signal.len();
114 let half = n / 2;
115 let mut approx = vec![0.0; half];
116 let mut detail = vec![0.0; half];
117 for i in 0..half {
118 let mut a = 0.0;
119 let mut d = 0.0;
120 for k in 0..4 {
121 let idx = (2 * i + k) % n;
122 a += Self::H[k] * signal[idx];
123 d += Self::G[k] * signal[idx];
124 }
125 approx[i] = a;
126 detail[i] = d;
127 }
128 (approx, detail)
129 }
130
131 pub fn inverse(approx: &[f64], detail: &[f64]) -> Vec<f64> {
135 let half = approx.len().min(detail.len());
136 let n = half * 2;
137 let mut out = vec![0.0; n];
138 for i in 0..half {
139 for k in 0..4 {
140 let idx = (2 * i + k) % n;
141 out[idx] += Self::H[k] * approx[i] + Self::G[k] * detail[i];
142 }
143 }
144 out
145 }
146}
147
148pub struct Symlet4;
157
158impl Symlet4 {
159 pub const H: [f64; 8] = [
161 -0.075_765_714_789_273_13,
162 -0.02963552764599851,
163 0.49761866763201545,
164 0.803_738_751_805_916_1,
165 0.29785779560527736,
166 -0.099_219_543_576_847_22,
167 -0.012603967262037833,
168 0.032_223_100_604_042_7,
169 ];
170
171 pub const G: [f64; 8] = [
173 -0.032_223_100_604_042_7,
174 -0.012603967262037833,
175 0.099_219_543_576_847_22,
176 0.29785779560527736,
177 -0.803_738_751_805_916_1,
178 0.49761866763201545,
179 0.02963552764599851,
180 -0.075_765_714_789_273_13,
181 ];
182
183 pub fn forward(signal: &[f64]) -> (Vec<f64>, Vec<f64>) {
187 let n = signal.len();
188 if n < 2 {
189 return (signal.to_vec(), vec![]);
190 }
191 let half = n / 2;
192 let filter_len = 8;
193 let mut approx = vec![0.0; half];
194 let mut detail = vec![0.0; half];
195 for i in 0..half {
196 let mut a = 0.0;
197 let mut d = 0.0;
198 for k in 0..filter_len {
199 let idx = (2 * i + k) % n;
200 a += Self::H[k] * signal[idx];
201 d += Self::G[k] * signal[idx];
202 }
203 approx[i] = a;
204 detail[i] = d;
205 }
206 (approx, detail)
207 }
208
209 pub fn inverse(approx: &[f64], detail: &[f64]) -> Vec<f64> {
213 let half = approx.len().min(detail.len());
214 let n = half * 2;
215 if n == 0 {
216 return vec![];
217 }
218 let filter_len = 8;
219 let mut out = vec![0.0; n];
220 for i in 0..half {
221 for k in 0..filter_len {
222 let idx = (2 * i + k) % n;
223 out[idx] += Self::H[k] * approx[i] + Self::G[k] * detail[i];
224 }
225 }
226 out
227 }
228}
229
230#[derive(Debug, Clone, Copy, PartialEq, Eq)]
236pub enum WaveletFamily {
237 Haar,
239 Daubechies4,
241 Symlet4,
243}
244
245#[derive(Debug, Clone)]
250pub struct WaveletTransform {
251 pub family: WaveletFamily,
253}
254
255impl WaveletTransform {
256 pub fn new(family: WaveletFamily) -> Self {
258 Self { family }
259 }
260
261 pub fn forward_one_level(&self, signal: &[f64]) -> (Vec<f64>, Vec<f64>) {
265 match self.family {
266 WaveletFamily::Haar => HaarWavelet::forward(signal),
267 WaveletFamily::Daubechies4 => Daubechies4::forward(signal),
268 WaveletFamily::Symlet4 => Symlet4::forward(signal),
269 }
270 }
271
272 pub fn inverse_one_level(&self, approx: &[f64], detail: &[f64]) -> Vec<f64> {
276 match self.family {
277 WaveletFamily::Haar => HaarWavelet::inverse(approx, detail),
278 WaveletFamily::Daubechies4 => Daubechies4::inverse(approx, detail),
279 WaveletFamily::Symlet4 => Symlet4::inverse(approx, detail),
280 }
281 }
282
283 pub fn energy(coeffs: &[f64]) -> f64 {
285 coeffs.iter().map(|x| x * x).sum()
286 }
287
288 pub fn entropy(coeffs: &[f64]) -> f64 {
292 let total: f64 = coeffs.iter().map(|x| x * x).sum();
293 if total < 1e-30 {
294 return 0.0;
295 }
296 -coeffs
297 .iter()
298 .map(|x| {
299 let p = x * x / total;
300 if p < 1e-30 { 0.0 } else { p * p.ln() }
301 })
302 .sum::<f64>()
303 }
304}
305
306#[derive(Debug, Clone)]
315pub struct MultilevelDwt {
316 pub transform: WaveletTransform,
318 pub levels: usize,
320 pub approx: Vec<f64>,
322 pub details: Vec<Vec<f64>>,
324}
325
326impl MultilevelDwt {
327 pub fn decompose(signal: &[f64], levels: usize, family: WaveletFamily) -> Self {
332 let transform = WaveletTransform::new(family);
333 let mut approx = signal.to_vec();
334 let mut details = Vec::with_capacity(levels);
335
336 for _ in 0..levels {
337 if approx.len() < 2 {
338 break;
339 }
340 let (a, d) = transform.forward_one_level(&approx);
341 details.push(d);
342 approx = a;
343 }
344 details.reverse();
346
347 Self {
348 transform,
349 levels,
350 approx,
351 details,
352 }
353 }
354
355 pub fn reconstruct(&self) -> Vec<f64> {
359 let mut signal = self.approx.clone();
360 for detail in self.details.iter() {
362 signal = self.transform.inverse_one_level(&signal, detail);
363 }
364 signal
365 }
366
367 pub fn total_energy(&self) -> f64 {
369 let approx_e: f64 = self.approx.iter().map(|x| x * x).sum();
370 let detail_e: f64 = self
371 .details
372 .iter()
373 .flat_map(|d| d.iter())
374 .map(|x| x * x)
375 .sum();
376 approx_e + detail_e
377 }
378
379 pub fn level_entropies(&self) -> Vec<f64> {
381 let mut entropies = vec![WaveletTransform::entropy(&self.approx)];
382 for d in &self.details {
383 entropies.push(WaveletTransform::entropy(d));
384 }
385 entropies
386 }
387}
388
389#[derive(Debug, Clone)]
398pub struct WaveletPacket {
399 pub coefficients: Vec<Vec<f64>>,
401 pub levels: usize,
403}
404
405impl WaveletPacket {
406 pub fn new_haar(signal: &[f64], levels: usize) -> Self {
410 let mut nodes: Vec<Vec<f64>> = vec![signal.to_vec()];
411 for _ in 0..levels {
412 let mut next = Vec::with_capacity(nodes.len() * 2);
413 for node in &nodes {
414 if node.len() >= 2 {
415 let (a, d) = HaarWavelet::forward(node);
416 next.push(a);
417 next.push(d);
418 } else {
419 next.push(node.clone());
420 next.push(Vec::new());
421 }
422 }
423 nodes = next;
424 }
425 Self {
426 coefficients: nodes,
427 levels,
428 }
429 }
430
431 pub fn new(signal: &[f64], levels: usize, family: WaveletFamily) -> Self {
435 let transform = WaveletTransform::new(family);
436 let mut nodes: Vec<Vec<f64>> = vec![signal.to_vec()];
437 for _ in 0..levels {
438 let mut next = Vec::with_capacity(nodes.len() * 2);
439 for node in &nodes {
440 if node.len() >= 2 {
441 let (a, d) = transform.forward_one_level(node);
442 next.push(a);
443 next.push(d);
444 } else {
445 next.push(node.clone());
446 next.push(Vec::new());
447 }
448 }
449 nodes = next;
450 }
451 Self {
452 coefficients: nodes,
453 levels,
454 }
455 }
456
457 pub fn best_basis(&self) -> Vec<usize> {
459 let energies = self.energy_distribution();
460 let max_e = energies.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
461 energies
462 .iter()
463 .enumerate()
464 .filter(|&(_, &e)| (e - max_e).abs() < 1e-12 * max_e.abs().max(1.0))
465 .map(|(i, _)| i)
466 .collect()
467 }
468
469 pub fn best_basis_entropy(&self) -> Vec<usize> {
474 let entropies: Vec<f64> = self
475 .coefficients
476 .iter()
477 .map(|c| WaveletTransform::entropy(c))
478 .collect();
479 let min_e = entropies.iter().cloned().fold(f64::INFINITY, f64::min);
480 entropies
481 .iter()
482 .enumerate()
483 .filter(|&(_, &e)| (e - min_e).abs() < 1e-12 * min_e.abs().max(1.0))
484 .map(|(i, _)| i)
485 .collect()
486 }
487
488 pub fn energy_distribution(&self) -> Vec<f64> {
490 self.coefficients
491 .iter()
492 .map(|c| c.iter().map(|x| x * x).sum())
493 .collect()
494 }
495
496 pub fn entropy_distribution(&self) -> Vec<f64> {
498 self.coefficients
499 .iter()
500 .map(|c| WaveletTransform::entropy(c))
501 .collect()
502 }
503
504 pub fn total_energy(&self) -> f64 {
506 self.energy_distribution().iter().sum()
507 }
508}
509
510pub struct ContinuousWavelet;
519
520impl ContinuousWavelet {
521 pub fn morlet(t: f64, sigma: f64) -> f64 {
525 let gauss = (-t * t / (2.0 * sigma * sigma)).exp();
526 let wave = (t / sigma).cos();
527 gauss * wave
528 }
529
530 pub fn mexican_hat(t: f64, sigma: f64) -> f64 {
534 let s2 = sigma * sigma;
535 let u = t * t / s2;
536 (1.0 - u) * (-t * t / (2.0 * s2)).exp()
537 }
538
539 pub fn paul(t: f64, m: u32) -> f64 {
543 let denom = (1.0 + t * t).powf((m as f64 + 1.0) / 2.0);
544 let angle = (m as f64) * t.atan2(1.0);
545 angle.cos() / denom.max(1e-30)
546 }
547
548 pub fn cwt_morlet(signal: &[f64], dt: f64, scales: &[f64]) -> Vec<Vec<f64>> {
557 let n = signal.len();
558 let mut scalogram = Vec::with_capacity(scales.len());
559 let sigma = 1.0_f64;
560 for &scale in scales {
561 let mut row = Vec::with_capacity(n);
562 for t_idx in 0..n {
563 let mut val = 0.0;
564 for tau in 0..n {
565 let time = (t_idx as f64 - tau as f64) * dt / scale.max(1e-12);
566 val += signal[tau] * Self::morlet(time, sigma);
567 }
568 row.push(val * dt / scale.sqrt().max(1e-12));
569 }
570 scalogram.push(row);
571 }
572 scalogram
573 }
574
575 pub fn cwt_mexican_hat(signal: &[f64], dt: f64, scales: &[f64]) -> Vec<Vec<f64>> {
579 let n = signal.len();
580 let mut scalogram = Vec::with_capacity(scales.len());
581 let sigma = 1.0_f64;
582 for &scale in scales {
583 let mut row = Vec::with_capacity(n);
584 for t_idx in 0..n {
585 let mut val = 0.0;
586 for tau in 0..n {
587 let time = (t_idx as f64 - tau as f64) * dt / scale.max(1e-12);
588 val += signal[tau] * Self::mexican_hat(time, sigma);
589 }
590 row.push(val * dt / scale.sqrt().max(1e-12));
591 }
592 scalogram.push(row);
593 }
594 scalogram
595 }
596
597 pub fn cwt_paul(signal: &[f64], dt: f64, scales: &[f64], m: u32) -> Vec<Vec<f64>> {
601 let n = signal.len();
602 let mut scalogram = Vec::with_capacity(scales.len());
603 for &scale in scales {
604 let mut row = Vec::with_capacity(n);
605 for t_idx in 0..n {
606 let mut val = 0.0;
607 for tau in 0..n {
608 let time = (t_idx as f64 - tau as f64) * dt / scale.max(1e-12);
609 val += signal[tau] * Self::paul(time, m);
610 }
611 row.push(val * dt / scale.sqrt().max(1e-12));
612 }
613 scalogram.push(row);
614 }
615 scalogram
616 }
617
618 pub fn instantaneous_frequency(scalogram: &[Vec<f64>], scales: &[f64], dt: f64) -> Vec<f64> {
623 if scalogram.is_empty() || scales.is_empty() {
624 return Vec::new();
625 }
626 let n_time = scalogram[0].len();
627 let n_scales = scalogram.len().min(scales.len());
628 (0..n_time)
629 .map(|t| {
630 let mut best_scale = scales[0];
631 let mut best_val = scalogram[0][t].abs();
632 for s in 1..n_scales {
633 let v = scalogram[s][t].abs();
634 if v > best_val {
635 best_val = v;
636 best_scale = scales[s];
637 }
638 }
639 1.0 / (best_scale * dt).max(1e-30)
640 })
641 .collect()
642 }
643
644 pub fn scale_averaged_power(scalogram: &[Vec<f64>], scales: &[f64]) -> Vec<(f64, f64)> {
648 scalogram
649 .iter()
650 .zip(scales.iter())
651 .map(|(row, &scale)| {
652 let power: f64 = row.iter().map(|x| x * x).sum::<f64>() / row.len().max(1) as f64;
653 (scale, power)
654 })
655 .collect()
656 }
657}
658
659pub struct ContinuousWaveletTransform;
667
668impl ContinuousWaveletTransform {
669 pub fn morlet(t: f64, sigma: f64) -> f64 {
671 ContinuousWavelet::morlet(t, sigma)
672 }
673
674 pub fn mexican_hat(t: f64, sigma: f64) -> f64 {
676 ContinuousWavelet::mexican_hat(t, sigma)
677 }
678
679 pub fn cwt_morlet(signal: &[f64], dt: f64, scales: &[f64]) -> Vec<Vec<f64>> {
681 ContinuousWavelet::cwt_morlet(signal, dt, scales)
682 }
683
684 pub fn instantaneous_frequency(scalogram: &[Vec<f64>], scales: &[f64], dt: f64) -> Vec<f64> {
686 ContinuousWavelet::instantaneous_frequency(scalogram, scales, dt)
687 }
688}
689
690pub struct DiscreteWaveletTransform;
699
700impl DiscreteWaveletTransform {
701 pub fn dwt_haar(signal: &[f64]) -> Vec<f64> {
706 let mut coeffs = signal.to_vec();
707 let mut n = signal.len();
708 while n >= 2 {
710 let half = n / 2;
711 let tmp: Vec<f64> = (0..half)
712 .flat_map(|i| {
713 let a = coeffs[2 * i];
714 let b = coeffs[2 * i + 1];
715 let s = 1.0 / std::f64::consts::SQRT_2;
716 vec![(a + b) * s, (a - b) * s]
717 })
718 .collect();
719 for i in 0..half {
721 coeffs[i] = tmp[2 * i];
722 coeffs[half + i] = tmp[2 * i + 1];
723 }
724 n = half;
725 }
726 coeffs
727 }
728
729 pub fn idwt_haar(coeffs: &[f64]) -> Vec<f64> {
731 let n = coeffs.len();
732 let mut signal = coeffs.to_vec();
733 let mut level_size = 2usize;
734 while level_size <= n {
736 level_size *= 2;
737 }
738 level_size /= 2;
739 let mut cur = 2usize;
741 while cur <= level_size {
742 let half = cur / 2;
743 let tmp: Vec<f64> = (0..half)
744 .flat_map(|i| {
745 let a = signal[i];
746 let d = signal[half + i];
747 let s = 1.0 / std::f64::consts::SQRT_2;
748 vec![(a + d) * s, (a - d) * s]
749 })
750 .collect();
751 signal[..cur].copy_from_slice(&tmp[..cur]);
752 cur *= 2;
753 }
754 signal
755 }
756
757 pub fn dwt_level(signal: &[f64], level: usize) -> Vec<f64> {
762 let mut approx = signal.to_vec();
763 for _ in 0..level {
764 if approx.len() < 2 {
765 break;
766 }
767 let (a, _d) = HaarWavelet::forward(&approx);
768 approx = a;
769 }
770 approx
771 }
772
773 pub fn wavelet_energy(coeffs: &[f64]) -> f64 {
776 coeffs.iter().map(|x| x * x).sum()
777 }
778
779 pub fn wavelet_entropy(coeffs: &[f64]) -> f64 {
783 let energy: f64 = coeffs.iter().map(|x| x * x).sum();
784 if energy < 1e-30 {
785 return 0.0;
786 }
787 -coeffs
788 .iter()
789 .map(|x| {
790 let p = x * x / energy;
791 if p < 1e-30 { 0.0 } else { p * p.ln() }
792 })
793 .sum::<f64>()
794 }
795}
796
797pub struct WaveletDenoising;
806
807impl WaveletDenoising {
808 pub fn hard_threshold(coeffs: &mut Vec<f64>, threshold: f64) {
810 for c in coeffs.iter_mut() {
811 if c.abs() < threshold {
812 *c = 0.0;
813 }
814 }
815 }
816
817 pub fn soft_threshold(coeffs: &mut Vec<f64>, threshold: f64) {
819 for c in coeffs.iter_mut() {
820 if c.abs() <= threshold {
821 *c = 0.0;
822 } else {
823 *c = c.signum() * (c.abs() - threshold);
824 }
825 }
826 }
827
828 pub fn universal_threshold(signal: &[f64]) -> f64 {
833 let n = signal.len();
834 if n == 0 {
835 return 0.0;
836 }
837 let (_approx, detail) = HaarWavelet::forward(signal);
839 let sigma = Self::mad_sigma(&detail);
840 sigma * (2.0 * (n as f64).ln()).sqrt()
841 }
842
843 pub fn sure_threshold(signal: &[f64]) -> f64 {
847 let n = signal.len();
848 if n == 0 {
849 return 0.0;
850 }
851 let mut sorted: Vec<f64> = signal.iter().map(|x| x.abs()).collect();
852 sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
853 let mut best_risk = f64::INFINITY;
854 let mut best_thresh = 0.0;
855 let total_sq: f64 = signal.iter().map(|x| x * x).sum();
856 for (i, &thresh) in sorted.iter().enumerate() {
857 let k = i + 1; let sum_clamped: f64 = sorted
860 .iter()
861 .map(|&x| if x <= thresh { x * x } else { thresh * thresh })
862 .sum();
863 let risk = (n as f64) - 2.0 * (k as f64) + sum_clamped + (total_sq - sum_clamped);
864 if risk < best_risk {
865 best_risk = risk;
866 best_thresh = thresh;
867 }
868 }
869 best_thresh
870 }
871
872 pub fn denoise_signal(signal: &[f64]) -> Vec<f64> {
877 if signal.len() < 2 {
878 return signal.to_vec();
879 }
880 let threshold = Self::universal_threshold(signal);
881 let mut coeffs = DiscreteWaveletTransform::dwt_haar(signal);
882 let n_approx = coeffs.len() / 2;
884 let (_, detail_part) = coeffs.split_at_mut(n_approx);
885 let mut detail_vec = detail_part.to_vec();
886 Self::soft_threshold(&mut detail_vec, threshold);
887 for (c, d) in detail_part.iter_mut().zip(detail_vec.iter()) {
888 *c = *d;
889 }
890 DiscreteWaveletTransform::idwt_haar(&coeffs)
891 }
892
893 fn mad_sigma(detail: &[f64]) -> f64 {
895 if detail.is_empty() {
896 return 1.0;
897 }
898 let mut abs_vals: Vec<f64> = detail.iter().map(|x| x.abs()).collect();
899 abs_vals.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
900 let med = if abs_vals.len() % 2 == 1 {
901 abs_vals[abs_vals.len() / 2]
902 } else {
903 let m = abs_vals.len() / 2;
904 (abs_vals[m - 1] + abs_vals[m]) / 2.0
905 };
906 (med / 0.6745).max(1e-12)
907 }
908}
909
910#[derive(Debug, Clone)]
919pub struct WaveletCompression {
920 pub original_length: usize,
922 pub nonzero_count: usize,
924 pub sparse_indices: Vec<usize>,
926 pub sparse_values: Vec<f64>,
928 pub threshold: f64,
930}
931
932impl WaveletCompression {
933 pub fn compress(signal: &[f64], retain_fraction: f64) -> Self {
938 let n = signal.len();
939 let coeffs = DiscreteWaveletTransform::dwt_haar(signal);
940 let total_energy: f64 = coeffs.iter().map(|x| x * x).sum();
941
942 let mut sorted_mags: Vec<f64> = coeffs.iter().map(|x| x.abs()).collect();
944 sorted_mags.sort_by(|a, b| b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal));
945
946 let target_energy = retain_fraction.clamp(0.0, 1.0) * total_energy;
947 let mut cumulative = 0.0;
948 let mut threshold = 0.0;
949 for &mag in &sorted_mags {
950 cumulative += mag * mag;
951 if cumulative >= target_energy {
952 threshold = mag;
953 break;
954 }
955 }
956
957 let sparse_indices: Vec<usize> = coeffs
958 .iter()
959 .enumerate()
960 .filter(|&(_, c)| c.abs() >= threshold)
961 .map(|(i, _)| i)
962 .collect();
963 let sparse_values: Vec<f64> = sparse_indices.iter().map(|&i| coeffs[i]).collect();
964 let nonzero_count = sparse_indices.len();
965
966 Self {
967 original_length: n,
968 nonzero_count,
969 sparse_indices,
970 sparse_values,
971 threshold,
972 }
973 }
974
975 pub fn decompress(&self) -> Vec<f64> {
980 let mut coeffs = vec![0.0f64; self.original_length];
981 for (&idx, &val) in self.sparse_indices.iter().zip(self.sparse_values.iter()) {
982 if idx < coeffs.len() {
983 coeffs[idx] = val;
984 }
985 }
986 DiscreteWaveletTransform::idwt_haar(&coeffs)
987 }
988
989 pub fn compression_ratio(&self) -> f64 {
993 if self.nonzero_count == 0 {
994 return self.original_length as f64;
995 }
996 self.original_length as f64 / self.nonzero_count as f64
997 }
998
999 pub fn reconstruction_error(&self, original: &[f64]) -> f64 {
1001 let reconstructed = self.decompress();
1002 let n = original.len().min(reconstructed.len());
1003 if n == 0 {
1004 return 0.0;
1005 }
1006 let mse: f64 = original
1007 .iter()
1008 .zip(reconstructed.iter())
1009 .map(|(a, b)| (a - b).powi(2))
1010 .sum::<f64>()
1011 / n as f64;
1012 mse.sqrt()
1013 }
1014
1015 pub fn psnr(&self, original: &[f64]) -> f64 {
1019 let rmse = self.reconstruction_error(original);
1020 if rmse < 1e-30 {
1021 return f64::INFINITY;
1022 }
1023 let max_val = original.iter().map(|x| x.abs()).fold(0.0_f64, f64::max);
1024 if max_val < 1e-30 {
1025 return 0.0;
1026 }
1027 20.0 * (max_val / rmse).log10()
1028 }
1029}
1030
1031pub struct MultiResolutionAnalysis;
1040
1041impl MultiResolutionAnalysis {
1042 pub fn approximate_coefficients(signal: &[f64], level: usize) -> Vec<f64> {
1047 DiscreteWaveletTransform::dwt_level(signal, level)
1048 }
1049
1050 pub fn detail_coefficients(signal: &[f64], level: usize) -> Vec<f64> {
1055 if level == 0 {
1056 return signal.to_vec();
1057 }
1058 let mut approx = signal.to_vec();
1059 for _ in 0..level - 1 {
1060 if approx.len() < 2 {
1061 break;
1062 }
1063 let (a, _) = HaarWavelet::forward(&approx);
1064 approx = a;
1065 }
1066 if approx.len() < 2 {
1067 return approx;
1068 }
1069 let (_, detail) = HaarWavelet::forward(&approx);
1070 detail
1071 }
1072
1073 pub fn reconstruct_from_level(approx: &[f64], details: &[Vec<f64>]) -> Vec<f64> {
1077 let mut signal = approx.to_vec();
1078 for detail in details {
1079 signal = HaarWavelet::inverse(&signal, detail);
1080 }
1081 signal
1082 }
1083}
1084
1085pub struct PoddedWavelet;
1094
1095impl PoddedWavelet {
1096 pub fn turbulence_spectrum_wavelet(velocity_field: &[f64], dt: f64) -> Vec<(f64, f64)> {
1102 if velocity_field.len() < 2 {
1103 return Vec::new();
1104 }
1105 let decomp = HaarWavelet::decompose_level(velocity_field, 8);
1106 let n_levels = decomp.len();
1107 let mut result = Vec::with_capacity(n_levels);
1108 for (level_idx, coeffs) in decomp.iter().enumerate() {
1109 if coeffs.is_empty() {
1110 continue;
1111 }
1112 let energy: f64 = coeffs.iter().map(|x| x * x).sum::<f64>() / coeffs.len() as f64;
1113 let scale = 2.0_f64.powi(level_idx as i32);
1115 let freq = 1.0 / (scale * dt.max(1e-30));
1116 result.push((freq, energy));
1117 }
1118 result
1119 }
1120
1121 pub fn shock_detection(signal: &[f64], threshold: f64) -> Vec<usize> {
1127 if signal.len() < 2 {
1128 return Vec::new();
1129 }
1130 signal
1131 .windows(2)
1132 .enumerate()
1133 .filter_map(|(i, w)| {
1134 let diff = (w[1] - w[0]).abs();
1135 if diff > threshold { Some(i) } else { None }
1136 })
1137 .collect()
1138 }
1139
1140 pub fn intermittency_measure(signal: &[f64], scale: f64) -> f64 {
1145 let n = signal.len();
1146 if n < 4 {
1147 return 0.0;
1148 }
1149 let dt = 1.0;
1151 let scales = [scale];
1152 let scalogram = ContinuousWavelet::cwt_morlet(signal, dt, &scales);
1153 if scalogram.is_empty() {
1154 return 0.0;
1155 }
1156 let coeffs = &scalogram[0];
1157 let n_c = coeffs.len() as f64;
1158 if n_c < 2.0 {
1159 return 0.0;
1160 }
1161 let mean = coeffs.iter().sum::<f64>() / n_c;
1162 let variance = coeffs.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / n_c;
1163 if variance < 1e-30 {
1164 return 0.0;
1165 }
1166 let kurtosis =
1167 coeffs.iter().map(|x| (x - mean).powi(4)).sum::<f64>() / (n_c * variance * variance);
1168 kurtosis - 3.0 }
1170}
1171
1172#[cfg(test)]
1177mod tests {
1178 use super::*;
1179
1180 #[test]
1183 fn test_haar_forward_basic() {
1184 let signal = vec![1.0, 3.0, 5.0, 11.0];
1185 let (a, d) = HaarWavelet::forward(&signal);
1186 assert_eq!(a.len(), 2);
1187 assert_eq!(d.len(), 2);
1188 }
1189
1190 #[test]
1191 fn test_haar_perfect_reconstruction() {
1192 let signal = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
1193 let (a, d) = HaarWavelet::forward(&signal);
1194 let reconstructed = HaarWavelet::inverse(&a, &d);
1195 for (orig, rec) in signal.iter().zip(reconstructed.iter()) {
1196 assert!((orig - rec).abs() < 1e-10, "{orig} vs {rec}");
1197 }
1198 }
1199
1200 #[test]
1201 fn test_haar_energy_conservation() {
1202 let signal: Vec<f64> = (1..=8).map(|x| x as f64).collect();
1203 let energy_in: f64 = signal.iter().map(|x| x * x).sum();
1204 let (a, d) = HaarWavelet::forward(&signal);
1205 let energy_out: f64 =
1206 a.iter().map(|x| x * x).sum::<f64>() + d.iter().map(|x| x * x).sum::<f64>();
1207 assert!(
1208 (energy_in - energy_out).abs() < 1e-8,
1209 "energy not conserved"
1210 );
1211 }
1212
1213 #[test]
1214 fn test_haar_constant_signal_zero_detail() {
1215 let signal = vec![5.0; 8];
1216 let (_a, d) = HaarWavelet::forward(&signal);
1217 for x in &d {
1218 assert!(x.abs() < 1e-12, "detail should be zero for constant signal");
1219 }
1220 }
1221
1222 #[test]
1223 fn test_haar_decompose_level_count() {
1224 let signal: Vec<f64> = (0..16).map(|x| x as f64).collect();
1225 let levels = HaarWavelet::decompose_level(&signal, 3);
1226 assert_eq!(levels.len(), 4, "should return levels+1 = 4 entries");
1227 }
1228
1229 #[test]
1230 fn test_haar_decompose_level_first_is_coarsest() {
1231 let signal: Vec<f64> = (0..16).map(|x| x as f64).collect();
1232 let levels = HaarWavelet::decompose_level(&signal, 3);
1233 let coarsest = &levels[0];
1235 let finest_detail = &levels[levels.len() - 1];
1236 assert!(coarsest.len() <= finest_detail.len());
1237 }
1238
1239 #[test]
1240 fn test_haar_inverse_length() {
1241 let a = vec![1.0, 2.0];
1242 let d = vec![0.5, 0.3];
1243 let out = HaarWavelet::inverse(&a, &d);
1244 assert_eq!(out.len(), 4);
1245 }
1246
1247 #[test]
1250 fn test_d4_forward_length() {
1251 let signal: Vec<f64> = (0..16).map(|x| x as f64).collect();
1252 let (a, d) = Daubechies4::forward(&signal);
1253 assert_eq!(a.len(), 8);
1254 assert_eq!(d.len(), 8);
1255 }
1256
1257 #[test]
1258 fn test_d4_filter_norms() {
1259 let h_norm: f64 = Daubechies4::H.iter().map(|x| x * x).sum::<f64>().sqrt();
1261 let g_norm: f64 = Daubechies4::G.iter().map(|x| x * x).sum::<f64>().sqrt();
1262 assert!((h_norm - 1.0).abs() < 1e-7, "H norm = {h_norm}");
1263 assert!((g_norm - 1.0).abs() < 1e-7, "G norm = {g_norm}");
1264 }
1265
1266 #[test]
1267 fn test_d4_constant_signal() {
1268 let signal = vec![1.0; 8];
1270 let (a, d) = Daubechies4::forward(&signal);
1271 let rec = Daubechies4::inverse(&a, &d);
1272 let sum_in: f64 = signal.iter().sum();
1274 let sum_rec: f64 = rec.iter().sum();
1275 assert!((sum_in - sum_rec).abs() < 1e-6, "{sum_in} vs {sum_rec}");
1276 }
1277
1278 #[test]
1279 fn test_d4_energy_approximately_conserved() {
1280 let signal: Vec<f64> = (0..16).map(|x| (x as f64).sin()).collect();
1281 let e_in: f64 = signal.iter().map(|x| x * x).sum();
1282 let (a, d) = Daubechies4::forward(&signal);
1283 let e_out: f64 =
1284 a.iter().map(|x| x * x).sum::<f64>() + d.iter().map(|x| x * x).sum::<f64>();
1285 assert!(
1286 (e_in - e_out).abs() / e_in.max(1e-12) < 0.01,
1287 "D4 energy deviation too large: {e_in} vs {e_out}"
1288 );
1289 }
1290
1291 #[test]
1294 fn test_sym4_filter_norms() {
1295 let h_norm: f64 = Symlet4::H.iter().map(|x| x * x).sum::<f64>().sqrt();
1296 let g_norm: f64 = Symlet4::G.iter().map(|x| x * x).sum::<f64>().sqrt();
1297 assert!((h_norm - 1.0).abs() < 1e-7, "Sym4 H norm = {h_norm}");
1298 assert!((g_norm - 1.0).abs() < 1e-7, "Sym4 G norm = {g_norm}");
1299 }
1300
1301 #[test]
1302 fn test_sym4_forward_length() {
1303 let signal: Vec<f64> = (0..32).map(|x| x as f64).collect();
1304 let (a, d) = Symlet4::forward(&signal);
1305 assert_eq!(a.len(), 16);
1306 assert_eq!(d.len(), 16);
1307 }
1308
1309 #[test]
1310 fn test_sym4_energy_approximately_conserved() {
1311 let signal: Vec<f64> = (0..32).map(|x| (x as f64 * 0.5).sin()).collect();
1312 let e_in: f64 = signal.iter().map(|x| x * x).sum();
1313 let (a, d) = Symlet4::forward(&signal);
1314 let e_out: f64 =
1315 a.iter().map(|x| x * x).sum::<f64>() + d.iter().map(|x| x * x).sum::<f64>();
1316 assert!(
1317 (e_in - e_out).abs() / e_in.max(1e-12) < 0.02,
1318 "Sym4 energy deviation: {e_in} vs {e_out}"
1319 );
1320 }
1321
1322 #[test]
1323 fn test_sym4_inverse_length() {
1324 let a = vec![1.0; 8];
1325 let d = vec![0.5; 8];
1326 let out = Symlet4::inverse(&a, &d);
1327 assert_eq!(out.len(), 16);
1328 }
1329
1330 #[test]
1333 fn test_wavelet_transform_haar_forward() {
1334 let wt = WaveletTransform::new(WaveletFamily::Haar);
1335 let signal = vec![1.0, 2.0, 3.0, 4.0];
1336 let (a, d) = wt.forward_one_level(&signal);
1337 assert_eq!(a.len(), 2);
1338 assert_eq!(d.len(), 2);
1339 }
1340
1341 #[test]
1342 fn test_wavelet_transform_d4_roundtrip_length() {
1343 let wt = WaveletTransform::new(WaveletFamily::Daubechies4);
1344 let signal: Vec<f64> = (0..16).map(|x| x as f64).collect();
1345 let (a, d) = wt.forward_one_level(&signal);
1346 let rec = wt.inverse_one_level(&a, &d);
1347 assert_eq!(rec.len(), signal.len());
1348 }
1349
1350 #[test]
1351 fn test_wavelet_transform_sym4_forward() {
1352 let wt = WaveletTransform::new(WaveletFamily::Symlet4);
1353 let signal: Vec<f64> = (0..16).map(|x| x as f64).collect();
1354 let (a, d) = wt.forward_one_level(&signal);
1355 assert_eq!(a.len(), 8);
1356 assert_eq!(d.len(), 8);
1357 }
1358
1359 #[test]
1360 fn test_wavelet_transform_energy() {
1361 let coeffs = vec![3.0, 4.0];
1362 assert!((WaveletTransform::energy(&coeffs) - 25.0).abs() < 1e-12);
1363 }
1364
1365 #[test]
1366 fn test_wavelet_transform_entropy_uniform() {
1367 let coeffs = vec![1.0; 4];
1369 let h = WaveletTransform::entropy(&coeffs);
1370 assert!(h > 0.0);
1371 }
1372
1373 #[test]
1374 fn test_wavelet_transform_entropy_spike() {
1375 let mut coeffs = vec![0.0; 8];
1377 coeffs[0] = 1.0;
1378 let h = WaveletTransform::entropy(&coeffs);
1379 assert!(h.abs() < 1e-12);
1380 }
1381
1382 #[test]
1385 fn test_multilevel_haar_reconstruction() {
1386 let signal: Vec<f64> = (0..16).map(|x| x as f64).collect();
1387 let mld = MultilevelDwt::decompose(&signal, 3, WaveletFamily::Haar);
1388 let rec = mld.reconstruct();
1389 assert_eq!(rec.len(), signal.len());
1390 for (a, b) in signal.iter().zip(rec.iter()) {
1391 assert!((a - b).abs() < 1e-8, "MultilevelDwt roundtrip: {a} vs {b}");
1392 }
1393 }
1394
1395 #[test]
1396 fn test_multilevel_d4_reconstruction() {
1397 let signal: Vec<f64> = (0..32).map(|x| (x as f64 * 0.3).sin()).collect();
1398 let mld = MultilevelDwt::decompose(&signal, 2, WaveletFamily::Daubechies4);
1399 let rec = mld.reconstruct();
1400 assert_eq!(rec.len(), signal.len());
1401 }
1402
1403 #[test]
1404 fn test_multilevel_approx_coarsest() {
1405 let signal: Vec<f64> = (0..16).map(|x| x as f64).collect();
1406 let mld = MultilevelDwt::decompose(&signal, 3, WaveletFamily::Haar);
1407 assert_eq!(mld.approx.len(), 2);
1409 }
1410
1411 #[test]
1412 fn test_multilevel_detail_count() {
1413 let signal: Vec<f64> = (0..16).map(|x| x as f64).collect();
1414 let mld = MultilevelDwt::decompose(&signal, 3, WaveletFamily::Haar);
1415 assert_eq!(mld.details.len(), 3);
1416 }
1417
1418 #[test]
1419 fn test_multilevel_total_energy_conserved() {
1420 let signal: Vec<f64> = (0..16).map(|x| (x as f64).cos()).collect();
1421 let e_in: f64 = signal.iter().map(|x| x * x).sum();
1422 let mld = MultilevelDwt::decompose(&signal, 3, WaveletFamily::Haar);
1423 let e_stored = mld.total_energy();
1424 assert!(
1425 (e_in - e_stored).abs() / e_in.max(1e-12) < 1e-8,
1426 "energy conserved: {e_in} vs {e_stored}"
1427 );
1428 }
1429
1430 #[test]
1431 fn test_multilevel_level_entropies_count() {
1432 let signal: Vec<f64> = (0..16).map(|x| x as f64).collect();
1433 let mld = MultilevelDwt::decompose(&signal, 3, WaveletFamily::Haar);
1434 let entropies = mld.level_entropies();
1435 assert_eq!(entropies.len(), 4);
1437 }
1438
1439 #[test]
1442 fn test_packet_leaf_count() {
1443 let signal: Vec<f64> = (0..16).map(|x| x as f64).collect();
1444 let pkt = WaveletPacket::new_haar(&signal, 3);
1445 assert_eq!(pkt.coefficients.len(), 8); }
1447
1448 #[test]
1449 fn test_packet_energy_distribution_length() {
1450 let signal: Vec<f64> = (0..8).map(|x| x as f64).collect();
1451 let pkt = WaveletPacket::new_haar(&signal, 2);
1452 let energies = pkt.energy_distribution();
1453 assert_eq!(energies.len(), 4);
1454 }
1455
1456 #[test]
1457 fn test_packet_best_basis_nonempty() {
1458 let signal: Vec<f64> = (0..8).map(|x| (x as f64).sin()).collect();
1459 let pkt = WaveletPacket::new_haar(&signal, 2);
1460 let bb = pkt.best_basis();
1461 assert!(!bb.is_empty());
1462 }
1463
1464 #[test]
1465 fn test_packet_total_energy_conserved() {
1466 let signal: Vec<f64> = (0..16).map(|x| x as f64).collect();
1467 let e_in: f64 = signal.iter().map(|x| x * x).sum();
1468 let pkt = WaveletPacket::new_haar(&signal, 1); let e_out: f64 = pkt.energy_distribution().iter().sum();
1470 assert!((e_in - e_out).abs() < 1e-6, "packet energy not conserved");
1471 }
1472
1473 #[test]
1474 fn test_packet_best_basis_entropy_nonempty() {
1475 let signal: Vec<f64> = (0..16).map(|x| (x as f64 * 0.5).sin()).collect();
1476 let pkt = WaveletPacket::new_haar(&signal, 2);
1477 let bb = pkt.best_basis_entropy();
1478 assert!(!bb.is_empty());
1479 }
1480
1481 #[test]
1482 fn test_packet_entropy_distribution_length() {
1483 let signal: Vec<f64> = (0..8).map(|x| x as f64).collect();
1484 let pkt = WaveletPacket::new_haar(&signal, 2);
1485 let ent = pkt.entropy_distribution();
1486 assert_eq!(ent.len(), 4);
1487 }
1488
1489 #[test]
1490 fn test_packet_new_with_family() {
1491 let signal: Vec<f64> = (0..16).map(|x| x as f64).collect();
1492 let pkt = WaveletPacket::new(&signal, 2, WaveletFamily::Daubechies4);
1493 assert_eq!(pkt.coefficients.len(), 4); }
1495
1496 #[test]
1497 fn test_packet_total_energy_method() {
1498 let signal: Vec<f64> = (0..8).map(|x| x as f64).collect();
1499 let pkt = WaveletPacket::new_haar(&signal, 1);
1500 assert!(pkt.total_energy() > 0.0);
1501 }
1502
1503 #[test]
1506 fn test_morlet_at_zero() {
1507 let v = ContinuousWavelet::morlet(0.0, 1.0);
1509 assert!((v - 1.0).abs() < 1e-12);
1510 }
1511
1512 #[test]
1513 fn test_morlet_decays_at_large_t() {
1514 let v0 = ContinuousWavelet::morlet(0.0, 1.0).abs();
1515 let v10 = ContinuousWavelet::morlet(10.0, 1.0).abs();
1516 assert!(v10 < v0 * 0.01, "Morlet should decay: {v10} vs {v0}");
1517 }
1518
1519 #[test]
1520 fn test_mexican_hat_at_zero() {
1521 let v = ContinuousWavelet::mexican_hat(0.0, 1.0);
1522 assert!((v - 1.0).abs() < 1e-12);
1523 }
1524
1525 #[test]
1526 fn test_mexican_hat_sign_change() {
1527 let sigma = 1.0;
1529 let v = ContinuousWavelet::mexican_hat(sigma, sigma);
1530 assert!(v.abs() < 1e-12);
1531 }
1532
1533 #[test]
1534 fn test_paul_wavelet_finite() {
1535 let v = ContinuousWavelet::paul(0.5, 4);
1536 assert!(v.is_finite());
1537 }
1538
1539 #[test]
1540 fn test_paul_wavelet_decay() {
1541 let v_near = ContinuousWavelet::paul(0.0, 2).abs();
1543 let v_far = ContinuousWavelet::paul(100.0, 2).abs();
1544 assert!(
1545 v_far < v_near,
1546 "Paul wavelet should decay: {v_far} vs {v_near}"
1547 );
1548 }
1549
1550 #[test]
1551 fn test_cwt_morlet_shape() {
1552 let signal: Vec<f64> = (0..16).map(|x| (x as f64 * 0.5).sin()).collect();
1553 let scales = vec![1.0, 2.0, 4.0];
1554 let scalogram = ContinuousWavelet::cwt_morlet(&signal, 1.0, &scales);
1555 assert_eq!(scalogram.len(), 3);
1556 assert_eq!(scalogram[0].len(), 16);
1557 }
1558
1559 #[test]
1560 fn test_cwt_mexican_hat_shape() {
1561 let signal: Vec<f64> = (0..16).map(|x| (x as f64 * 0.5).sin()).collect();
1562 let scales = vec![1.0, 2.0];
1563 let scalogram = ContinuousWavelet::cwt_mexican_hat(&signal, 1.0, &scales);
1564 assert_eq!(scalogram.len(), 2);
1565 assert_eq!(scalogram[0].len(), 16);
1566 }
1567
1568 #[test]
1569 fn test_cwt_paul_shape() {
1570 let signal: Vec<f64> = (0..8).map(|x| (x as f64).sin()).collect();
1571 let scales = vec![1.0, 2.0];
1572 let scalogram = ContinuousWavelet::cwt_paul(&signal, 1.0, &scales, 4);
1573 assert_eq!(scalogram.len(), 2);
1574 assert_eq!(scalogram[0].len(), 8);
1575 }
1576
1577 #[test]
1578 fn test_instantaneous_frequency_length() {
1579 let signal: Vec<f64> = (0..8).map(|x| (x as f64).sin()).collect();
1580 let scales = vec![1.0, 2.0];
1581 let scalogram = ContinuousWavelet::cwt_morlet(&signal, 1.0, &scales);
1582 let freq = ContinuousWavelet::instantaneous_frequency(&scalogram, &scales, 1.0);
1583 assert_eq!(freq.len(), 8);
1584 }
1585
1586 #[test]
1587 fn test_instantaneous_frequency_positive() {
1588 let signal: Vec<f64> = (0..8).map(|x| (x as f64).sin()).collect();
1589 let scales = vec![1.0, 2.0, 4.0];
1590 let scalogram = ContinuousWavelet::cwt_morlet(&signal, 1.0, &scales);
1591 let freq = ContinuousWavelet::instantaneous_frequency(&scalogram, &scales, 1.0);
1592 for f in &freq {
1593 assert!(*f > 0.0, "frequency must be positive");
1594 }
1595 }
1596
1597 #[test]
1598 fn test_scale_averaged_power_length() {
1599 let signal: Vec<f64> = (0..8).map(|x| (x as f64).sin()).collect();
1600 let scales = vec![1.0, 2.0, 4.0];
1601 let scalogram = ContinuousWavelet::cwt_morlet(&signal, 1.0, &scales);
1602 let power = ContinuousWavelet::scale_averaged_power(&scalogram, &scales);
1603 assert_eq!(power.len(), 3);
1604 }
1605
1606 #[test]
1609 fn test_dwt_haar_length_preserved() {
1610 let signal: Vec<f64> = (0..8).map(|x| x as f64).collect();
1611 let coeffs = DiscreteWaveletTransform::dwt_haar(&signal);
1612 assert_eq!(coeffs.len(), signal.len());
1613 }
1614
1615 #[test]
1616 fn test_idwt_haar_roundtrip() {
1617 let signal: Vec<f64> = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
1618 let coeffs = DiscreteWaveletTransform::dwt_haar(&signal);
1619 let reconstructed = DiscreteWaveletTransform::idwt_haar(&coeffs);
1620 for (a, b) in signal.iter().zip(reconstructed.iter()) {
1621 assert!((a - b).abs() < 1e-8, "IDWT roundtrip failed: {a} vs {b}");
1622 }
1623 }
1624
1625 #[test]
1626 fn test_dwt_level_length() {
1627 let signal: Vec<f64> = (0..16).map(|x| x as f64).collect();
1628 let approx = DiscreteWaveletTransform::dwt_level(&signal, 2);
1629 assert_eq!(approx.len(), 4); }
1631
1632 #[test]
1633 fn test_wavelet_energy_nonneg() {
1634 let coeffs = vec![1.0, -2.0, 3.0];
1635 let e = DiscreteWaveletTransform::wavelet_energy(&coeffs);
1636 assert!(e >= 0.0);
1637 assert!((e - 14.0).abs() < 1e-12);
1638 }
1639
1640 #[test]
1641 fn test_wavelet_entropy_nonneg() {
1642 let coeffs = vec![1.0, 2.0, 3.0, 4.0];
1643 let h = DiscreteWaveletTransform::wavelet_entropy(&coeffs);
1644 assert!(h >= 0.0);
1645 }
1646
1647 #[test]
1648 fn test_wavelet_entropy_uniform_max() {
1649 let uniform = vec![1.0; 8];
1651 let spike: Vec<f64> = {
1652 let mut v = vec![0.0; 8];
1653 v[0] = 8.0;
1654 v
1655 };
1656 let h_uniform = DiscreteWaveletTransform::wavelet_entropy(&uniform);
1657 let h_spike = DiscreteWaveletTransform::wavelet_entropy(&spike);
1658 assert!(h_uniform >= h_spike, "uniform should have higher entropy");
1659 }
1660
1661 #[test]
1664 fn test_hard_threshold_zeroes_small() {
1665 let mut coeffs = vec![0.1, 0.5, -0.2, 1.0];
1666 WaveletDenoising::hard_threshold(&mut coeffs, 0.3);
1667 assert_eq!(coeffs[0], 0.0);
1668 assert_eq!(coeffs[1], 0.5);
1669 assert_eq!(coeffs[2], 0.0);
1670 assert_eq!(coeffs[3], 1.0);
1671 }
1672
1673 #[test]
1674 fn test_soft_threshold_shrinks() {
1675 let mut coeffs = vec![1.0, -2.0, 0.5];
1676 WaveletDenoising::soft_threshold(&mut coeffs, 0.5);
1677 assert!((coeffs[0] - 0.5).abs() < 1e-12);
1678 assert!((coeffs[1] + 1.5).abs() < 1e-12);
1679 assert!(coeffs[2].abs() < 1e-12);
1680 }
1681
1682 #[test]
1683 fn test_universal_threshold_positive() {
1684 let signal: Vec<f64> = (0..64)
1685 .map(|i| (i as f64 * 0.1).sin() + 0.1 * (i as f64))
1686 .collect();
1687 let t = WaveletDenoising::universal_threshold(&signal);
1688 assert!(t >= 0.0, "threshold must be non-negative");
1689 }
1690
1691 #[test]
1692 fn test_sure_threshold_nonneg() {
1693 let signal = vec![1.0, 2.0, 3.0, 0.1, 0.05];
1694 let t = WaveletDenoising::sure_threshold(&signal);
1695 assert!(t >= 0.0);
1696 }
1697
1698 #[test]
1699 fn test_soft_threshold_zero_input() {
1700 let mut coeffs: Vec<f64> = vec![];
1701 WaveletDenoising::soft_threshold(&mut coeffs, 1.0);
1702 assert!(coeffs.is_empty());
1703 }
1704
1705 #[test]
1706 fn test_denoise_signal_length_preserved() {
1707 let signal: Vec<f64> = (0..16).map(|x| (x as f64 * 0.5).sin()).collect();
1708 let denoised = WaveletDenoising::denoise_signal(&signal);
1709 assert_eq!(denoised.len(), signal.len());
1710 }
1711
1712 #[test]
1713 fn test_denoise_small_signal_unchanged() {
1714 let signal = vec![1.0];
1715 let denoised = WaveletDenoising::denoise_signal(&signal);
1716 assert_eq!(denoised, signal);
1717 }
1718
1719 #[test]
1722 fn test_compression_ratio_ge_one() {
1723 let signal: Vec<f64> = (0..64).map(|x| (x as f64 * 0.5).sin()).collect();
1724 let comp = WaveletCompression::compress(&signal, 0.90);
1725 assert!(comp.compression_ratio() >= 1.0);
1726 }
1727
1728 #[test]
1729 fn test_compression_lossless_full_retention() {
1730 let signal: Vec<f64> = (0..8).map(|x| x as f64).collect();
1731 let comp = WaveletCompression::compress(&signal, 1.0);
1732 let rec = comp.decompress();
1733 assert_eq!(rec.len(), signal.len());
1734 for (a, b) in signal.iter().zip(rec.iter()) {
1735 assert!((a - b).abs() < 1e-6, "lossless compression: {a} vs {b}");
1736 }
1737 }
1738
1739 #[test]
1740 fn test_compression_original_length_stored() {
1741 let signal: Vec<f64> = (0..32).map(|x| x as f64).collect();
1742 let comp = WaveletCompression::compress(&signal, 0.95);
1743 assert_eq!(comp.original_length, 32);
1744 }
1745
1746 #[test]
1747 fn test_compression_reconstruction_error_finite() {
1748 let signal: Vec<f64> = (0..32).map(|x| (x as f64 * 0.5).sin()).collect();
1749 let comp = WaveletCompression::compress(&signal, 0.99);
1750 let err = comp.reconstruction_error(&signal);
1751 assert!(err.is_finite());
1752 assert!(err >= 0.0);
1753 }
1754
1755 #[test]
1756 fn test_compression_psnr_nonneg() {
1757 let signal: Vec<f64> = (0..32).map(|x| (x as f64 * 0.3).cos()).collect();
1758 let comp = WaveletCompression::compress(&signal, 0.99);
1759 let psnr = comp.psnr(&signal);
1760 assert!(psnr >= 0.0 || psnr.is_infinite());
1761 }
1762
1763 #[test]
1764 fn test_compression_sparse_indices_nonempty() {
1765 let signal: Vec<f64> = (0..16).map(|x| (x as f64).sin()).collect();
1766 let comp = WaveletCompression::compress(&signal, 0.8);
1767 assert!(!comp.sparse_indices.is_empty());
1768 }
1769
1770 #[test]
1773 fn test_mra_approx_length() {
1774 let signal: Vec<f64> = (0..16).map(|x| x as f64).collect();
1775 let approx = MultiResolutionAnalysis::approximate_coefficients(&signal, 2);
1776 assert_eq!(approx.len(), 4);
1777 }
1778
1779 #[test]
1780 fn test_mra_detail_length() {
1781 let signal: Vec<f64> = (0..16).map(|x| x as f64).collect();
1782 let detail = MultiResolutionAnalysis::detail_coefficients(&signal, 1);
1783 assert_eq!(detail.len(), 8);
1784 }
1785
1786 #[test]
1787 fn test_mra_detail_level_zero() {
1788 let signal = vec![1.0, 2.0, 3.0, 4.0];
1789 let d = MultiResolutionAnalysis::detail_coefficients(&signal, 0);
1790 assert_eq!(d, signal);
1792 }
1793
1794 #[test]
1795 fn test_mra_reconstruct_single_level() {
1796 let signal: Vec<f64> = (0..8).map(|x| x as f64).collect();
1797 let approx = MultiResolutionAnalysis::approximate_coefficients(&signal, 1);
1798 let detail = MultiResolutionAnalysis::detail_coefficients(&signal, 1);
1799 let rec = MultiResolutionAnalysis::reconstruct_from_level(&approx, &[detail]);
1800 assert_eq!(rec.len(), signal.len());
1801 for (a, b) in signal.iter().zip(rec.iter()) {
1802 assert!((a - b).abs() < 1e-8, "MRA roundtrip: {a} vs {b}");
1803 }
1804 }
1805
1806 #[test]
1809 fn test_turbulence_spectrum_nonempty() {
1810 let vel: Vec<f64> = (0..64).map(|i| (i as f64 * 0.3).sin()).collect();
1811 let spectrum = PoddedWavelet::turbulence_spectrum_wavelet(&vel, 0.01);
1812 assert!(!spectrum.is_empty(), "spectrum should not be empty");
1813 }
1814
1815 #[test]
1816 fn test_turbulence_spectrum_positive_freq() {
1817 let vel: Vec<f64> = (0..32).map(|i| i as f64).collect();
1818 let spectrum = PoddedWavelet::turbulence_spectrum_wavelet(&vel, 0.01);
1819 for (f, _e) in &spectrum {
1820 assert!(*f > 0.0, "frequencies must be positive");
1821 }
1822 }
1823
1824 #[test]
1825 fn test_turbulence_spectrum_nonneg_energy() {
1826 let vel: Vec<f64> = (0..32).map(|i| (i as f64).cos()).collect();
1827 for (_f, e) in PoddedWavelet::turbulence_spectrum_wavelet(&vel, 0.01) {
1828 assert!(e >= 0.0);
1829 }
1830 }
1831
1832 #[test]
1833 fn test_shock_detection_step_function() {
1834 let mut signal = vec![0.0_f64; 16];
1836 for i in 8..16 {
1837 signal[i] = 10.0;
1838 }
1839 let shocks = PoddedWavelet::shock_detection(&signal, 0.5);
1840 assert!(!shocks.is_empty(), "should detect discontinuity");
1841 }
1842
1843 #[test]
1844 fn test_shock_detection_smooth_signal_empty() {
1845 let signal: Vec<f64> = (0..16).map(|i| (i as f64 * 0.1).sin()).collect();
1847 let shocks = PoddedWavelet::shock_detection(&signal, 1000.0);
1848 assert!(
1849 shocks.is_empty(),
1850 "no shocks expected for smooth signal at high threshold"
1851 );
1852 }
1853
1854 #[test]
1855 fn test_shock_detection_empty_signal() {
1856 let shocks = PoddedWavelet::shock_detection(&[], 0.5);
1857 assert!(shocks.is_empty());
1858 }
1859
1860 #[test]
1861 fn test_intermittency_small_signal() {
1862 let v = PoddedWavelet::intermittency_measure(&[1.0, 2.0], 1.0);
1864 assert_eq!(v, 0.0);
1865 }
1866
1867 #[test]
1868 fn test_intermittency_gaussian_near_zero() {
1869 let signal: Vec<f64> = (0..64).map(|i| (i as f64 * 0.2).sin()).collect();
1871 let k = PoddedWavelet::intermittency_measure(&signal, 2.0);
1872 assert!(k.is_finite(), "intermittency should be finite");
1874 }
1875}