openmodalpy 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openmodalpy-0.1.0/.gitignore +62 -0
- openmodalpy-0.1.0/CHANGELOG.md +36 -0
- openmodalpy-0.1.0/LICENSE +21 -0
- openmodalpy-0.1.0/PKG-INFO +219 -0
- openmodalpy-0.1.0/README.md +176 -0
- openmodalpy-0.1.0/pyproject.toml +132 -0
- openmodalpy-0.1.0/src/modalpy/__init__.py +41 -0
- openmodalpy-0.1.0/src/modalpy/__main__.py +6 -0
- openmodalpy-0.1.0/src/modalpy/bsmd.py +1148 -0
- openmodalpy-0.1.0/src/modalpy/cli.py +206 -0
- openmodalpy-0.1.0/src/modalpy/commands.py +1136 -0
- openmodalpy-0.1.0/src/modalpy/config_io.py +50 -0
- openmodalpy-0.1.0/src/modalpy/core/__init__.py +29 -0
- openmodalpy-0.1.0/src/modalpy/core/base.py +1164 -0
- openmodalpy-0.1.0/src/modalpy/core/config.py +118 -0
- openmodalpy-0.1.0/src/modalpy/core/indent.py +143 -0
- openmodalpy-0.1.0/src/modalpy/core/io.py +1079 -0
- openmodalpy-0.1.0/src/modalpy/core/parallel.py +366 -0
- openmodalpy-0.1.0/src/modalpy/dmd.py +983 -0
- openmodalpy-0.1.0/src/modalpy/example_data.py +161 -0
- openmodalpy-0.1.0/src/modalpy/examples/__init__.py +1 -0
- openmodalpy-0.1.0/src/modalpy/examples/cylinder_wake.jsonc +37 -0
- openmodalpy-0.1.0/src/modalpy/examples/double_gyre.jsonc +37 -0
- openmodalpy-0.1.0/src/modalpy/examples/run_benchmarks.jsonc +10 -0
- openmodalpy-0.1.0/src/modalpy/examples/taylor_green.jsonc +37 -0
- openmodalpy-0.1.0/src/modalpy/mpod.py +259 -0
- openmodalpy-0.1.0/src/modalpy/pod.py +1497 -0
- openmodalpy-0.1.0/src/modalpy/specs.py +97 -0
- openmodalpy-0.1.0/src/modalpy/spod.py +1125 -0
- openmodalpy-0.1.0/src/modalpy/stpod.py +920 -0
- openmodalpy-0.1.0/tests/__init__.py +0 -0
- openmodalpy-0.1.0/tests/test_all.py +727 -0
- openmodalpy-0.1.0/tests/test_analytical_benchmarks.py +694 -0
- openmodalpy-0.1.0/tests/test_blocksfft.py +11 -0
- openmodalpy-0.1.0/tests/test_bsmd_core.py +179 -0
- openmodalpy-0.1.0/tests/test_cli_commands.py +416 -0
- openmodalpy-0.1.0/tests/test_dmd.py +506 -0
- openmodalpy-0.1.0/tests/test_dnami_loader.py +109 -0
- openmodalpy-0.1.0/tests/test_mat_loader.py +27 -0
- openmodalpy-0.1.0/tests/test_mpod.py +127 -0
- openmodalpy-0.1.0/tests/test_pod.py +211 -0
- openmodalpy-0.1.0/tests/test_spod_function.py +26 -0
- openmodalpy-0.1.0/tests/test_spod_plot.py +125 -0
- openmodalpy-0.1.0/tests/test_stpod.py +275 -0
- openmodalpy-0.1.0/tests/test_weights.py +55 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
**/__pycache__/
|
|
4
|
+
*.py[cod]
|
|
5
|
+
*$py.class
|
|
6
|
+
*.so
|
|
7
|
+
.Python
|
|
8
|
+
build/
|
|
9
|
+
dist/
|
|
10
|
+
*.egg-info/
|
|
11
|
+
.eggs/
|
|
12
|
+
*.egg
|
|
13
|
+
|
|
14
|
+
# Virtual environments
|
|
15
|
+
.venv/
|
|
16
|
+
venv/
|
|
17
|
+
ENV/
|
|
18
|
+
|
|
19
|
+
# IDE
|
|
20
|
+
.idea/
|
|
21
|
+
.vscode/
|
|
22
|
+
*.swp
|
|
23
|
+
*.swo
|
|
24
|
+
|
|
25
|
+
# Testing
|
|
26
|
+
.pytest_cache/
|
|
27
|
+
.coverage
|
|
28
|
+
htmlcov/
|
|
29
|
+
|
|
30
|
+
# Linting
|
|
31
|
+
.ruff_cache/
|
|
32
|
+
.mypy_cache/
|
|
33
|
+
|
|
34
|
+
# Output directories (generated by analysis)
|
|
35
|
+
results_*/
|
|
36
|
+
figs_*/
|
|
37
|
+
figures/
|
|
38
|
+
preprocess_*/
|
|
39
|
+
cache/
|
|
40
|
+
|
|
41
|
+
# Data files — not shipped in the public repo (kept in the private dev repo)
|
|
42
|
+
*.npz
|
|
43
|
+
*.mat
|
|
44
|
+
*.h5
|
|
45
|
+
*.hdf5
|
|
46
|
+
data/
|
|
47
|
+
|
|
48
|
+
# Paper / manuscript material — kept private
|
|
49
|
+
paper/
|
|
50
|
+
*.tex
|
|
51
|
+
*.bbl
|
|
52
|
+
*.aux
|
|
53
|
+
*.blg
|
|
54
|
+
*.fdb_latexmk
|
|
55
|
+
*.fls
|
|
56
|
+
*.synctex.gz
|
|
57
|
+
|
|
58
|
+
# Collaborator working directories — kept private
|
|
59
|
+
baptiste/
|
|
60
|
+
|
|
61
|
+
# macOS
|
|
62
|
+
.DS_Store
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here.
|
|
4
|
+
|
|
5
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.0] - 2026-07-24
|
|
11
|
+
|
|
12
|
+
First public release, distributed on PyPI as `openmodalpy` and imported as `modalpy`.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- Modal decomposition analyzers: POD, mPOD, PSD-POD, SPOD, ST-POD, DMD (LS/TLS),
|
|
16
|
+
HODMD (LS/TLS) and BSMD.
|
|
17
|
+
- Configuration-driven workflow: a single JSONC file runs several methods over one
|
|
18
|
+
dataset, via `modalpy run --config`.
|
|
19
|
+
- Command line interface: `analyze`, `run`, `methods`, `examples`, `results`.
|
|
20
|
+
- Data loaders for `.mat` and `.npz` inputs, plus support for user-supplied loaders.
|
|
21
|
+
- Bundled self-contained example configs backed by analytic generators
|
|
22
|
+
(`double_gyre`, `cylinder_wake`, `taylor_green`, `run_benchmarks`).
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
- FFT backend dispatch moved out of this package into
|
|
26
|
+
[`fftkit`](https://github.com/openfluids/fftkit), now a required dependency.
|
|
27
|
+
`modalpy.core.config.FFT_BACKEND` re-exports the backend fftkit resolves, so the
|
|
28
|
+
reported backend always matches the one actually used.
|
|
29
|
+
- The `mkl` and `gpu` extras now defer to `fftkit[mkl]` and `fftkit[gpu]`.
|
|
30
|
+
|
|
31
|
+
### Removed
|
|
32
|
+
- The bundled `modalpy.fft` subpackage. Import FFT helpers from `fftkit` instead:
|
|
33
|
+
`get_fft_func`, `periodogram_rfft`, `find_peaks` and related functions.
|
|
34
|
+
|
|
35
|
+
[Unreleased]: https://github.com/openfluids/openmodalpy/compare/v0.1.0...HEAD
|
|
36
|
+
[0.1.0]: https://github.com/openfluids/openmodalpy/releases/tag/v0.1.0
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 ricardo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: openmodalpy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: ModalPy: Python tools for POD, MPOD, DMD, SPOD, PSD-POD, BSMD, and ST-POD on spatiotemporal data
|
|
5
|
+
Project-URL: Homepage, https://github.com/openfluids/openmodalpy
|
|
6
|
+
Project-URL: Source, https://github.com/openfluids/openmodalpy
|
|
7
|
+
Project-URL: Tracker, https://github.com/openfluids/openmodalpy/issues
|
|
8
|
+
Project-URL: Documentation, https://github.com/openfluids/openmodalpy#readme
|
|
9
|
+
Project-URL: Changelog, https://github.com/openfluids/openmodalpy/blob/main/CHANGELOG.md
|
|
10
|
+
Author-email: Ricardo Frantz <rasfrantz@gmail.com>
|
|
11
|
+
Maintainer-email: Ricardo Frantz <rasfrantz@gmail.com>
|
|
12
|
+
License-Expression: MIT
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Keywords: BSMD,DMD,MPOD,POD,PSD-POD,SPOD,ST-POD,fluid dynamics,modal decomposition,spatiotemporal data
|
|
15
|
+
Classifier: Development Status :: 4 - Beta
|
|
16
|
+
Classifier: Intended Audience :: Education
|
|
17
|
+
Classifier: Intended Audience :: Science/Research
|
|
18
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
19
|
+
Classifier: Operating System :: OS Independent
|
|
20
|
+
Classifier: Programming Language :: Python :: 3
|
|
21
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
26
|
+
Classifier: Topic :: Scientific/Engineering
|
|
27
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
28
|
+
Requires-Python: >=3.11
|
|
29
|
+
Requires-Dist: fftkit>=0.1.0
|
|
30
|
+
Requires-Dist: h5py>=3.8
|
|
31
|
+
Requires-Dist: matplotlib>=3.7
|
|
32
|
+
Requires-Dist: numpy>=1.24
|
|
33
|
+
Requires-Dist: scipy>=1.10
|
|
34
|
+
Requires-Dist: threadpoolctl>=3.0
|
|
35
|
+
Requires-Dist: tqdm>=4.65
|
|
36
|
+
Provides-Extra: gpu
|
|
37
|
+
Requires-Dist: fftkit[gpu]; extra == 'gpu'
|
|
38
|
+
Provides-Extra: mkl
|
|
39
|
+
Requires-Dist: fftkit[mkl]; extra == 'mkl'
|
|
40
|
+
Provides-Extra: viz3d
|
|
41
|
+
Requires-Dist: pyvista>=0.46; extra == 'viz3d'
|
|
42
|
+
Description-Content-Type: text/markdown
|
|
43
|
+
|
|
44
|
+
# ModalPy
|
|
45
|
+
|
|
46
|
+
Modal decomposition of spatiotemporal data in Python.
|
|
47
|
+
Pure NumPy/SciPy — no external solver dependencies.
|
|
48
|
+
|
|
49
|
+
**Methods:** POD · mPOD · PSD-POD · SPOD · ST-POD · DMD (LS/TLS) · HODMD (LS/TLS) · BSMD
|
|
50
|
+
|
|
51
|
+
## Installation
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
uv add openmodalpy
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The installed package is imported as `modalpy`.
|
|
58
|
+
|
|
59
|
+
Or as a standalone CLI:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
uv tool install openmodalpy
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
For modern 3D slice/isosurface plotting:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
uv add "openmodalpy[viz3d]"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## FFT Backend
|
|
72
|
+
|
|
73
|
+
FFT dispatch is handled by [`fftkit`](https://github.com/openfluids/fftkit), installed
|
|
74
|
+
automatically. It probes the available backends and picks the fastest one, falling back
|
|
75
|
+
to SciPy when nothing else is present — so no configuration is needed.
|
|
76
|
+
|
|
77
|
+
To pin a backend explicitly:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
export FFTKIT_BACKEND=mkl # or scipy, numpy, cupy, accelerate
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
from modalpy.core.config import FFT_BACKEND
|
|
85
|
+
print(FFT_BACKEND) # the backend actually in use
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Accelerator support comes from the corresponding extra:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
uv add "openmodalpy[mkl]" # Intel MKL
|
|
92
|
+
uv add "openmodalpy[gpu]" # CuPy / PyTorch
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
> The legacy `PYMODAL_FFT_BACKEND` variable is still honoured as a fallback, but
|
|
96
|
+
> `FFTKIT_BACKEND` is the supported name.
|
|
97
|
+
|
|
98
|
+
## Quick Start
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from modalpy import PODAnalyzer, DMDAnalyzer, SPODAnalyzer
|
|
102
|
+
|
|
103
|
+
pod = PODAnalyzer(file_path="data.mat", n_modes_save=10)
|
|
104
|
+
pod.run_analysis()
|
|
105
|
+
|
|
106
|
+
dmd = DMDAnalyzer(file_path="data.mat", n_modes_save=10)
|
|
107
|
+
dmd.load_and_preprocess()
|
|
108
|
+
dmd.perform_dmd(method="ls")
|
|
109
|
+
|
|
110
|
+
spod = SPODAnalyzer(file_path="data.mat", nfft=256, overlap=0.5)
|
|
111
|
+
spod.run_analysis()
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Configuration-Driven Workflow
|
|
115
|
+
|
|
116
|
+
A single JSONC file runs multiple methods on one dataset:
|
|
117
|
+
|
|
118
|
+
```jsonc
|
|
119
|
+
{
|
|
120
|
+
"case": {
|
|
121
|
+
"name": "my_case",
|
|
122
|
+
"data": { "kind": "file", "path": "data.mat" },
|
|
123
|
+
"n_modes_save": 10, "nfft": 128, "overlap": 0.5
|
|
124
|
+
},
|
|
125
|
+
"runs": [
|
|
126
|
+
{ "id": "pod", "method": "pod" },
|
|
127
|
+
{ "id": "spod", "method": "spod" },
|
|
128
|
+
{ "id": "dmd", "method": "dmd", "params": { "method": "ls" } },
|
|
129
|
+
{ "id": "hodmd","method": "hodmd", "params": { "delays": 4 } },
|
|
130
|
+
{ "id": "bsmd", "method": "bsmd" }
|
|
131
|
+
]
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
modalpy run --config analysis.jsonc
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## CLI
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
modalpy analyze pod --config case.jsonc # one method
|
|
143
|
+
modalpy run --config suite.jsonc # full suite
|
|
144
|
+
modalpy run --config suite.jsonc --dry-run # preview
|
|
145
|
+
modalpy methods list # supported methods
|
|
146
|
+
modalpy examples list # bundled examples
|
|
147
|
+
modalpy results inspect output.hdf5 # inspect result
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Bundled Example Configs
|
|
151
|
+
|
|
152
|
+
The public package ships only self-contained generator-backed example configs:
|
|
153
|
+
|
|
154
|
+
- `double_gyre.jsonc`
|
|
155
|
+
- `cylinder_wake.jsonc`
|
|
156
|
+
- `taylor_green.jsonc`
|
|
157
|
+
- `run_benchmarks.jsonc`
|
|
158
|
+
|
|
159
|
+
These configs do not depend on repository-local benchmark datasets.
|
|
160
|
+
|
|
161
|
+
## Supported Methods
|
|
162
|
+
|
|
163
|
+
| Method | Class | What it extracts |
|
|
164
|
+
|--------|-------|-----------------|
|
|
165
|
+
| POD | variance-optimal | energy-ranked spatial modes |
|
|
166
|
+
| mPOD | variance-optimal | scale-separated modes |
|
|
167
|
+
| PSD-POD | variance-optimal | broadband spectral modes |
|
|
168
|
+
| SPOD | variance-optimal | frequency-local modes |
|
|
169
|
+
| ST-POD | variance-optimal | space-time structures (delay embedding) |
|
|
170
|
+
| DMD (LS) | evolution-fit | modes with frequency and growth rate |
|
|
171
|
+
| DMD (TLS) | evolution-fit | de-biased DMD for noisy data |
|
|
172
|
+
| HODMD | evolution-fit | delay-embedded DMD |
|
|
173
|
+
| TLS-HODMD | evolution-fit | de-biased delay-embedded DMD |
|
|
174
|
+
| BSMD | triadic interaction | nonlinear triad structures |
|
|
175
|
+
|
|
176
|
+
The BSMD implementation follows
|
|
177
|
+
[Schmidt (2020)](https://doi.org/10.1007/s11071-020-06037-z) and was
|
|
178
|
+
inspired by the reference
|
|
179
|
+
[MATLAB implementation](https://github.com/olivertschmidt/bmd).
|
|
180
|
+
|
|
181
|
+
## Data Format
|
|
182
|
+
|
|
183
|
+
ModalPy auto-detects `.mat` and `.npz` files:
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
{
|
|
187
|
+
"q": np.ndarray, # (Ns, Nspace) — snapshots × spatial points
|
|
188
|
+
"dt": float, # time step
|
|
189
|
+
"Nx": int, # grid points in x
|
|
190
|
+
"Ny": int, # grid points in y
|
|
191
|
+
"x": np.ndarray, # x-coordinates
|
|
192
|
+
"y": np.ndarray, # y-coordinates
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Custom loaders:
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
def my_loader(path):
|
|
200
|
+
return {"q": data, "dt": 0.01, "Nx": 100, "Ny": 50, "x": x, "y": y}
|
|
201
|
+
|
|
202
|
+
pod = PODAnalyzer(file_path="ignored", data_loader=my_loader)
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## References
|
|
206
|
+
|
|
207
|
+
| Method | Key reference |
|
|
208
|
+
|--------|--------------|
|
|
209
|
+
| POD | Lumley (1967); Sirovich (1987) |
|
|
210
|
+
| mPOD | [Mendez et al. (2019)](https://doi.org/10.1017/jfm.2019.212) |
|
|
211
|
+
| SPOD | [Towne, Schmidt & Colonius (2018)](https://doi.org/10.1017/jfm.2018.283) |
|
|
212
|
+
| DMD | [Schmid (2010)](https://doi.org/10.1017/S0022112010001217); [Tu et al. (2014)](https://doi.org/10.3934/jcd.2014.1.391) |
|
|
213
|
+
| TLS-DMD | [Hemati et al. (2017)](https://doi.org/10.1007/s00162-017-0432-2) |
|
|
214
|
+
| HODMD | [Le Clainche & Vega (2017)](https://doi.org/10.1137/15M1054924) |
|
|
215
|
+
| BSMD | [Schmidt (2020)](https://doi.org/10.1007/s11071-020-06037-z) |
|
|
216
|
+
|
|
217
|
+
## License
|
|
218
|
+
|
|
219
|
+
MIT — see [LICENSE](LICENSE)
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# ModalPy
|
|
2
|
+
|
|
3
|
+
Modal decomposition of spatiotemporal data in Python.
|
|
4
|
+
Pure NumPy/SciPy — no external solver dependencies.
|
|
5
|
+
|
|
6
|
+
**Methods:** POD · mPOD · PSD-POD · SPOD · ST-POD · DMD (LS/TLS) · HODMD (LS/TLS) · BSMD
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
uv add openmodalpy
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The installed package is imported as `modalpy`.
|
|
15
|
+
|
|
16
|
+
Or as a standalone CLI:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
uv tool install openmodalpy
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
For modern 3D slice/isosurface plotting:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
uv add "openmodalpy[viz3d]"
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## FFT Backend
|
|
29
|
+
|
|
30
|
+
FFT dispatch is handled by [`fftkit`](https://github.com/openfluids/fftkit), installed
|
|
31
|
+
automatically. It probes the available backends and picks the fastest one, falling back
|
|
32
|
+
to SciPy when nothing else is present — so no configuration is needed.
|
|
33
|
+
|
|
34
|
+
To pin a backend explicitly:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
export FFTKIT_BACKEND=mkl # or scipy, numpy, cupy, accelerate
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from modalpy.core.config import FFT_BACKEND
|
|
42
|
+
print(FFT_BACKEND) # the backend actually in use
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Accelerator support comes from the corresponding extra:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
uv add "openmodalpy[mkl]" # Intel MKL
|
|
49
|
+
uv add "openmodalpy[gpu]" # CuPy / PyTorch
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
> The legacy `PYMODAL_FFT_BACKEND` variable is still honoured as a fallback, but
|
|
53
|
+
> `FFTKIT_BACKEND` is the supported name.
|
|
54
|
+
|
|
55
|
+
## Quick Start
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
from modalpy import PODAnalyzer, DMDAnalyzer, SPODAnalyzer
|
|
59
|
+
|
|
60
|
+
pod = PODAnalyzer(file_path="data.mat", n_modes_save=10)
|
|
61
|
+
pod.run_analysis()
|
|
62
|
+
|
|
63
|
+
dmd = DMDAnalyzer(file_path="data.mat", n_modes_save=10)
|
|
64
|
+
dmd.load_and_preprocess()
|
|
65
|
+
dmd.perform_dmd(method="ls")
|
|
66
|
+
|
|
67
|
+
spod = SPODAnalyzer(file_path="data.mat", nfft=256, overlap=0.5)
|
|
68
|
+
spod.run_analysis()
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Configuration-Driven Workflow
|
|
72
|
+
|
|
73
|
+
A single JSONC file runs multiple methods on one dataset:
|
|
74
|
+
|
|
75
|
+
```jsonc
|
|
76
|
+
{
|
|
77
|
+
"case": {
|
|
78
|
+
"name": "my_case",
|
|
79
|
+
"data": { "kind": "file", "path": "data.mat" },
|
|
80
|
+
"n_modes_save": 10, "nfft": 128, "overlap": 0.5
|
|
81
|
+
},
|
|
82
|
+
"runs": [
|
|
83
|
+
{ "id": "pod", "method": "pod" },
|
|
84
|
+
{ "id": "spod", "method": "spod" },
|
|
85
|
+
{ "id": "dmd", "method": "dmd", "params": { "method": "ls" } },
|
|
86
|
+
{ "id": "hodmd","method": "hodmd", "params": { "delays": 4 } },
|
|
87
|
+
{ "id": "bsmd", "method": "bsmd" }
|
|
88
|
+
]
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
modalpy run --config analysis.jsonc
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## CLI
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
modalpy analyze pod --config case.jsonc # one method
|
|
100
|
+
modalpy run --config suite.jsonc # full suite
|
|
101
|
+
modalpy run --config suite.jsonc --dry-run # preview
|
|
102
|
+
modalpy methods list # supported methods
|
|
103
|
+
modalpy examples list # bundled examples
|
|
104
|
+
modalpy results inspect output.hdf5 # inspect result
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Bundled Example Configs
|
|
108
|
+
|
|
109
|
+
The public package ships only self-contained generator-backed example configs:
|
|
110
|
+
|
|
111
|
+
- `double_gyre.jsonc`
|
|
112
|
+
- `cylinder_wake.jsonc`
|
|
113
|
+
- `taylor_green.jsonc`
|
|
114
|
+
- `run_benchmarks.jsonc`
|
|
115
|
+
|
|
116
|
+
These configs do not depend on repository-local benchmark datasets.
|
|
117
|
+
|
|
118
|
+
## Supported Methods
|
|
119
|
+
|
|
120
|
+
| Method | Class | What it extracts |
|
|
121
|
+
|--------|-------|-----------------|
|
|
122
|
+
| POD | variance-optimal | energy-ranked spatial modes |
|
|
123
|
+
| mPOD | variance-optimal | scale-separated modes |
|
|
124
|
+
| PSD-POD | variance-optimal | broadband spectral modes |
|
|
125
|
+
| SPOD | variance-optimal | frequency-local modes |
|
|
126
|
+
| ST-POD | variance-optimal | space-time structures (delay embedding) |
|
|
127
|
+
| DMD (LS) | evolution-fit | modes with frequency and growth rate |
|
|
128
|
+
| DMD (TLS) | evolution-fit | de-biased DMD for noisy data |
|
|
129
|
+
| HODMD | evolution-fit | delay-embedded DMD |
|
|
130
|
+
| TLS-HODMD | evolution-fit | de-biased delay-embedded DMD |
|
|
131
|
+
| BSMD | triadic interaction | nonlinear triad structures |
|
|
132
|
+
|
|
133
|
+
The BSMD implementation follows
|
|
134
|
+
[Schmidt (2020)](https://doi.org/10.1007/s11071-020-06037-z) and was
|
|
135
|
+
inspired by the reference
|
|
136
|
+
[MATLAB implementation](https://github.com/olivertschmidt/bmd).
|
|
137
|
+
|
|
138
|
+
## Data Format
|
|
139
|
+
|
|
140
|
+
ModalPy auto-detects `.mat` and `.npz` files:
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
{
|
|
144
|
+
"q": np.ndarray, # (Ns, Nspace) — snapshots × spatial points
|
|
145
|
+
"dt": float, # time step
|
|
146
|
+
"Nx": int, # grid points in x
|
|
147
|
+
"Ny": int, # grid points in y
|
|
148
|
+
"x": np.ndarray, # x-coordinates
|
|
149
|
+
"y": np.ndarray, # y-coordinates
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Custom loaders:
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
def my_loader(path):
|
|
157
|
+
return {"q": data, "dt": 0.01, "Nx": 100, "Ny": 50, "x": x, "y": y}
|
|
158
|
+
|
|
159
|
+
pod = PODAnalyzer(file_path="ignored", data_loader=my_loader)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## References
|
|
163
|
+
|
|
164
|
+
| Method | Key reference |
|
|
165
|
+
|--------|--------------|
|
|
166
|
+
| POD | Lumley (1967); Sirovich (1987) |
|
|
167
|
+
| mPOD | [Mendez et al. (2019)](https://doi.org/10.1017/jfm.2019.212) |
|
|
168
|
+
| SPOD | [Towne, Schmidt & Colonius (2018)](https://doi.org/10.1017/jfm.2018.283) |
|
|
169
|
+
| DMD | [Schmid (2010)](https://doi.org/10.1017/S0022112010001217); [Tu et al. (2014)](https://doi.org/10.3934/jcd.2014.1.391) |
|
|
170
|
+
| TLS-DMD | [Hemati et al. (2017)](https://doi.org/10.1007/s00162-017-0432-2) |
|
|
171
|
+
| HODMD | [Le Clainche & Vega (2017)](https://doi.org/10.1137/15M1054924) |
|
|
172
|
+
| BSMD | [Schmidt (2020)](https://doi.org/10.1007/s11071-020-06037-z) |
|
|
173
|
+
|
|
174
|
+
## License
|
|
175
|
+
|
|
176
|
+
MIT — see [LICENSE](LICENSE)
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "openmodalpy"
|
|
7
|
+
# Single source of truth: __version__ in src/modalpy/__init__.py (see [tool.hatch.version]).
|
|
8
|
+
# Keeping it in one place means the release workflow's tag check and the built
|
|
9
|
+
# artifact can never disagree about the version.
|
|
10
|
+
dynamic = ["version"]
|
|
11
|
+
description = "ModalPy: Python tools for POD, MPOD, DMD, SPOD, PSD-POD, BSMD, and ST-POD on spatiotemporal data"
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
license = "MIT"
|
|
14
|
+
license-files = ["LICENSE"]
|
|
15
|
+
requires-python = ">=3.11"
|
|
16
|
+
authors = [
|
|
17
|
+
{ name = "Ricardo Frantz", email = "rasfrantz@gmail.com" },
|
|
18
|
+
]
|
|
19
|
+
maintainers = [
|
|
20
|
+
{ name = "Ricardo Frantz", email = "rasfrantz@gmail.com" },
|
|
21
|
+
]
|
|
22
|
+
keywords = [
|
|
23
|
+
"POD",
|
|
24
|
+
"MPOD",
|
|
25
|
+
"DMD",
|
|
26
|
+
"SPOD",
|
|
27
|
+
"PSD-POD",
|
|
28
|
+
"BSMD",
|
|
29
|
+
"ST-POD",
|
|
30
|
+
"modal decomposition",
|
|
31
|
+
"spatiotemporal data",
|
|
32
|
+
"fluid dynamics",
|
|
33
|
+
]
|
|
34
|
+
classifiers = [
|
|
35
|
+
"Development Status :: 4 - Beta",
|
|
36
|
+
"Intended Audience :: Science/Research",
|
|
37
|
+
"Intended Audience :: Education",
|
|
38
|
+
"License :: OSI Approved :: MIT License",
|
|
39
|
+
"Operating System :: OS Independent",
|
|
40
|
+
"Programming Language :: Python :: 3",
|
|
41
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
42
|
+
"Programming Language :: Python :: 3.11",
|
|
43
|
+
"Programming Language :: Python :: 3.12",
|
|
44
|
+
"Programming Language :: Python :: 3.13",
|
|
45
|
+
"Programming Language :: Python :: 3.14",
|
|
46
|
+
"Topic :: Scientific/Engineering",
|
|
47
|
+
"Topic :: Scientific/Engineering :: Physics",
|
|
48
|
+
]
|
|
49
|
+
dependencies = [
|
|
50
|
+
"numpy>=1.24",
|
|
51
|
+
"scipy>=1.10",
|
|
52
|
+
"matplotlib>=3.7",
|
|
53
|
+
"h5py>=3.8",
|
|
54
|
+
"tqdm>=4.65",
|
|
55
|
+
"threadpoolctl>=3.0",
|
|
56
|
+
"fftkit>=0.1.0",
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
[project.optional-dependencies]
|
|
60
|
+
# FFT accelerators are provided by fftkit, which owns backend dispatch.
|
|
61
|
+
mkl = ["fftkit[mkl]"]
|
|
62
|
+
gpu = ["fftkit[gpu]"]
|
|
63
|
+
viz3d = ["pyvista>=0.46"]
|
|
64
|
+
|
|
65
|
+
[project.scripts]
|
|
66
|
+
modalpy = "modalpy.cli:main"
|
|
67
|
+
|
|
68
|
+
[project.urls]
|
|
69
|
+
Homepage = "https://github.com/openfluids/openmodalpy"
|
|
70
|
+
Source = "https://github.com/openfluids/openmodalpy"
|
|
71
|
+
Tracker = "https://github.com/openfluids/openmodalpy/issues"
|
|
72
|
+
Documentation = "https://github.com/openfluids/openmodalpy#readme"
|
|
73
|
+
Changelog = "https://github.com/openfluids/openmodalpy/blob/main/CHANGELOG.md"
|
|
74
|
+
|
|
75
|
+
# Development dependencies as PEP 735 groups; the CI workflows call `uv run --group ...`.
|
|
76
|
+
[dependency-groups]
|
|
77
|
+
test = [
|
|
78
|
+
"pytest>=7.0",
|
|
79
|
+
]
|
|
80
|
+
lint = [
|
|
81
|
+
"ruff>=0.1",
|
|
82
|
+
]
|
|
83
|
+
dev = [
|
|
84
|
+
{ include-group = "test" },
|
|
85
|
+
{ include-group = "lint" },
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
[tool.hatch.version]
|
|
89
|
+
path = "src/modalpy/__init__.py"
|
|
90
|
+
|
|
91
|
+
[tool.hatch.build]
|
|
92
|
+
dev-mode-dirs = ["src"]
|
|
93
|
+
|
|
94
|
+
# Only the self-contained, generator-backed example configs ship. The excluded ones
|
|
95
|
+
# depend on benchmark datasets that live outside the repository.
|
|
96
|
+
[tool.hatch.build.targets.sdist]
|
|
97
|
+
include = [
|
|
98
|
+
"/src/modalpy",
|
|
99
|
+
"/tests",
|
|
100
|
+
"/README.md",
|
|
101
|
+
"/CHANGELOG.md",
|
|
102
|
+
"/LICENSE",
|
|
103
|
+
"/pyproject.toml",
|
|
104
|
+
]
|
|
105
|
+
exclude = [
|
|
106
|
+
"/src/modalpy/examples/cavity.jsonc",
|
|
107
|
+
"/src/modalpy/examples/cylinder.jsonc",
|
|
108
|
+
"/src/modalpy/examples/cylinder_wake_compressible.jsonc",
|
|
109
|
+
"/src/modalpy/examples/jet.jsonc",
|
|
110
|
+
"/src/modalpy/examples/jet_small.jsonc",
|
|
111
|
+
]
|
|
112
|
+
|
|
113
|
+
[tool.hatch.build.targets.wheel]
|
|
114
|
+
packages = ["src/modalpy"]
|
|
115
|
+
include = [
|
|
116
|
+
"src/modalpy/examples/cylinder_wake.jsonc",
|
|
117
|
+
"src/modalpy/examples/double_gyre.jsonc",
|
|
118
|
+
"src/modalpy/examples/run_benchmarks.jsonc",
|
|
119
|
+
"src/modalpy/examples/taylor_green.jsonc",
|
|
120
|
+
]
|
|
121
|
+
|
|
122
|
+
[tool.ruff]
|
|
123
|
+
line-length = 120
|
|
124
|
+
target-version = "py311"
|
|
125
|
+
|
|
126
|
+
[tool.ruff.lint]
|
|
127
|
+
select = ["E", "F", "I", "W"]
|
|
128
|
+
ignore = ["E501"]
|
|
129
|
+
|
|
130
|
+
[tool.pytest.ini_options]
|
|
131
|
+
testpaths = ["tests"]
|
|
132
|
+
python_files = ["test_*.py"]
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""ModalPy public package."""
|
|
2
|
+
|
|
3
|
+
from modalpy.bsmd import BSMDAnalyzer
|
|
4
|
+
from modalpy.commands import (
|
|
5
|
+
analyze_from_config,
|
|
6
|
+
analyze_from_spec,
|
|
7
|
+
discover_examples,
|
|
8
|
+
get_method_spec,
|
|
9
|
+
inspect_results,
|
|
10
|
+
list_methods,
|
|
11
|
+
load_case_spec,
|
|
12
|
+
run_from_config,
|
|
13
|
+
)
|
|
14
|
+
from modalpy.dmd import DMDAnalyzer
|
|
15
|
+
from modalpy.mpod import MPODAnalyzer
|
|
16
|
+
from modalpy.pod import PODAnalyzer
|
|
17
|
+
from modalpy.specs import AnalyzeSpec, CaseSpec, DataSourceSpec, RunOutcome
|
|
18
|
+
from modalpy.spod import SPODAnalyzer
|
|
19
|
+
from modalpy.stpod import STPODAnalyzer
|
|
20
|
+
|
|
21
|
+
__version__ = "0.1.0"
|
|
22
|
+
__all__ = [
|
|
23
|
+
"PODAnalyzer",
|
|
24
|
+
"MPODAnalyzer",
|
|
25
|
+
"DMDAnalyzer",
|
|
26
|
+
"SPODAnalyzer",
|
|
27
|
+
"BSMDAnalyzer",
|
|
28
|
+
"STPODAnalyzer",
|
|
29
|
+
"AnalyzeSpec",
|
|
30
|
+
"CaseSpec",
|
|
31
|
+
"DataSourceSpec",
|
|
32
|
+
"RunOutcome",
|
|
33
|
+
"analyze_from_spec",
|
|
34
|
+
"analyze_from_config",
|
|
35
|
+
"run_from_config",
|
|
36
|
+
"discover_examples",
|
|
37
|
+
"list_methods",
|
|
38
|
+
"get_method_spec",
|
|
39
|
+
"inspect_results",
|
|
40
|
+
"load_case_spec",
|
|
41
|
+
]
|