GitHub

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.
Tip: If you only need Rust, you can skip the Python and R sections entirely. Each language binding is an independent, self-contained wrapper around the same core engine.

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.

Note: The build requires a working Rust toolchain. Make sure 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.

X = [ x(t_1)   x(t_2)   ...   x(t_m) ]    where each x(t_k) is an n-dimensional column

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:

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:

x(t_k) = Phi * diag(b) * lambda^k

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.