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.
Files changed (45) hide show
  1. openmodalpy-0.1.0/.gitignore +62 -0
  2. openmodalpy-0.1.0/CHANGELOG.md +36 -0
  3. openmodalpy-0.1.0/LICENSE +21 -0
  4. openmodalpy-0.1.0/PKG-INFO +219 -0
  5. openmodalpy-0.1.0/README.md +176 -0
  6. openmodalpy-0.1.0/pyproject.toml +132 -0
  7. openmodalpy-0.1.0/src/modalpy/__init__.py +41 -0
  8. openmodalpy-0.1.0/src/modalpy/__main__.py +6 -0
  9. openmodalpy-0.1.0/src/modalpy/bsmd.py +1148 -0
  10. openmodalpy-0.1.0/src/modalpy/cli.py +206 -0
  11. openmodalpy-0.1.0/src/modalpy/commands.py +1136 -0
  12. openmodalpy-0.1.0/src/modalpy/config_io.py +50 -0
  13. openmodalpy-0.1.0/src/modalpy/core/__init__.py +29 -0
  14. openmodalpy-0.1.0/src/modalpy/core/base.py +1164 -0
  15. openmodalpy-0.1.0/src/modalpy/core/config.py +118 -0
  16. openmodalpy-0.1.0/src/modalpy/core/indent.py +143 -0
  17. openmodalpy-0.1.0/src/modalpy/core/io.py +1079 -0
  18. openmodalpy-0.1.0/src/modalpy/core/parallel.py +366 -0
  19. openmodalpy-0.1.0/src/modalpy/dmd.py +983 -0
  20. openmodalpy-0.1.0/src/modalpy/example_data.py +161 -0
  21. openmodalpy-0.1.0/src/modalpy/examples/__init__.py +1 -0
  22. openmodalpy-0.1.0/src/modalpy/examples/cylinder_wake.jsonc +37 -0
  23. openmodalpy-0.1.0/src/modalpy/examples/double_gyre.jsonc +37 -0
  24. openmodalpy-0.1.0/src/modalpy/examples/run_benchmarks.jsonc +10 -0
  25. openmodalpy-0.1.0/src/modalpy/examples/taylor_green.jsonc +37 -0
  26. openmodalpy-0.1.0/src/modalpy/mpod.py +259 -0
  27. openmodalpy-0.1.0/src/modalpy/pod.py +1497 -0
  28. openmodalpy-0.1.0/src/modalpy/specs.py +97 -0
  29. openmodalpy-0.1.0/src/modalpy/spod.py +1125 -0
  30. openmodalpy-0.1.0/src/modalpy/stpod.py +920 -0
  31. openmodalpy-0.1.0/tests/__init__.py +0 -0
  32. openmodalpy-0.1.0/tests/test_all.py +727 -0
  33. openmodalpy-0.1.0/tests/test_analytical_benchmarks.py +694 -0
  34. openmodalpy-0.1.0/tests/test_blocksfft.py +11 -0
  35. openmodalpy-0.1.0/tests/test_bsmd_core.py +179 -0
  36. openmodalpy-0.1.0/tests/test_cli_commands.py +416 -0
  37. openmodalpy-0.1.0/tests/test_dmd.py +506 -0
  38. openmodalpy-0.1.0/tests/test_dnami_loader.py +109 -0
  39. openmodalpy-0.1.0/tests/test_mat_loader.py +27 -0
  40. openmodalpy-0.1.0/tests/test_mpod.py +127 -0
  41. openmodalpy-0.1.0/tests/test_pod.py +211 -0
  42. openmodalpy-0.1.0/tests/test_spod_function.py +26 -0
  43. openmodalpy-0.1.0/tests/test_spod_plot.py +125 -0
  44. openmodalpy-0.1.0/tests/test_stpod.py +275 -0
  45. 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
+ ]
@@ -0,0 +1,6 @@
1
+ """Support `python -m modalpy`."""
2
+
3
+ from modalpy.cli import main
4
+
5
+ if __name__ == "__main__":
6
+ main()