Skip to content

openmodalpy banner

CI PyPI Python License

openmodalpy puts nine modal decomposition methods for spatiotemporal data behind one API. Extract coherent structures from simulation or experimental data — energy-ranked POD modes, frequency-resolved SPOD modes, DMD eigenvalues, nonlinear BSMD triads — without switching libraries or rewriting your loading code for each method.

Why this package

Most Python tools here specialise: PyDMD covers DMD variants in depth, PySPOD covers SPOD. That depth is real, and if you only need one method they are excellent choices.

OpenModalPy trades some of that depth for breadth. Nine methods share one analyzer interface, one data contract and one config file, so running POD, SPOD, DMD and BSMD over the same dataset — and comparing them directly — is a single command rather than four integrations. Bispectral mode decomposition (BSMD) in particular has little open-source coverage elsewhere.

It runs on the NumPy/SciPy stack. No compiled solver toolchain (PETSc, SLEPc) to install.

Installation

uv add openmodalpy                 # library
uv tool install openmodalpy        # standalone CLI

Optional extras:

Extra Adds
openmodalpy[viz3d] 3D slice and isosurface plotting (PyVista)
openmodalpy[mkl] Intel MKL FFT backend
openmodalpy[gpu] CuPy / PyTorch FFT backends

Quick Start

from openmodalpy import PODAnalyzer, SPODAnalyzer, DMDAnalyzer

pod = PODAnalyzer(file_path="data.mat", n_modes_save=10)
pod.run_analysis()

spod = SPODAnalyzer(file_path="data.mat", nfft=256, overlap=0.5)
spod.run_analysis()

# DMD is driven in two steps, so the fit method can be chosen after loading.
dmd = DMDAnalyzer(file_path="data.mat", n_modes_save=10)
dmd.load_and_preprocess()
dmd.perform_dmd(method="ls")

Configuration-Driven Workflow

One JSONC file runs several methods over the same dataset — the main reason to reach for this package over a single-method library:

{
  "case": {
    "name": "my_case",
    "data": { "kind": "file", "path": "data.mat" },
    "n_modes_save": 10, "nfft": 128, "overlap": 0.5
  },
  "runs": [
    { "id": "pod",   "method": "pod" },
    { "id": "spod",  "method": "spod" },
    { "id": "dmd",   "method": "dmd",   "params": { "method": "ls" } },
    { "id": "hodmd", "method": "hodmd", "params": { "delays": 4 } },
    { "id": "bsmd",  "method": "bsmd" }
  ]
}
openmodalpy run --config analysis.jsonc

CLI

openmodalpy analyze pod --config case.jsonc     # one method
openmodalpy run --config suite.jsonc            # full suite
openmodalpy run --config suite.jsonc --dry-run  # preview without computing
openmodalpy methods list                        # supported methods
openmodalpy examples list                       # bundled examples
openmodalpy results inspect output.hdf5         # inspect a result file

Three example cases ship with the package and need no external data — double_gyre, cylinder_wake and taylor_green generate their fields analytically. A fourth config, run_benchmarks, runs all three as a suite. So openmodalpy examples list gives you something runnable immediately, with nothing to download.

Methods

These are the names openmodalpy methods list reports and the values the method field takes in a config file.

method Class What it extracts Reference
pod variance-optimal energy-ranked spatial modes Lumley (1967); Sirovich (1987)
mpod variance-optimal scale-separated modes across non-overlapping bands Mendez et al. (2019)
psd-pod variance-optimal POD of blockwise Fourier realizations
spod variance-optimal frequency-local modes (Welch blocks) Towne, Schmidt & Colonius (2018)
stpod variance-optimal space-time structures via delay embedding
dmd evolution-fit modes with frequency and growth rate Schmid (2010); Tu et al. (2014)
hodmd evolution-fit delay-embedded (Hankel) DMD Le Clainche & Vega (2017)
tls-hodmd evolution-fit delay-embedded DMD, total-least-squares fit Hemati et al. (2017)
bsmd triadic interaction nonlinear triad structures Schmidt (2020)

dmd accepts method: "ls" (least squares) or method: "tls" (total least squares, de-biased for noisy data).

The BSMD implementation follows Schmidt (2020) and was inspired by the reference MATLAB implementation.

Data Format

.mat and .npz files are auto-detected and must provide:

{
    "q": np.ndarray,   # (Ns, Nspace) — snapshots × spatial points
    "dt": float,       # time step
    "Nx": int,         # grid points in x
    "Ny": int,         # grid points in y
    "x": np.ndarray,   # x-coordinates
    "y": np.ndarray,   # y-coordinates
}

Anything else can be read with a custom loader returning the same dictionary:

def my_loader(path):
    return {"q": data, "dt": 0.01, "Nx": 100, "Ny": 50, "x": x, "y": y}

pod = PODAnalyzer(file_path="ignored", data_loader=my_loader)

FFT Backend

FFT dispatch comes from fftkit, installed automatically. It probes the available backends, picks the fastest, and falls back to SciPy when nothing else is present — so this section is optional reading.

To pin a backend:

export FFTKIT_BACKEND=mkl      # or scipy, numpy, cupy, accelerate
from openmodalpy.core.config import FFT_BACKEND
print(FFT_BACKEND)   # the backend actually in use

The legacy PYMODAL_FFT_BACKEND variable still works as a fallback, but FFTKIT_BACKEND is the supported name.

Contributing

Contributions are welcome, and questions and bug reports count. See CONTRIBUTING.md for setup and the checks CI runs, and the openfluids Code of Conduct for how we work together.

License

Apache-2.0. Originally developed by Ricardo A S Frantz — see LICENSE and NOTICE for terms and attribution.

Releases

Packages

Used by

Contributors

Languages