1use faer::Mat;
2
3use crate::lifting::{LiftingConfig, LiftingInfo};
4
5#[derive(Debug, thiserror::Error)]
7pub enum DmdError {
8 #[error("invalid input: {0}")]
9 InvalidInput(String),
10
11 #[error("SVD computation failed: {0}")]
12 SvdFailed(String),
13
14 #[error("eigendecomposition failed: {0}")]
15 EigenFailed(String),
16
17 #[error("linear solve failed: {0}")]
18 SolveFailed(String),
19
20 #[error("numerical error: {0}")]
21 NumericalError(String),
22}
23
24#[derive(Debug, Clone)]
26pub struct DmdConfig {
27 pub rank: Option<usize>,
29 pub center: bool,
31 pub dt: f64,
33 pub lifting: Option<LiftingConfig>,
35}
36
37impl Default for DmdConfig {
38 fn default() -> Self {
39 Self {
40 rank: None,
41 center: false,
42 dt: 1.0,
43 lifting: None,
44 }
45 }
46}
47
48#[derive(Debug, Clone)]
50pub struct SvdComponents {
51 pub u: Mat<f64>,
53 pub s: Vec<f64>,
55 pub v: Mat<f64>,
57}
58
59#[derive(Debug, Clone, Copy)]
61pub struct C64 {
62 pub re: f64,
63 pub im: f64,
64}
65
66impl C64 {
67 pub fn new(re: f64, im: f64) -> Self {
69 Self { re, im }
70 }
71
72 pub fn norm(&self) -> f64 {
74 (self.re * self.re + self.im * self.im).sqrt()
75 }
76
77 pub fn norm_sqr(&self) -> f64 {
79 self.re * self.re + self.im * self.im
80 }
81
82 pub fn arg(&self) -> f64 {
84 self.im.atan2(self.re)
85 }
86
87 pub fn conj(&self) -> Self {
89 Self {
90 re: self.re,
91 im: -self.im,
92 }
93 }
94
95 pub fn powf(&self, p: f64) -> Self {
97 let r = self.norm();
98 let theta = self.arg();
99 let rp = r.powf(p);
100 Self {
101 re: rp * (p * theta).cos(),
102 im: rp * (p * theta).sin(),
103 }
104 }
105
106 pub fn zero() -> Self {
108 Self { re: 0.0, im: 0.0 }
109 }
110}
111
112impl std::ops::Add for C64 {
113 type Output = Self;
114 fn add(self, rhs: Self) -> Self {
115 Self {
116 re: self.re + rhs.re,
117 im: self.im + rhs.im,
118 }
119 }
120}
121
122impl std::ops::AddAssign for C64 {
123 fn add_assign(&mut self, rhs: Self) {
124 self.re += rhs.re;
125 self.im += rhs.im;
126 }
127}
128
129impl std::ops::Sub for C64 {
130 type Output = Self;
131 fn sub(self, rhs: Self) -> Self {
132 Self {
133 re: self.re - rhs.re,
134 im: self.im - rhs.im,
135 }
136 }
137}
138
139impl std::ops::Mul for C64 {
140 type Output = Self;
141 fn mul(self, rhs: Self) -> Self {
142 Self {
143 re: self.re * rhs.re - self.im * rhs.im,
144 im: self.re * rhs.im + self.im * rhs.re,
145 }
146 }
147}
148
149impl std::ops::Mul<f64> for C64 {
150 type Output = Self;
151 fn mul(self, rhs: f64) -> Self {
152 Self {
153 re: self.re * rhs,
154 im: self.im * rhs,
155 }
156 }
157}
158
159impl std::ops::Div for C64 {
160 type Output = Self;
161 fn div(self, rhs: Self) -> Self {
162 let denom = rhs.norm_sqr();
163 Self {
164 re: (self.re * rhs.re + self.im * rhs.im) / denom,
165 im: (self.im * rhs.re - self.re * rhs.im) / denom,
166 }
167 }
168}
169
170impl std::ops::Div<f64> for C64 {
171 type Output = Self;
172 fn div(self, rhs: f64) -> Self {
173 Self {
174 re: self.re / rhs,
175 im: self.im / rhs,
176 }
177 }
178}
179
180#[derive(Debug, Clone)]
182pub struct DmdResult {
183 pub a_matrix: Vec<Vec<C64>>,
185 pub modes: Vec<Vec<C64>>,
187 pub eigenvalues: Vec<C64>,
189 pub amplitudes: Vec<C64>,
191 pub rank: usize,
193 pub svd: SvdComponents,
195 pub a_tilde: Vec<Vec<C64>>,
197 pub x_first: Vec<f64>,
199 pub x_last: Vec<f64>,
201 pub data_dim: (usize, usize),
203 pub center: bool,
205 pub x_mean: Option<Vec<f64>>,
207 pub dt: f64,
209 pub lifting_info: Option<LiftingInfo>,
211}
212
213impl DmdResult {
214 pub fn n_vars_original(&self) -> usize {
216 match &self.lifting_info {
217 Some(info) => info.n_vars_original,
218 None => self.data_dim.0,
219 }
220 }
221
222 pub fn is_lifted(&self) -> bool {
224 self.lifting_info.is_some()
225 }
226 pub fn mode(&self, j: usize) -> Vec<C64> {
228 let n_vars = self.data_dim.0;
229 (0..n_vars).map(|i| self.modes[i][j]).collect()
230 }
231
232 pub fn n_vars(&self) -> usize {
234 self.data_dim.0
235 }
236}
237
238#[derive(Debug, Clone)]
240pub struct ModeInfo {
241 pub index: usize,
243 pub eigenvalue: C64,
245 pub magnitude: f64,
247 pub phase: f64,
249 pub frequency: f64,
251 pub period: f64,
253 pub growth_rate: f64,
255 pub half_life: Option<f64>,
257 pub stability: Stability,
259 pub amplitude: f64,
261}
262
263#[derive(Debug, Clone, Copy, PartialEq, Eq)]
265pub enum Stability {
266 Decaying,
267 Neutral,
268 Growing,
269}
270
271impl std::fmt::Display for Stability {
272 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
273 match self {
274 Stability::Decaying => write!(f, "decaying"),
275 Stability::Neutral => write!(f, "neutral"),
276 Stability::Growing => write!(f, "growing"),
277 }
278 }
279}
280
281#[derive(Debug, Clone)]
283pub struct StabilityResult {
284 pub is_stable: bool,
286 pub is_unstable: bool,
288 pub is_marginal: bool,
290 pub spectral_radius: f64,
292 pub mode_stability: Vec<Stability>,
294}
295
296#[derive(Debug, Clone)]
298pub struct ErrorMetrics {
299 pub rmse: f64,
301 pub mae: f64,
303 pub mape: f64,
305 pub relative_error: f64,
307 pub per_variable_rmse: Vec<f64>,
309}
310
311#[derive(Debug, Clone, Copy)]
313pub enum DominantCriterion {
314 Amplitude,
316 Energy,
318 Stability,
320}
321
322#[derive(Debug, Clone)]
324pub struct ResidualResult {
325 pub residual_norm: f64,
327 pub residual_relative: f64,
329 pub per_step_residual: Vec<f64>,
331 pub per_mode_residual: Vec<f64>,
333}
334
335#[derive(Debug, Clone)]
337pub struct PseudospectrumResult {
338 pub x: Vec<f64>,
340 pub y: Vec<f64>,
342 pub sigma_min: Vec<Vec<f64>>,
344 pub eigenvalues: Vec<C64>,
346 pub epsilon: Vec<f64>,
348}
349
350#[derive(Debug, Clone)]
352pub struct ConvergenceResult {
353 pub sample_sizes: Vec<usize>,
355 pub eigenvalues: Vec<Vec<C64>>,
357 pub eigenvalue_changes: Vec<f64>,
359 pub convergence_estimate: Option<f64>,
361}