pyModal is a collection of zero-MPI, minimal-dependency scripts for uncovering coherent structures in complex data. It currently implements Proper Orthogonal Decomposition (POD), Spectral POD (SPOD), and the recently introduced Bispectral Mode Decomposition (BSMD), with full Space-Time POD (ST-POD) support on the roadmap. The focus is on clarity over boilerplate: every algorithm fits in a few hundred readable lines so you can study, tweak, or extend the maths without fighting a framework.
-
Why another modal toolbox? pyModal is the only open-source package to combine POD + SPOD and BSMD in a single, laptop-friendly codebase—something you won’t find in established libraries like PySPOD (SPOD only) , PyDMD (DMD variants), or modred (POD/BPOD/DMD).
-
Proper Orthogonal Decomposition (POD) Performs a weighted singular value decomposition of mean-subtracted snapshots to recover energy-ranked spatial modes and their temporal coefficients. Periodograms of the time coefficients are plotted in Hertz using the sampling interval
dt. Multiply the frequency axis byL/Uto express results in the dimensionless Strouhal number when physical scales are known. -
Spectral Proper Orthogonal Decomposition (SPOD) Solves the cross-spectral density eigenvalue problem to yield energy-ranked, harmonic spatial modes under the assumption of wide-sense stationarity. Reference: Towne, Schmidt & Colonius (2018)
-
Bispectral Mode Decomposition (BSMD) Extracts third-order phase-coupled spatial modes by diagonalizing an estimated bispectral density tensor, revealing the triadic interactions that drive nonlinear energy transfer. Reference: Nekkanti, Pickering, Schmidt & Colonius (2025) If FFT blocks are already present in
results_spod/, BSMD automatically reuses them (printing "Reusing cached FFT blocks...") and writes new output toresults_bsmd/. -
Dynamic Mode Decomposition (DMD) Recovers eigenvalues and spatial modes of the best-fit linear operator advancing the system in time. Useful for extracting growth rates and oscillatory structures. The implementation here mirrors the exact DMD algorithm from the
PyDMDproject. -
Space-Time Proper Orthogonal Decomposition (ST-POD) Generalizes POD to a full space–time framework by solving an eigenproblem of the space-time correlation tensor, capturing arbitrary nonstationary and transient dynamics over finite windows. Support for ST-POD is planned but not yet implemented. Reference: Yeung & Schmidt (2025)
- Pure-Python & portable – runs out-of-the-box on CPython ≥3.9; no MPI or GPU prerequisites.
- Unified CLI workflow – --prep, --compute, --plot stages for each method; caches FFT blocks automatically.
- Minimal install footprint – only numpy, scipy, matplotlib, h5py, tqdm.
- Flexible I/O – HDF5, NetCDF, MATLAB .mat, raw NumPy arrays.
- Built-in visualisation - Quick mode shapes, spectra, and bispectral-energy maps straight from the CLI.
git clone https://github.com/ricardofrantz/pyModal.git
cd pyModalInstall the required Python packages with:
pip install h5py matplotlib numpy scipy tqdmThese scripts were tested on Python 3.13 running on Ubuntu 24.04 and macOS.
On Apple silicon (M‑series) Macs we recommend installing Python via Miniforge and using the packages provided by conda-forge:
conda install numpy scipy matplotlibTo enable the optional 'accelerate' FFT backend install the PyObjC bindings:
pip install pyobjc-framework-AccelerateFor Intel workstations with the Intel compiler stack you can take advantage of Intel's optimized libraries:
conda install intel-openmp
conda install numpy[mkl]
conda install mkl_fft # optional direct MKL backendInstall the dependencies and run the unit tests with:
pytestFFT and matrix operations rely on NumPy's multithreaded BLAS libraries.
Only the underlying BLAS/LAPACK routines honor the OMP_NUM_THREADS
environment variable automatically. Other Python code typically runs
single-threaded.
Check which optimizations are active by running:
python -m parallel_utilsThe analysis scripts (pod.py, spod.py, and bmsd.py) can now be executed in
separate stages. Each accepts the flags --prep, --compute, and --plot to
run only the desired part of the workflow:
python pod.py --prep # preprocess input data
python pod.py --compute # perform the decomposition
python pod.py --plot # generate figures from resultsRunning a script with no flags executes all steps in sequence. The same options
apply to spod.py and bmsd.py.
This section summarizes how the repository is organized and the mathematics implemented. It is intended for contributors extending the code.
spod.py,bmsd.py,pod.pyanddmd.pyimplement Spectral POD, Bispectral Mode Decomposition, standard POD and Dynamic Mode Decomposition respectively.utils.pyprovides theBaseAnalyzerclass with common routines for loading data, computing FFT blocks viablocksfft, and saving results.configs.pycontains global settings such as output directories, plotting defaults and the FFT backend.- The
fft/folder houses backend-specific FFT helpers.
POD performs a weighted singular value decomposition of the mean-subtracted snapshots. Depending on the dimensions it solves either the temporal or spatial covariance problem and projects the data to obtain modes and time coefficients.
SPOD solves an eigenvalue problem for the cross–spectral density matrix. FFT blocks of the signal are computed with Welch's method (blocksfft). For each frequency bin f_i the weighted matrix
[M_i = X_i^H W X_i]
is diagonalized to obtain spatial modes and their energies.
BSMD analyzes triadic interactions. For a triad (p1,p2,p3) with f_{p1}+f_{p2}=f_{p3} it forms matrices A and B from cached FFT blocks and solves
[C = A^\dagger W B,\quad C a = \lambda a.]
The resulting eigenvectors reconstruct two coupled sets of spatial modes.
Intermediate FFT blocks (qhat) are stored in HDF5 files whose names are generated by make_result_filename. Both SPOD and BSMD check for an existing cache before recomputing, and BSMD can reuse SPOD caches. This greatly speeds up iterative analyses and ensures reproducibility.
FFT computation uses the backend specified in configs.py and automatically leverages the multithreaded BLAS library shipped with NumPy. The helper get_num_threads() reports the number of threads requested via OMP_NUM_THREADS, but only BLAS/LAPACK operations use these threads by default.
- New decompositions can be developed by subclassing
BaseAnalyzer. - Place cached data and figures in the respective
results_*andfigs_*directories. - Override any global option via a JSON or YAML file passed to
configs.load_config(). - For BSMD, adjust the
ALL_TRIADSlist or provide your own set of frequency triplets.
By documenting these design choices, new contributors should be able to navigate the project and implement future updates more easily.