R Examples
Complete worked examples with plots showing predicted vs actual results
1. Core DMD -- Predicted vs Actual
This example fits a standard DMD model to a damped oscillator and predicts beyond the training window.
library(koopman.dmd)
# Create a damped oscillator signal
dt <- 0.05
n_train <- 200; n_total <- 300
t_all <- seq(0, (n_total - 1) * dt, by = dt)
x1 <- exp(-0.05 * t_all) * sin(2 * pi * 0.5 * t_all)
x2 <- exp(-0.05 * t_all) * cos(2 * pi * 0.5 * t_all)
X_train <- rbind(x1[1:n_train], x2[1:n_train])
# Fit DMD with rank 2
result <- dmd(X_train, rank = 2, dt = dt)
summary(result)
# Predict the full time range (including out-of-sample)
pred <- predict(result, n_ahead = n_total - 1)
DMD captures the damped oscillation dynamics perfectly. The dashed line extends beyond the training window (vertical line) and matches the true signal.
2. Eigenvalue Spectrum
Examining the eigenvalues in the complex plane reveals the stability and frequency content of the decomposition.
# Eigenvalues in the complex plane
spec <- dmd_spectrum(result)
print(spec)
# Stability analysis
stab <- dmd_stability(result)
cat("Spectral radius:", stab$spectral_radius, "\n")
cat("Stable:", stab$is_stable, "\n")
Both eigenvalues lie just inside the unit circle (|lambda|=0.9975), confirming the slight damping. The frequency 0.5 Hz matches the input signal.
3. Reconstruction Residuals
Reconstruction error quantifies how well DMD approximates the original data.
recon <- dmd_reconstruct(result)
err <- dmd_error(result)
cat("RMSE:", err$rmse, "\n")
cat("Relative error:", err$relative_error, "\n")
For a purely linear system with rank matching the true rank, DMD achieves near-zero reconstruction error.
4. Extended DMD with Polynomial Lifting
When the dynamics are nonlinear, polynomial lifting maps the data into a higher-dimensional space where DMD can better approximate the Koopman operator.
# Nonlinear signal: sin^2(t)
t3 <- seq(0, 9.95, by = 0.05)
X3 <- matrix(sin(t3)^2, nrow = 1)
# Standard DMD
res_std <- dmd(X3, dt = 0.05)
# Extended DMD with degree-2 polynomial lifting
res_lift <- dmd(X3, lifting = "polynomial", lifting_param = 2, dt = 0.05)
# Compare errors
cat("Standard RMSE:", dmd_error(res_std)$rmse, "\n")
cat("Lifted RMSE: ", dmd_error(res_lift)$rmse, "\n")
Polynomial lifting maps the nonlinear sin^2 signal into a space where DMD can better approximate the dynamics.
5. Hankel-DMD for Scalar Time Series
Hankel-DMD augments a scalar measurement with time-delay embeddings, enabling DMD to recover multiple frequencies from a single observable.
# Two-frequency scalar signal
dt4 <- 0.02; t4 <- seq(0, 9.98, by = dt4)
signal <- sin(2 * pi * 1.0 * t4) + 0.4 * sin(2 * pi * 3.0 * t4)
X4 <- matrix(signal, nrow = 1)
# Train on first 400 samples, predict last 100
hresult <- hankel_dmd(X4[, 1:400, drop = FALSE], delays = 30, rank = 4, dt = dt4)
# Reconstruct and predict
hrecon <- hankel_reconstruct(hresult, 400)
hpred <- predict(hresult, n_ahead = 100)
# Recovered frequencies
eig_freq <- abs(atan2(hresult$eigenvalues_im, hresult$eigenvalues_re)) / (2 * pi * dt4)
cat("Recovered frequencies:", round(eig_freq, 2), "Hz\n")
Hankel-DMD recovers the correct frequencies (1.0 Hz and 3.0 Hz) from a scalar measurement via time-delay embedding.
6. GLA Reconstruction and Prediction
Generalized Laplace Analysis (GLA) directly computes Koopman eigenfunctions from trajectory data using an iterative algorithm.
# Simple oscillator
t5 <- seq(0, 39.9, by = 0.1)
gla_data <- rbind(sin(t5), cos(t5))
# GLA with 2 eigenvalues
gresult <- gla(gla_data, n_eigenvalues = 2, tol = 1e-4)
grecon <- gla_reconstruct(gresult)
gpred <- predict(gresult, n_ahead = 100)
GLA directly computes Koopman eigenfunctions. For a pure oscillator, it achieves exact reconstruction and accurate extrapolation.
7. Dynamical Map Phase Portraits
The library includes several classic dynamical systems for generating trajectories and studying phase space structure.
# Standard map -- regular and chaotic
traj_reg <- generate_trajectory("standard", c(0.1, 0.2), 5000, epsilon = 0.12)
traj_chaos <- generate_trajectory("standard", c(0.5, 0.5), 50000, epsilon = 0.97)
# Henon attractor
traj_henon <- generate_trajectory("henon", c(0, 0), 20000, a = 1.4, b = 0.3)
# Logistic map
traj_log <- generate_trajectory("logistic", 0.4, 200, r = 3.9)
The library includes classic dynamical systems. The standard map transitions from regular islands to global chaos as epsilon increases. The Henon map shows a strange attractor, and the logistic map exhibits chaotic time evolution.
8. HTA Convergence
The harmonic time average (HTA) converges differently for regular and chaotic orbits, providing a diagnostic for orbit classification.
# Regular orbit converges to a non-zero value
conv_reg <- hta_convergence("standard", c(0.1, 0.2),
observable = "sin_pi", omega = 0.0,
n_iter = 50000, epsilon = 0.12)
# Chaotic orbit decays toward zero
conv_chaos <- hta_convergence("standard", c(0.5, 0.5),
observable = "sin_pi", omega = 0.0,
n_iter = 50000, epsilon = 0.97)
The HTA magnitude converges to a stable non-zero value for regular orbits (phase-locked to the frequency), while it decays for chaotic orbits (no persistent periodicity).
9. Mesochronic Harmonic Plot
The mesochronic plot computes the HTA over a grid of initial conditions, revealing the phase space structure of a dynamical system.
meso <- mesochronic_compute("standard",
x_range = c(0, 1), y_range = c(0, 1),
resolution = 100, observable = "sin_pi",
omega = 0.0, n_iter = 5000, epsilon = 0.12)
# Plot magnitude heatmap
image(meso$x_coords, meso$y_coords, meso$hta_matrix,
col = hcl.colors(256, "viridis"),
xlab = "x", ylab = "y", main = "|HTA| Magnitude")
The mesochronic plot reveals the phase space structure of the standard map. Bright regions correspond to regular islands where orbits resonate with the test frequency. Dark regions indicate chaotic motion.
10. Method Comparison
This example applies all three decomposition methods to the same multi-frequency signal and compares their out-of-sample prediction accuracy.
# Multi-frequency test signal
dt10 <- 0.01; t10 <- seq(0, 2.99, by = dt10)
signal <- sin(2 * pi * 2 * t10) + 0.3 * sin(2 * pi * 7 * t10)
X10 <- matrix(signal, nrow = 1)
# Train on 250 samples, predict 50
X10_train <- X10[, 1:250, drop = FALSE]
dmd_res <- dmd(X10_train, rank = 4, dt = dt10)
hdmd_res <- hankel_dmd(X10_train, delays = 30, rank = 4, dt = dt10)
gla_res <- gla(X10_train, n_eigenvalues = 4, tol = 1e-4)
# Compare prediction RMSE
dmd_pred <- predict(dmd_res, n_ahead = 50)
hdmd_pred <- predict(hdmd_res, n_ahead = 50)
gla_pred <- predict(gla_res, n_ahead = 50)
All three methods are compared on the same out-of-sample prediction task. Performance varies by signal characteristics -- standard DMD works well for multi-variable systems, Hankel-DMD excels at scalar frequency extraction, and GLA provides direct eigenfunction computation.