use faer::Mat;
use crate::lifting::{LiftingConfig, LiftingInfo};
#[derive(Debug, thiserror::Error)]
pub enum DmdError {
#[error("invalid input: {0}")]
InvalidInput(String),
#[error("SVD computation failed: {0}")]
SvdFailed(String),
#[error("eigendecomposition failed: {0}")]
EigenFailed(String),
#[error("linear solve failed: {0}")]
SolveFailed(String),
#[error("numerical error: {0}")]
NumericalError(String),
}
#[derive(Debug, Clone)]
pub struct DmdConfig {
pub rank: Option<usize>,
pub center: bool,
pub dt: f64,
pub lifting: Option<LiftingConfig>,
}
impl Default for DmdConfig {
fn default() -> Self {
Self {
rank: None,
center: false,
dt: 1.0,
lifting: None,
}
}
}
#[derive(Debug, Clone)]
pub struct SvdComponents {
pub u: Mat<f64>,
pub s: Vec<f64>,
pub v: Mat<f64>,
}
#[derive(Debug, Clone, Copy)]
pub struct C64 {
pub re: f64,
pub im: f64,
}
impl C64 {
pub fn new(re: f64, im: f64) -> Self {
Self { re, im }
}
pub fn norm(&self) -> f64 {
(self.re * self.re + self.im * self.im).sqrt()
}
pub fn norm_sqr(&self) -> f64 {
self.re * self.re + self.im * self.im
}
pub fn arg(&self) -> f64 {
self.im.atan2(self.re)
}
pub fn conj(&self) -> Self {
Self {
re: self.re,
im: -self.im,
}
}
pub fn powf(&self, p: f64) -> Self {
let r = self.norm();
let theta = self.arg();
let rp = r.powf(p);
Self {
re: rp * (p * theta).cos(),
im: rp * (p * theta).sin(),
}
}
pub fn zero() -> Self {
Self { re: 0.0, im: 0.0 }
}
}
impl std::ops::Add for C64 {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self {
re: self.re + rhs.re,
im: self.im + rhs.im,
}
}
}
impl std::ops::AddAssign for C64 {
fn add_assign(&mut self, rhs: Self) {
self.re += rhs.re;
self.im += rhs.im;
}
}
impl std::ops::Sub for C64 {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
Self {
re: self.re - rhs.re,
im: self.im - rhs.im,
}
}
}
impl std::ops::Mul for C64 {
type Output = Self;
fn mul(self, rhs: Self) -> Self {
Self {
re: self.re * rhs.re - self.im * rhs.im,
im: self.re * rhs.im + self.im * rhs.re,
}
}
}
impl std::ops::Mul<f64> for C64 {
type Output = Self;
fn mul(self, rhs: f64) -> Self {
Self {
re: self.re * rhs,
im: self.im * rhs,
}
}
}
impl std::ops::Div for C64 {
type Output = Self;
fn div(self, rhs: Self) -> Self {
let denom = rhs.norm_sqr();
Self {
re: (self.re * rhs.re + self.im * rhs.im) / denom,
im: (self.im * rhs.re - self.re * rhs.im) / denom,
}
}
}
impl std::ops::Div<f64> for C64 {
type Output = Self;
fn div(self, rhs: f64) -> Self {
Self {
re: self.re / rhs,
im: self.im / rhs,
}
}
}
#[derive(Debug, Clone)]
pub struct DmdResult {
pub a_matrix: Vec<Vec<C64>>,
pub modes: Vec<Vec<C64>>,
pub eigenvalues: Vec<C64>,
pub amplitudes: Vec<C64>,
pub rank: usize,
pub svd: SvdComponents,
pub a_tilde: Vec<Vec<C64>>,
pub x_first: Vec<f64>,
pub x_last: Vec<f64>,
pub data_dim: (usize, usize),
pub center: bool,
pub x_mean: Option<Vec<f64>>,
pub dt: f64,
pub lifting_info: Option<LiftingInfo>,
}
impl DmdResult {
pub fn n_vars_original(&self) -> usize {
match &self.lifting_info {
Some(info) => info.n_vars_original,
None => self.data_dim.0,
}
}
pub fn is_lifted(&self) -> bool {
self.lifting_info.is_some()
}
pub fn mode(&self, j: usize) -> Vec<C64> {
let n_vars = self.data_dim.0;
(0..n_vars).map(|i| self.modes[i][j]).collect()
}
pub fn n_vars(&self) -> usize {
self.data_dim.0
}
}
#[derive(Debug, Clone)]
pub struct ModeInfo {
pub index: usize,
pub eigenvalue: C64,
pub magnitude: f64,
pub phase: f64,
pub frequency: f64,
pub period: f64,
pub growth_rate: f64,
pub half_life: Option<f64>,
pub stability: Stability,
pub amplitude: f64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Stability {
Decaying,
Neutral,
Growing,
}
impl std::fmt::Display for Stability {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Stability::Decaying => write!(f, "decaying"),
Stability::Neutral => write!(f, "neutral"),
Stability::Growing => write!(f, "growing"),
}
}
}
#[derive(Debug, Clone)]
pub struct StabilityResult {
pub is_stable: bool,
pub is_unstable: bool,
pub is_marginal: bool,
pub spectral_radius: f64,
pub mode_stability: Vec<Stability>,
}
#[derive(Debug, Clone)]
pub struct ErrorMetrics {
pub rmse: f64,
pub mae: f64,
pub mape: f64,
pub relative_error: f64,
pub per_variable_rmse: Vec<f64>,
}
#[derive(Debug, Clone, Copy)]
pub enum DominantCriterion {
Amplitude,
Energy,
Stability,
}
#[derive(Debug, Clone)]
pub struct ResidualResult {
pub residual_norm: f64,
pub residual_relative: f64,
pub per_step_residual: Vec<f64>,
pub per_mode_residual: Vec<f64>,
}
#[derive(Debug, Clone)]
pub struct PseudospectrumResult {
pub x: Vec<f64>,
pub y: Vec<f64>,
pub sigma_min: Vec<Vec<f64>>,
pub eigenvalues: Vec<C64>,
pub epsilon: Vec<f64>,
}
#[derive(Debug, Clone)]
pub struct ConvergenceResult {
pub sample_sizes: Vec<usize>,
pub eigenvalues: Vec<Vec<C64>>,
pub eigenvalue_changes: Vec<f64>,
pub convergence_estimate: Option<f64>,
}