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.
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.
uv add openmodalpy # library
uv tool install openmodalpy # standalone CLIOptional extras:
| Extra | Adds |
|---|---|
openmodalpy[viz3d] |
3D slice and isosurface plotting (PyVista) |
openmodalpy[mkl] |
Intel MKL FFT backend |
openmodalpy[gpu] |
CuPy / PyTorch FFT backends |
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")One JSONC file runs several methods over the same dataset — the main reason to reach for this package over a single-method library:
openmodalpy run --config analysis.jsoncopenmodalpy 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 fileThree 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.
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.
.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 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, acceleratefrom openmodalpy.core.config import FFT_BACKEND
print(FFT_BACKEND) # the backend actually in useThe legacy PYMODAL_FFT_BACKEND variable still works as a fallback, but
FFTKIT_BACKEND is the supported name.
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.
Apache-2.0. Originally developed by Ricardo A S Frantz — see LICENSE and NOTICE for terms and attribution.

{ "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" } ] }