modewise_operations/
wavelet.rs

1use modewise_decomposition::ModeDecomposer;
2use modewise_pipeline::{
3    Artifact, ArtifactKind, Inputs, Operation, OperationDescriptor, Outputs, Port, resolve_params,
4};
5use modewise_wavelet::{Boundary, ModwtMra};
6use schemars::JsonSchema;
7use serde::{Deserialize, Serialize};
8use serde_json::Value;
9
10use crate::input;
11
12#[derive(Serialize, Deserialize, JsonSchema, Clone, Copy)]
13#[serde(rename_all = "snake_case")]
14enum BoundaryName {
15    Periodic,
16    Reflection,
17}
18
19#[derive(Serialize, Deserialize, JsonSchema)]
20#[serde(deny_unknown_fields)]
21struct Params {
22    /// sym4 is MATLAB's default and Percival and Walden's LA(8).
23    #[serde(default = "sym4")]
24    wavelet: String,
25    /// Deepest level; by default the deepest whose filter fits the series.
26    #[serde(default, skip_serializing_if = "Option::is_none")]
27    levels: Option<usize>,
28    /// `periodic` (as the MODWT is defined) or `reflection` (fewer edge effects for non-periodic series).
29    #[serde(default = "periodic")]
30    boundary: BoundaryName,
31}
32
33fn sym4() -> String {
34    "sym4".into()
35}
36
37fn periodic() -> BoundaryName {
38    BoundaryName::Periodic
39}
40
41impl Params {
42    fn method(&self) -> Result<ModwtMra, String> {
43        let boundary = match self.boundary {
44            BoundaryName::Periodic => Boundary::Periodic,
45            BoundaryName::Reflection => Boundary::Reflection,
46        };
47        ModwtMra::new(&self.wavelet, self.levels, boundary).map_err(|e| e.to_string())
48    }
49}
50
51/// `modwt_mra`: wavelet multiresolution analysis of each channel.
52pub struct ModwtMraOperation;
53
54impl Operation for ModwtMraOperation {
55    fn summary(&self) -> &'static str {
56        "MODWT multiresolution analysis: octave-band details and a smooth that sum to each channel (docs/wavelets.md)."
57    }
58
59    fn params_schema(&self) -> serde_json::Value {
60        modewise_pipeline::schema_of::<Params>()
61    }
62
63    fn descriptor(&self) -> OperationDescriptor {
64        OperationDescriptor {
65            name: "modwt_mra",
66            version: env!("CARGO_PKG_VERSION"),
67            inputs: &[Port {
68                name: "signal",
69                kinds: &[ArtifactKind::Series],
70            }],
71            outputs: &[Port {
72                name: "modes",
73                kinds: &[ArtifactKind::Decomposition],
74            }],
75        }
76    }
77
78    fn resolve(&self, params: Value) -> Result<Value, String> {
79        let (typed, resolved) = resolve_params::<Params>(params)?;
80        typed.method()?;
81        Ok(resolved)
82    }
83
84    fn run(&self, params: &Value, inputs: &Inputs) -> Result<Outputs, String> {
85        let method = serde_json::from_value::<Params>(params.clone())
86            .map_err(|e| e.to_string())?
87            .method()?;
88        let Artifact::Series(signal) = input(inputs, "signal")? else {
89            return Err("input \"signal\" must be a series".into());
90        };
91        let modes = method.decompose(signal).map_err(|e| e.to_string())?;
92        Ok(Outputs::from([(
93            "modes".into(),
94            Artifact::Decomposition(modes),
95        )]))
96    }
97}