modewise_operations/
vlmd.rs1use modewise_pipeline::{
2 Artifact, ArtifactKind, Inputs, Operation, OperationDescriptor, Outputs, Port, resolve_params,
3};
4use modewise_vlmd::{Vlmd, VlmdParams};
5use schemars::JsonSchema;
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8
9use crate::input;
10
11#[derive(Serialize, Deserialize, JsonSchema)]
12#[serde(deny_unknown_fields)]
13struct Params {
14 num_modes: usize,
16 #[serde(default)]
18 num_latents: Option<usize>,
19 #[serde(default = "defaults::alpha")]
21 alpha: f64,
22 #[serde(default = "defaults::reg_lambda")]
24 reg_lambda: f64,
25 #[serde(default = "defaults::reg_rho")]
27 reg_rho: f64,
28 #[serde(default = "defaults::tau")]
30 tau: f64,
31 #[serde(default = "defaults::tolerance")]
33 tolerance: f64,
34 #[serde(default = "defaults::max_iterations")]
36 max_iterations: usize,
37}
38
39mod defaults {
40 use modewise_vlmd::VlmdParams;
41
42 fn reference() -> VlmdParams {
43 VlmdParams::new(1)
44 }
45 pub fn alpha() -> f64 {
46 reference().alpha
47 }
48 pub fn reg_lambda() -> f64 {
49 reference().reg_lambda
50 }
51 pub fn reg_rho() -> f64 {
52 reference().reg_rho
53 }
54 pub fn tau() -> f64 {
55 reference().tau
56 }
57 pub fn tolerance() -> f64 {
58 reference().tolerance
59 }
60 pub fn max_iterations() -> usize {
61 reference().max_iterations
62 }
63}
64
65impl Params {
66 fn method(self) -> Result<Vlmd, String> {
67 Vlmd::new(VlmdParams {
68 num_modes: self.num_modes,
69 num_latents: self.num_latents,
70 alpha: self.alpha,
71 reg_lambda: self.reg_lambda,
72 reg_rho: self.reg_rho,
73 tau: self.tau,
74 tolerance: self.tolerance,
75 max_iterations: self.max_iterations,
76 })
77 .map_err(|e| e.to_string())
78 }
79}
80
81pub struct VlmdOperation;
83
84impl Operation for VlmdOperation {
85 fn summary(&self) -> &'static str {
86 "Variational latent mode decomposition: modes of a few latent signals and sparse channel loadings (docs/vlmd.md)."
87 }
88
89 fn params_schema(&self) -> serde_json::Value {
90 modewise_pipeline::schema_of::<Params>()
91 }
92
93 fn descriptor(&self) -> OperationDescriptor {
94 OperationDescriptor {
95 name: "vlmd",
96 version: env!("CARGO_PKG_VERSION"),
97 inputs: &[Port {
98 name: "signal",
99 kinds: &[ArtifactKind::Series],
100 }],
101 outputs: &[
102 Port {
103 name: "modes",
104 kinds: &[ArtifactKind::Decomposition],
105 },
106 Port {
107 name: "latent_modes",
108 kinds: &[ArtifactKind::Decomposition],
109 },
110 Port {
111 name: "loadings",
112 kinds: &[ArtifactKind::Loadings],
113 },
114 ],
115 }
116 }
117
118 fn resolve(&self, params: Value) -> Result<Value, String> {
119 let (typed, resolved) = resolve_params::<Params>(params)?;
120 typed.method()?;
121 Ok(resolved)
122 }
123
124 fn run(&self, params: &Value, inputs: &Inputs) -> Result<Outputs, String> {
125 let method = serde_json::from_value::<Params>(params.clone())
126 .map_err(|e| e.to_string())?
127 .method()?;
128 let Artifact::Series(signal) = input(inputs, "signal")? else {
129 return Err("input \"signal\" must be a series".into());
130 };
131 let out = method.decompose_latent(signal).map_err(|e| e.to_string())?;
132 Ok(Outputs::from([
133 ("modes".into(), Artifact::Decomposition(out.modes)),
134 (
135 "latent_modes".into(),
136 Artifact::Decomposition(out.latent_modes),
137 ),
138 ("loadings".into(), Artifact::Loadings(out.loadings)),
139 ]))
140 }
141}