Getting Started
Install koopman-dmd and run your first Dynamic Mode Decomposition in Rust, Python, or R.
Prerequisites
The core library is written in Rust. Language bindings for Python and R are optional and only required if you intend to call koopman-dmd from those languages.
| Requirement | Version | Notes |
|---|---|---|
rustc + cargo |
stable 1.70+ | Required. Install via rustup.rs. |
| Python | 3.8+ | Optional. Needed only for the Python bindings. |
| R | 4.0+ | Optional. Needed only for the R bindings. |
Rust Installation
You can depend on koopman-dmd as a Cargo crate or build directly from source.
Option A: Add to Cargo.toml
In your project's Cargo.toml, add the dependency:
# Cargo.toml
[dependencies]
koopman-dmd = { git = "https://github.com/jimeharrisjr/rust-dmd", branch = "main" }
Then build your project as usual:
cargo build --release
Option B: Clone the repository
git clone https://github.com/jimeharrisjr/rust-dmd.git
cd rust-dmd/koopman-dmd
cargo build --release
cargo test
Python Installation
The Python bindings use maturin to compile the Rust core into a native Python extension module.
# Install maturin (one-time setup)
pip install maturin
# Build and install the Python package in development mode
cd koopman-dmd-py
maturin develop --release
After this completes, you can import koopman_dmd from any Python script or notebook.
cargo and rustc are on your PATH before running maturin.
R Installation
The R package wraps the Rust core through compiled native code. Install it with R CMD INSTALL:
R CMD INSTALL koopman-dmd-r
This will invoke Cargo internally to compile the Rust library, then install the resulting R package. Both cargo and rustc must be available on your system PATH.
Once installed, load the package in R:
library(koopmandmd)
Quick Start
The following example creates a simple oscillating signal with two variables, computes a DMD decomposition, inspects the eigenvalues, and predicts ten steps into the future. Choose your language below.
use koopman_dmd::{DmdConfig, Dmd};
use ndarray::Array2;
use std::f64::consts::PI;
fn main() {
// 1. Create an oscillating 2-variable signal (n_vars x n_time)
let n_time = 100;
let dt = 0.05;
let mut data = Array2::zeros((2, n_time));
for j in 0..n_time {
let t = j as f64 * dt;
data[[0, j]] = (2.0 * PI * t).sin();
data[[1, j]] = (2.0 * PI * t).cos();
}
// 2. Compute DMD with default configuration
let config = DmdConfig::default();
let result = Dmd::fit(&data, &config).expect("DMD fit failed");
// 3. Examine eigenvalues
println!("Eigenvalues: {:?}", result.eigenvalues());
// 4. Predict 10 steps ahead
let prediction = result.predict(10);
println!("Prediction shape: {:?}", prediction.dim());
}
import numpy as np
import koopman_dmd
# 1. Create an oscillating 2-variable signal (n_vars x n_time)
n_time = 100
dt = 0.05
t = np.arange(n_time) * dt
data = np.array([
np.sin(2 * np.pi * t),
np.cos(2 * np.pi * t),
])
# 2. Compute DMD with default configuration
result = koopman_dmd.dmd(data)
# 3. Examine eigenvalues
print("Eigenvalues:", result.eigenvalues())
# 4. Predict 10 steps ahead
prediction = result.predict(10)
print("Prediction shape:", prediction.shape)
library(koopmandmd)
# 1. Create an oscillating 2-variable signal (n_vars x n_time)
n_time <- 100
dt <- 0.05
t <- seq(0, by = dt, length.out = n_time)
data <- rbind(
sin(2 * pi * t),
cos(2 * pi * t)
)
# 2. Compute DMD with default configuration
result <- dmd(data)
# 3. Examine eigenvalues
print(result$eigenvalues)
# 4. Predict 10 steps ahead
prediction <- predict(result, n_steps = 10)
print(dim(prediction))
Key Concepts
Data matrix layout
koopman-dmd expects input data as a matrix with shape n_vars x n_time, where each row is an observed variable and each column is a snapshot at a single point in time. For example, a system with 3 sensors sampled at 200 time steps should be passed as a 3-by-200 matrix.
Rank truncation
DMD internally performs a Singular Value Decomposition (SVD) of the data. In many practical applications the data is approximately low-rank, meaning that only a handful of singular values carry significant energy. Rank truncation discards the remaining singular values and their associated modes, which serves two purposes:
- Noise filtering -- small singular values often correspond to measurement noise rather than true dynamics.
- Computational efficiency -- working with a reduced rank lowers the cost of all subsequent linear algebra operations.
By default, koopman-dmd retains all singular values. You can set an
explicit rank via the configuration object (e.g.,
DmdConfig::default().rank(5) in Rust) or specify an energy
threshold to let the library choose the rank automatically.
Eigenvalues, modes, and amplitudes
A DMD decomposition produces three quantities that together describe the dynamics of the system:
| Quantity | Symbol | Interpretation |
|---|---|---|
| Eigenvalues | lambda | Complex scalars encoding the growth/decay rate and oscillation frequency of each dynamic component. Eigenvalues on the unit circle correspond to purely oscillatory behaviour; those inside the circle are decaying. |
| Modes | phi | Spatial patterns (complex vectors) that describe how each dynamic component is distributed across the observed variables. Each mode has the same dimension as a single snapshot. |
| Amplitudes | b | Complex coefficients that weight each mode's contribution to the reconstruction. They are determined by projecting the initial condition onto the set of modes. |
Together, these allow you to reconstruct the data or extrapolate forward in time:
where Phi is the matrix of modes, b is the
amplitude vector, and lambda^k raises each eigenvalue to the
power of the time index.