pyfuga 0.2.0__tar.gz → 0.4.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.
- {pyfuga-0.2.0 → pyfuga-0.4.0}/PKG-INFO +20 -6
- {pyfuga-0.2.0 → pyfuga-0.4.0}/README.md +16 -5
- {pyfuga-0.2.0 → pyfuga-0.4.0}/pyfuga/__init__.py +7 -6
- {pyfuga-0.2.0 → pyfuga-0.4.0}/pyfuga/_fuga.py +92 -53
- {pyfuga-0.2.0 → pyfuga-0.4.0}/pyfuga/common.py +12 -6
- pyfuga-0.4.0/pyfuga/constants.py +44 -0
- {pyfuga-0.2.0 → pyfuga-0.4.0}/pyfuga/file_readers.py +29 -20
- {pyfuga-0.2.0 → pyfuga-0.4.0}/pyfuga/flut.py +219 -143
- {pyfuga-0.2.0 → pyfuga-0.4.0}/pyfuga/paths.py +37 -4
- {pyfuga-0.2.0 → pyfuga-0.4.0}/pyfuga/preludium_eq_routines.py +9 -6
- {pyfuga-0.2.0 → pyfuga-0.4.0}/pyfuga/preluts.py +77 -42
- {pyfuga-0.2.0 → pyfuga-0.4.0}/pyfuga/preluts_generator/generator.py +23 -5
- {pyfuga-0.2.0 → pyfuga-0.4.0}/pyfuga/preluts_generator/integration.py +22 -4
- {pyfuga-0.2.0 → pyfuga-0.4.0}/pyfuga/preluts_generator/matrices.py +40 -40
- {pyfuga-0.2.0 → pyfuga-0.4.0}/pyfuga/preluts_generator/nodes.py +5 -5
- pyfuga-0.4.0/pyfuga/profiling.py +109 -0
- {pyfuga-0.2.0 → pyfuga-0.4.0}/pyfuga/trafalgar.py +35 -21
- pyfuga-0.4.0/pyfuga/utils.py +420 -0
- {pyfuga-0.2.0 → pyfuga-0.4.0}/pyfuga/z0.py +11 -10
- {pyfuga-0.2.0 → pyfuga-0.4.0}/pyfuga.egg-info/PKG-INFO +20 -6
- {pyfuga-0.2.0 → pyfuga-0.4.0}/pyfuga.egg-info/SOURCES.txt +3 -0
- {pyfuga-0.2.0 → pyfuga-0.4.0}/pyfuga.egg-info/requires.txt +3 -0
- {pyfuga-0.2.0 → pyfuga-0.4.0}/pyproject.toml +108 -8
- pyfuga-0.4.0/tests/test_api.py +500 -0
- pyfuga-0.4.0/tests/test_common.py +6 -0
- {pyfuga-0.2.0 → pyfuga-0.4.0}/tests/test_flut.py +136 -16
- pyfuga-0.4.0/tests/test_lut_schema.py +120 -0
- {pyfuga-0.2.0 → pyfuga-0.4.0}/tests/test_preluts_jit.py +10 -6
- pyfuga-0.4.0/tests/test_profiling.py +12 -0
- pyfuga-0.4.0/tests/test_trafalgar.py +110 -0
- pyfuga-0.4.0/tests/test_utils.py +105 -0
- pyfuga-0.2.0/pyfuga/constants.py +0 -33
- pyfuga-0.2.0/pyfuga/profiling.py +0 -86
- pyfuga-0.2.0/pyfuga/utils.py +0 -228
- pyfuga-0.2.0/tests/test_api.py +0 -145
- pyfuga-0.2.0/tests/test_trafalgar.py +0 -55
- pyfuga-0.2.0/tests/test_utils.py +0 -19
- {pyfuga-0.2.0 → pyfuga-0.4.0}/LICENSE +0 -0
- {pyfuga-0.2.0 → pyfuga-0.4.0}/pyfuga/preluts_generator/__init__.py +0 -0
- {pyfuga-0.2.0 → pyfuga-0.4.0}/pyfuga/preluts_generator/transforms.py +0 -0
- {pyfuga-0.2.0 → pyfuga-0.4.0}/pyfuga/typing.py +0 -0
- {pyfuga-0.2.0 → pyfuga-0.4.0}/pyfuga.egg-info/dependency_links.txt +0 -0
- {pyfuga-0.2.0 → pyfuga-0.4.0}/pyfuga.egg-info/top_level.txt +0 -0
- {pyfuga-0.2.0 → pyfuga-0.4.0}/setup.cfg +0 -0
- {pyfuga-0.2.0 → pyfuga-0.4.0}/tests/test_jit_signatures.py +0 -0
- {pyfuga-0.2.0 → pyfuga-0.4.0}/tests/test_paths.py +0 -0
- {pyfuga-0.2.0 → pyfuga-0.4.0}/tests/test_preluts.py +0 -0
- {pyfuga-0.2.0 → pyfuga-0.4.0}/tests/test_readers.py +0 -0
- {pyfuga-0.2.0 → pyfuga-0.4.0}/tests/test_typing.py +0 -0
- {pyfuga-0.2.0 → pyfuga-0.4.0}/tests/test_z0.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyfuga
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Linearised CFD wake model (PyFuga)
|
|
5
5
|
Author-email: PyFuga team <lalc@dtu.dk>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -43,6 +43,9 @@ Provides-Extra: lint
|
|
|
43
43
|
Requires-Dist: ruff>0.14; extra == "lint"
|
|
44
44
|
Requires-Dist: black[jupyter]>=26.1.0; extra == "lint"
|
|
45
45
|
Requires-Dist: mypy>=1.19.1; extra == "lint"
|
|
46
|
+
Requires-Dist: packaging>=24; extra == "lint"
|
|
47
|
+
Requires-Dist: scipy-stubs<1.18,>=1.10; extra == "lint"
|
|
48
|
+
Requires-Dist: types-tqdm>=4.0; extra == "lint"
|
|
46
49
|
Provides-Extra: docs
|
|
47
50
|
Requires-Dist: sphinx==7.4.7; extra == "docs"
|
|
48
51
|
Requires-Dist: sphinx-autobuild==2024.10.3; extra == "docs"
|
|
@@ -69,9 +72,8 @@ PyFuga is part of the [**PyWake**](https://gitlab.windenergy.dtu.dk/TOPFARM/PyWa
|
|
|
69
72
|
|
|
70
73
|
[](https://gitlab.windenergy.dtu.dk/TOPFARM/PyFuga/-/commits/main)
|
|
71
74
|
[](https://gitlab.windenergy.dtu.dk/TOPFARM/PyFuga/-/commits/main)
|
|
72
|
-
[](https://topfarm.pages.windenergy.dtu.dk/PyFuga
|
|
73
|
-
|
|
74
|
-
[](https://zenodo.org/badge/latestdoi/000000000)
|
|
75
|
+
[](https://topfarm.pages.windenergy.dtu.dk/PyFuga)
|
|
76
|
+
[](https://doi.org/10.5281/zenodo.19569518)
|
|
75
77
|
|
|
76
78
|
## Installation
|
|
77
79
|
|
|
@@ -97,7 +99,7 @@ A minimal example to get you started is available in **[the QuickStart Jupyter n
|
|
|
97
99
|
|
|
98
100
|
## Documentation
|
|
99
101
|
|
|
100
|
-
Learn more about PyFuga at the **[official documentation](https://topfarm.pages.windenergy.dtu.dk/PyFuga
|
|
102
|
+
Learn more about PyFuga at the **[official documentation](https://topfarm.pages.windenergy.dtu.dk/PyFuga)**.
|
|
101
103
|
|
|
102
104
|
## Release history
|
|
103
105
|
|
|
@@ -113,7 +115,19 @@ Issues and feature requests can be submitted through the project's [**GitLab iss
|
|
|
113
115
|
|
|
114
116
|
## Citing PyFuga
|
|
115
117
|
|
|
116
|
-
Please cite both the underlying Fuga theory and the PyFuga software implementation.
|
|
118
|
+
Please cite both the underlying Fuga theory and the PyFuga software implementation.
|
|
119
|
+
|
|
120
|
+
For PyFuga itself, **use the DOI on the Zenodo record for the release version you used** (this is the canonical source of truth). The repository `CITATION.cff` mirrors this metadata for convenience.
|
|
121
|
+
|
|
122
|
+
The badge above points to the latest release DOI. For scientific citation, always use the DOI for the exact release version used in your work.
|
|
123
|
+
|
|
124
|
+
If you have PyFuga installed, you can print a Zenodo search link for your installed version:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
python scripts/citation.py
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
See the "Citing PyFuga" section in the documentation for recommended references and BibTeX entries.
|
|
117
131
|
|
|
118
132
|
## Authors and acknowledgements
|
|
119
133
|
|
|
@@ -8,9 +8,8 @@ PyFuga is part of the [**PyWake**](https://gitlab.windenergy.dtu.dk/TOPFARM/PyWa
|
|
|
8
8
|
|
|
9
9
|
[](https://gitlab.windenergy.dtu.dk/TOPFARM/PyFuga/-/commits/main)
|
|
10
10
|
[](https://gitlab.windenergy.dtu.dk/TOPFARM/PyFuga/-/commits/main)
|
|
11
|
-
[](https://topfarm.pages.windenergy.dtu.dk/PyFuga
|
|
12
|
-
|
|
13
|
-
[](https://zenodo.org/badge/latestdoi/000000000)
|
|
11
|
+
[](https://topfarm.pages.windenergy.dtu.dk/PyFuga)
|
|
12
|
+
[](https://doi.org/10.5281/zenodo.19569518)
|
|
14
13
|
|
|
15
14
|
## Installation
|
|
16
15
|
|
|
@@ -36,7 +35,7 @@ A minimal example to get you started is available in **[the QuickStart Jupyter n
|
|
|
36
35
|
|
|
37
36
|
## Documentation
|
|
38
37
|
|
|
39
|
-
Learn more about PyFuga at the **[official documentation](https://topfarm.pages.windenergy.dtu.dk/PyFuga
|
|
38
|
+
Learn more about PyFuga at the **[official documentation](https://topfarm.pages.windenergy.dtu.dk/PyFuga)**.
|
|
40
39
|
|
|
41
40
|
## Release history
|
|
42
41
|
|
|
@@ -52,7 +51,19 @@ Issues and feature requests can be submitted through the project's [**GitLab iss
|
|
|
52
51
|
|
|
53
52
|
## Citing PyFuga
|
|
54
53
|
|
|
55
|
-
Please cite both the underlying Fuga theory and the PyFuga software implementation.
|
|
54
|
+
Please cite both the underlying Fuga theory and the PyFuga software implementation.
|
|
55
|
+
|
|
56
|
+
For PyFuga itself, **use the DOI on the Zenodo record for the release version you used** (this is the canonical source of truth). The repository `CITATION.cff` mirrors this metadata for convenience.
|
|
57
|
+
|
|
58
|
+
The badge above points to the latest release DOI. For scientific citation, always use the DOI for the exact release version used in your work.
|
|
59
|
+
|
|
60
|
+
If you have PyFuga installed, you can print a Zenodo search link for your installed version:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
python scripts/citation.py
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
See the "Citing PyFuga" section in the documentation for recommended references and BibTeX entries.
|
|
56
67
|
|
|
57
68
|
## Authors and acknowledgements
|
|
58
69
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import functools
|
|
2
2
|
import inspect
|
|
3
3
|
from importlib.metadata import PackageNotFoundError, version
|
|
4
|
+
from typing import Any
|
|
4
5
|
|
|
5
6
|
from . import _fuga
|
|
6
7
|
|
|
@@ -13,20 +14,20 @@ __all__ = ["get_luts", "get_fluts", "get_preluts", "__version__"]
|
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
@functools.wraps(_fuga.get_luts)
|
|
16
|
-
def get_luts(*args, **kwargs):
|
|
17
|
+
def get_luts(*args: Any, **kwargs: Any) -> Any:
|
|
17
18
|
return _fuga.get_luts(*args, **kwargs)
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
@functools.wraps(_fuga.get_fluts)
|
|
21
|
-
def get_fluts(*args, **kwargs):
|
|
22
|
+
def get_fluts(*args: Any, **kwargs: Any) -> Any:
|
|
22
23
|
return _fuga.get_fluts(*args, **kwargs)
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
@functools.wraps(_fuga.get_preluts)
|
|
26
|
-
def get_preluts(*args, **kwargs):
|
|
27
|
+
def get_preluts(*args: Any, **kwargs: Any) -> Any:
|
|
27
28
|
return _fuga.get_preluts(*args, **kwargs)
|
|
28
29
|
|
|
29
30
|
|
|
30
|
-
get_luts.__signature__ = inspect.signature(_fuga.get_luts)
|
|
31
|
-
get_fluts.__signature__ = inspect.signature(_fuga.get_fluts)
|
|
32
|
-
get_preluts.__signature__ = inspect.signature(_fuga.get_preluts)
|
|
31
|
+
get_luts.__signature__ = inspect.signature(_fuga.get_luts) # type: ignore[attr-defined]
|
|
32
|
+
get_fluts.__signature__ = inspect.signature(_fuga.get_fluts) # type: ignore[attr-defined]
|
|
33
|
+
get_preluts.__signature__ = inspect.signature(_fuga.get_preluts) # type: ignore[attr-defined]
|
|
@@ -27,28 +27,29 @@ from pyfuga.flut import FourierLUTGenerator
|
|
|
27
27
|
from pyfuga.paths import get_fluts_path, get_level_range, get_luts_path, get_preluts_path
|
|
28
28
|
from pyfuga.preluts import PreLUTs
|
|
29
29
|
from pyfuga.trafalgar import Trafalgar
|
|
30
|
-
from pyfuga.utils import ComplexXRDataset, get_beta_lst, get_kz0_lst
|
|
30
|
+
from pyfuga.utils import ComplexXRDataset, NCpuStageConfig, get_beta_lst, get_kz0_lst
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
def get_luts(
|
|
34
|
-
folder,
|
|
35
|
-
zeta0,
|
|
36
|
-
nkz0,
|
|
37
|
-
nbeta,
|
|
38
|
-
diameter,
|
|
39
|
-
zhub,
|
|
40
|
-
z0,
|
|
41
|
-
zi,
|
|
42
|
-
zlow,
|
|
43
|
-
zhigh,
|
|
44
|
-
lut_vars=UVW_LT,
|
|
45
|
-
nx=2048,
|
|
46
|
-
ny=512,
|
|
47
|
-
dx=None,
|
|
48
|
-
dy=None,
|
|
49
|
-
jit=True,
|
|
50
|
-
n_cpu=
|
|
51
|
-
|
|
34
|
+
folder: str | Path,
|
|
35
|
+
zeta0: float,
|
|
36
|
+
nkz0: int,
|
|
37
|
+
nbeta: int,
|
|
38
|
+
diameter: float,
|
|
39
|
+
zhub: float,
|
|
40
|
+
z0: float,
|
|
41
|
+
zi: float,
|
|
42
|
+
zlow: float,
|
|
43
|
+
zhigh: float,
|
|
44
|
+
lut_vars: list[str] = UVW_LT,
|
|
45
|
+
nx: int = 2048,
|
|
46
|
+
ny: int = 512,
|
|
47
|
+
dx: float | None = None,
|
|
48
|
+
dy: float | None = None,
|
|
49
|
+
jit: bool = True,
|
|
50
|
+
n_cpu: int | None = None,
|
|
51
|
+
n_cpu_stage: NCpuStageConfig | None = None,
|
|
52
|
+
) -> xr.Dataset: # pragma: no cover
|
|
52
53
|
"""Generate physical-space wake look-up tables via the full PyFuga pipeline.
|
|
53
54
|
|
|
54
55
|
This function orchestrates the three-stage PyFuga pipeline:
|
|
@@ -97,7 +98,17 @@ def get_luts(
|
|
|
97
98
|
jit : bool, optional
|
|
98
99
|
Enable JIT compilation (Numba). Default is True.
|
|
99
100
|
n_cpu : int or None, optional
|
|
100
|
-
|
|
101
|
+
Optional global worker-count override. If ``None`` (default), stage-aware
|
|
102
|
+
defaults are used and all stages run on all CPUs. If an integer is provided, that
|
|
103
|
+
worker count is applied to all stages unless overridden by
|
|
104
|
+
``n_cpu_stage``. Large ``nx``/``ny`` and multiple workers can increase
|
|
105
|
+
memory use substantially.
|
|
106
|
+
n_cpu_stage : dict, optional
|
|
107
|
+
Optional per-stage overrides for worker counts. Supported keys are
|
|
108
|
+
``"preluts"``, ``"fluts"``, and ``"trafalgar"``. Provided keys override
|
|
109
|
+
the resolved default/global setting for that stage. Precedence is:
|
|
110
|
+
defaults -> global ``n_cpu`` (if provided) -> ``n_cpu_stage`` keys.
|
|
111
|
+
A stage value may be ``None`` meaning all CPUs for that stage only.
|
|
101
112
|
|
|
102
113
|
Returns
|
|
103
114
|
-------
|
|
@@ -112,73 +123,100 @@ def get_luts(
|
|
|
112
123
|
"""
|
|
113
124
|
folder = Path(folder)
|
|
114
125
|
folder.mkdir(parents=True, exist_ok=True)
|
|
126
|
+
resolved_n_cpu = utils.resolve_n_cpu_stage(n_cpu, n_cpu_stage)
|
|
115
127
|
dx = dx or diameter / 4
|
|
116
128
|
dy = dy or diameter / 16
|
|
117
129
|
|
|
118
130
|
luts_path = get_luts_path(folder, zeta0, nkz0, nbeta, diameter, zhub, z0, zi, zlow, zhigh, lut_vars, nx, ny, dx, dy)
|
|
119
131
|
if luts_path.exists():
|
|
120
132
|
luts = xr.load_dataset(luts_path)
|
|
121
|
-
luts
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
133
|
+
if utils.load_lut_if_current(luts, luts_path):
|
|
134
|
+
luts.attrs["name"] = luts_path.stem
|
|
135
|
+
return luts
|
|
136
|
+
|
|
137
|
+
fluts = get_fluts(
|
|
138
|
+
folder,
|
|
139
|
+
zeta0,
|
|
140
|
+
nkz0,
|
|
141
|
+
nbeta,
|
|
142
|
+
diameter,
|
|
143
|
+
zhub,
|
|
144
|
+
z0,
|
|
145
|
+
zi,
|
|
146
|
+
zlow,
|
|
147
|
+
zhigh,
|
|
148
|
+
lut_vars,
|
|
149
|
+
jit,
|
|
150
|
+
resolved_n_cpu["fluts"],
|
|
151
|
+
n_cpu_stage={"preluts": resolved_n_cpu["preluts"]},
|
|
152
|
+
)
|
|
153
|
+
luts = Trafalgar(fluts, nx, ny, dx, dy).make_luts(interpolation_order=3, n_cpu=resolved_n_cpu["trafalgar"])
|
|
154
|
+
utils.stamp_lut_metadata(luts)
|
|
126
155
|
luts.to_netcdf(luts_path)
|
|
127
156
|
luts.attrs["name"] = luts_path.stem
|
|
128
157
|
return luts
|
|
129
158
|
|
|
130
159
|
|
|
131
160
|
def get_fluts(
|
|
132
|
-
folder,
|
|
133
|
-
zeta0,
|
|
134
|
-
nkz0,
|
|
135
|
-
nbeta,
|
|
136
|
-
diameter,
|
|
137
|
-
zhub,
|
|
138
|
-
z0,
|
|
139
|
-
zi,
|
|
140
|
-
zlow,
|
|
141
|
-
zhigh,
|
|
142
|
-
lut_vars=UVW_LT,
|
|
143
|
-
jit=True,
|
|
144
|
-
n_cpu=
|
|
145
|
-
|
|
161
|
+
folder: str | Path,
|
|
162
|
+
zeta0: float,
|
|
163
|
+
nkz0: int,
|
|
164
|
+
nbeta: int,
|
|
165
|
+
diameter: float,
|
|
166
|
+
zhub: float,
|
|
167
|
+
z0: float,
|
|
168
|
+
zi: float,
|
|
169
|
+
zlow: float,
|
|
170
|
+
zhigh: float,
|
|
171
|
+
lut_vars: list[str] = UVW_LT,
|
|
172
|
+
jit: bool = True,
|
|
173
|
+
n_cpu: int | None = None,
|
|
174
|
+
n_cpu_stage: NCpuStageConfig | None = None,
|
|
175
|
+
) -> ComplexXRDataset: # pragma: no cover
|
|
146
176
|
folder = Path(folder)
|
|
147
177
|
folder.mkdir(parents=True, exist_ok=True)
|
|
178
|
+
resolved_n_cpu = utils.resolve_n_cpu_stage(n_cpu, n_cpu_stage)
|
|
148
179
|
|
|
149
180
|
fluts_path = get_fluts_path(folder, zeta0, nkz0, nbeta, diameter, zhub, z0, zi, zlow, zhigh, lut_vars)
|
|
150
181
|
if fluts_path.exists():
|
|
151
|
-
|
|
182
|
+
fluts = ComplexXRDataset.from_netcdf(fluts_path)
|
|
183
|
+
if utils.load_lut_if_current(fluts, fluts_path):
|
|
184
|
+
return fluts
|
|
152
185
|
|
|
153
186
|
utils.compile(jit)
|
|
154
|
-
preluts = get_preluts(folder, zeta0, nkz0, nbeta, jit,
|
|
187
|
+
preluts = get_preluts(folder, zeta0, nkz0, nbeta, jit, resolved_n_cpu["preluts"], None)
|
|
155
188
|
|
|
156
189
|
low_level_out, high_level_out = get_level_range(zlow, zhigh, zhub, z0)
|
|
157
190
|
|
|
158
191
|
flut_generator = FourierLUTGenerator(preluts, zhub, diameter, zi)
|
|
159
192
|
if low_level_out == high_level_out == 9999:
|
|
160
|
-
fluts = flut_generator.make_hubheight_luts(z0, lut_vars, n_cpu=
|
|
193
|
+
fluts = flut_generator.make_hubheight_luts(z0, lut_vars, n_cpu=resolved_n_cpu["fluts"])
|
|
161
194
|
else:
|
|
162
|
-
fluts = flut_generator.make_lut(z0, low_level_out, high_level_out, lut_vars, n_cpu=
|
|
195
|
+
fluts = flut_generator.make_lut(z0, low_level_out, high_level_out, lut_vars, n_cpu=resolved_n_cpu["fluts"])
|
|
163
196
|
|
|
197
|
+
utils.stamp_lut_metadata(fluts)
|
|
164
198
|
fluts.save(fluts_path)
|
|
165
199
|
return fluts
|
|
166
200
|
|
|
167
201
|
|
|
168
202
|
def get_preluts(
|
|
169
|
-
folder,
|
|
170
|
-
zeta0,
|
|
171
|
-
nkz0,
|
|
172
|
-
nbeta,
|
|
173
|
-
jit=True,
|
|
174
|
-
n_cpu=
|
|
175
|
-
|
|
203
|
+
folder: str | Path,
|
|
204
|
+
zeta0: float,
|
|
205
|
+
nkz0: int,
|
|
206
|
+
nbeta: int,
|
|
207
|
+
jit: bool = True,
|
|
208
|
+
n_cpu: int | None = None,
|
|
209
|
+
n_cpu_stage: NCpuStageConfig | None = None,
|
|
210
|
+
) -> PreLUTs: # pragma: no cover
|
|
176
211
|
folder = Path(folder)
|
|
177
212
|
folder.mkdir(parents=True, exist_ok=True)
|
|
213
|
+
resolved_n_cpu = utils.resolve_n_cpu_stage(n_cpu, n_cpu_stage)
|
|
178
214
|
|
|
179
215
|
preluts_path = get_preluts_path(folder, zeta0, nkz0, nbeta)
|
|
180
216
|
if preluts_path.exists():
|
|
181
|
-
|
|
217
|
+
preluts = PreLUTs.from_netcdf(preluts_path)
|
|
218
|
+
if utils.load_lut_if_current(preluts, preluts_path):
|
|
219
|
+
return preluts
|
|
182
220
|
|
|
183
221
|
utils.compile(jit)
|
|
184
222
|
preluts = PreLUTs.make_preluts(
|
|
@@ -189,15 +227,16 @@ def get_preluts(
|
|
|
189
227
|
ds=DS,
|
|
190
228
|
accgoal=0.0001,
|
|
191
229
|
jit=jit,
|
|
192
|
-
n_cpu=
|
|
230
|
+
n_cpu=resolved_n_cpu["preluts"],
|
|
193
231
|
)
|
|
194
232
|
preluts.attrs["nkz0"] = nkz0
|
|
195
233
|
preluts.attrs["nbeta"] = nbeta
|
|
234
|
+
utils.stamp_lut_metadata(preluts)
|
|
196
235
|
preluts.save(preluts_path)
|
|
197
236
|
return preluts
|
|
198
237
|
|
|
199
238
|
|
|
200
|
-
def main():
|
|
239
|
+
def main() -> None:
|
|
201
240
|
if __name__ == "__main__": # pragma: no cover
|
|
202
241
|
luts = get_luts(
|
|
203
242
|
folder=Path(__file__).parent.parent,
|
|
@@ -16,6 +16,7 @@ Key components:
|
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
18
|
import numpy as np
|
|
19
|
+
import numpy.typing as npt
|
|
19
20
|
|
|
20
21
|
from pyfuga.utils import jit
|
|
21
22
|
|
|
@@ -23,7 +24,7 @@ from .constants import CM_STABLE, CM_UNSTABLE
|
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
@jit("double(double,double)")
|
|
26
|
-
def get_cdivkL(zeta0, kz0):
|
|
27
|
+
def get_cdivkL(zeta0: float, kz0: float) -> float:
|
|
27
28
|
"""c/(k*L), c is a stability constant: depending on the stability (0, Cm1, Cm2)"""
|
|
28
29
|
if abs(zeta0) < 1e-10: # Neutral
|
|
29
30
|
return 0.0
|
|
@@ -35,7 +36,7 @@ def get_cdivkL(zeta0, kz0):
|
|
|
35
36
|
|
|
36
37
|
|
|
37
38
|
@jit("double(double,double)")
|
|
38
|
-
def get_phi_inverse(kz, cdivkl):
|
|
39
|
+
def get_phi_inverse(kz: float, cdivkl: float) -> float:
|
|
39
40
|
"""
|
|
40
41
|
Inverse of stability function phi_m
|
|
41
42
|
for unstable conditions
|
|
@@ -45,7 +46,7 @@ def get_phi_inverse(kz, cdivkl):
|
|
|
45
46
|
|
|
46
47
|
|
|
47
48
|
@jit("double(double,double,double)")
|
|
48
|
-
def get_psi(zeta0, kz, cdivkl):
|
|
49
|
+
def get_psi(zeta0: float, kz: float, cdivkl: float) -> float:
|
|
49
50
|
"""Stability function -psi_m"""
|
|
50
51
|
if zeta0 < 0:
|
|
51
52
|
# Unstable: -psi_m=-ln(1/8*(1+phi_m**-2)*(1+phi_m**-1)**2)+2*atan(phi_m**-1)-pi/2
|
|
@@ -58,7 +59,7 @@ def get_psi(zeta0, kz, cdivkl):
|
|
|
58
59
|
|
|
59
60
|
|
|
60
61
|
@jit("double(double,double,double)")
|
|
61
|
-
def get_phi(zeta0, kz, cdivkl):
|
|
62
|
+
def get_phi(zeta0: float, kz: float, cdivkl: float) -> float:
|
|
62
63
|
"""Stability function phi_m"""
|
|
63
64
|
if zeta0 < 0: # Unstable: phi_m=(1+Cm1*z/L)**(-1/4)
|
|
64
65
|
return (1.0 + cdivkl * kz) ** (-0.25)
|
|
@@ -67,13 +68,18 @@ def get_phi(zeta0, kz, cdivkl):
|
|
|
67
68
|
|
|
68
69
|
|
|
69
70
|
@jit("double[:](complex128[:,:],int32)")
|
|
70
|
-
def complex_norm(arr, axis=0):
|
|
71
|
+
def complex_norm(arr: npt.NDArray[np.complex128], axis: int = 0) -> npt.NDArray[np.float64]:
|
|
71
72
|
"""Compute the norm of a complex array along the first axis"""
|
|
72
73
|
return np.sqrt((np.abs(arr) ** 2).sum(0))
|
|
73
74
|
|
|
74
75
|
|
|
75
76
|
@jit("double(double,double,complex128[:,:],complex128[:,:])")
|
|
76
|
-
def get_new_h2(
|
|
77
|
+
def get_new_h2(
|
|
78
|
+
h: float,
|
|
79
|
+
acc: float,
|
|
80
|
+
Yerr: npt.NDArray[np.complex128],
|
|
81
|
+
Y: npt.NDArray[np.complex128],
|
|
82
|
+
) -> float:
|
|
77
83
|
"""Get new step size based on error between integration estimates"""
|
|
78
84
|
err = np.max(complex_norm(Yerr, axis=0) / complex_norm(Y, axis=0))
|
|
79
85
|
return np.maximum(np.minimum(0.9 * h * (acc / err) ** (1 / 3), 4.0e-1), 1.0e-4)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""constants.py
|
|
2
|
+
==============
|
|
3
|
+
Global constants for atmospheric stability, numerical algorithms, and data structure sizes.
|
|
4
|
+
|
|
5
|
+
Key constants:
|
|
6
|
+
- **Stability (MO)**: CM_STABLE, CM_UNSTABLE for Monin-Obukhov relations
|
|
7
|
+
- **Numerics**: KAPPA (von Kármán), MAX_NODES (max preLUT nodes),
|
|
8
|
+
Y_NORM_THRESHOLD (threshold to trigger QR decomposition and substations
|
|
9
|
+
between nodes)
|
|
10
|
+
- **Data**: N_EQ (number of differential equations), UVW_LT (variable names)
|
|
11
|
+
- **Coordinate systems**: COORD_T (log profile), COORD_S (kz)
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
# PreLUT constants
|
|
15
|
+
CM_STABLE: float = 5.0 # MO Stability constant in phi_m (stable)
|
|
16
|
+
CM_UNSTABLE: float = -19.0 # MO Stability constant in phi_m (unstable) Hogstrom (1988)
|
|
17
|
+
N_EQ: int = 6 # ! Number of equations
|
|
18
|
+
KAPPA: float = 0.4 # Von Karman constant
|
|
19
|
+
KAPPA_SQUARED: float = 0.16 # Square of kappa
|
|
20
|
+
P_SCALE: int = 1 # Pressure normalisation variable
|
|
21
|
+
MAX_NODES: int = 400
|
|
22
|
+
Y_NORM_THRESHOLD: int = 100 # Max value of Ynorm before a sublevel is made
|
|
23
|
+
|
|
24
|
+
# LUT constant
|
|
25
|
+
FIRST_Z_OUTPUT: float = 1.0 # First height level in output
|
|
26
|
+
DS: float = 0.05 # Vertical grid spacing in log-height coordinate s = ln(z/z0)
|
|
27
|
+
|
|
28
|
+
# On-disk LUT cache compatibility version. Cached LUTs (preLUTs/fLUTs/LUTs) whose
|
|
29
|
+
# stored ``lut_format_version`` differs from this value - or which predate versioning
|
|
30
|
+
# entirely (legacy PyFuga caches) - are rejected and regenerated instead of loaded.
|
|
31
|
+
#
|
|
32
|
+
# Bump this for SEMANTIC/NUMERICAL changes to LUT output that keep the same on-disk
|
|
33
|
+
# structure (e.g. a physics bug-fix that changes values). STRUCTURAL changes (added,
|
|
34
|
+
# removed or renamed variables/dimensions) are additionally caught automatically by the
|
|
35
|
+
# ``lut_schema_fingerprint`` attribute and by the schema guard test in
|
|
36
|
+
# ``tests/test_lut_schema.py``, which fails and reminds you to bump this too.
|
|
37
|
+
LUT_FORMAT_VERSION: int = 1
|
|
38
|
+
|
|
39
|
+
# Other constants
|
|
40
|
+
UVW_LT: list[str] = ["UL", "UT", "VL", "VT", "WL", "WT", "PL", "PT"]
|
|
41
|
+
|
|
42
|
+
# Coordinate system indicators
|
|
43
|
+
COORD_T: int = 1 # t = u0 * KAPPA
|
|
44
|
+
COORD_S: int = 2 # s = kz
|
|
@@ -10,26 +10,34 @@ with legacy simulation data.
|
|
|
10
10
|
|
|
11
11
|
import os
|
|
12
12
|
import struct
|
|
13
|
+
from io import BufferedReader
|
|
13
14
|
from pathlib import Path
|
|
15
|
+
from typing import Any, TypeAlias
|
|
14
16
|
|
|
15
17
|
import numpy as np
|
|
16
18
|
import xarray as xr
|
|
17
19
|
from numpy import newaxis as na
|
|
18
20
|
|
|
21
|
+
PathLike: TypeAlias = str | os.PathLike[str]
|
|
22
|
+
PrelutTuple: TypeAlias = tuple[float, float, float, float, float, float]
|
|
23
|
+
PrelutListEntry: TypeAlias = tuple[str, float, float, float, float, float, float]
|
|
24
|
+
|
|
19
25
|
|
|
20
26
|
class BinaryReader:
|
|
21
|
-
|
|
27
|
+
fid: BufferedReader
|
|
28
|
+
|
|
29
|
+
def s(self, n: int) -> str:
|
|
22
30
|
return self.fid.read(n).decode().strip()
|
|
23
31
|
|
|
24
|
-
def i(self):
|
|
25
|
-
return struct.unpack("i", self.fid.read(4))[0]
|
|
32
|
+
def i(self) -> int:
|
|
33
|
+
return int(struct.unpack("i", self.fid.read(4))[0])
|
|
26
34
|
|
|
27
|
-
def d(self):
|
|
28
|
-
return struct.unpack("d", self.fid.read(8))[0]
|
|
35
|
+
def d(self) -> float:
|
|
36
|
+
return float(struct.unpack("d", self.fid.read(8))[0])
|
|
29
37
|
|
|
30
38
|
|
|
31
39
|
class Parameters(BinaryReader):
|
|
32
|
-
def __init__(self, folder):
|
|
40
|
+
def __init__(self, folder: PathLike) -> None:
|
|
33
41
|
i, d, s = self.i, self.d, self.s
|
|
34
42
|
with open(Path(folder) / "parameters.bin", "rb") as self.fid:
|
|
35
43
|
self.prelutname = s(127)
|
|
@@ -50,7 +58,7 @@ class Parameters(BinaryReader):
|
|
|
50
58
|
|
|
51
59
|
|
|
52
60
|
class CaseData(BinaryReader):
|
|
53
|
-
def __init__(self, folder):
|
|
61
|
+
def __init__(self, folder: PathLike) -> None:
|
|
54
62
|
i, d, s = self.i, self.d, self.s
|
|
55
63
|
|
|
56
64
|
with open(Path(folder) / "CaseData.bin", "rb") as self.fid:
|
|
@@ -65,31 +73,31 @@ class CaseData(BinaryReader):
|
|
|
65
73
|
self.closure = i()
|
|
66
74
|
|
|
67
75
|
|
|
68
|
-
def eof(fid):
|
|
76
|
+
def eof(fid: BufferedReader) -> int:
|
|
69
77
|
fid.seek(-1, 2) # go to the file end.
|
|
70
78
|
eof = fid.tell() + 1 # get the end of file location
|
|
71
79
|
fid.seek(0, 0) # go back to file beginning
|
|
72
80
|
return eof
|
|
73
81
|
|
|
74
82
|
|
|
75
|
-
def read_prelut_list(folder, dict=True):
|
|
83
|
+
def read_prelut_list(folder: PathLike, dict: bool = True) -> dict[str, PrelutTuple] | list[PrelutListEntry]:
|
|
76
84
|
|
|
77
85
|
folder = Path(folder)
|
|
78
86
|
with open(folder / "prelut_list.lst", "rb") as fid:
|
|
79
87
|
fid.seek(18)
|
|
80
88
|
n = 24 if fid.read(6) == b"\x00\x00\x00\x00\x00\x00" else 18
|
|
81
89
|
|
|
82
|
-
|
|
90
|
+
n_rows = eof(fid) // (n + 48)
|
|
83
91
|
|
|
84
92
|
# list of filename (18 or 18+6xNull), ds, smaxx, kz0, beta, kzmax, accgoal
|
|
85
93
|
if dict:
|
|
86
|
-
return {fid.read(n)[:18].decode(): struct.unpack("d" * 6, fid.read(8 * 6)) for _ in range(
|
|
94
|
+
return {fid.read(n)[:18].decode(): struct.unpack("d" * 6, fid.read(8 * 6)) for _ in range(n_rows)}
|
|
87
95
|
|
|
88
96
|
else:
|
|
89
|
-
return [(fid.read(n)[:18].decode(),) + struct.unpack("d" * 6, fid.read(8 * 6)) for _ in range(
|
|
97
|
+
return [(fid.read(n)[:18].decode(),) + struct.unpack("d" * 6, fid.read(8 * 6)) for _ in range(n_rows)]
|
|
90
98
|
|
|
91
99
|
|
|
92
|
-
def read_pre_file(filename):
|
|
100
|
+
def read_pre_file(filename: PathLike) -> dict[str, tuple[list[str], Any]]:
|
|
93
101
|
with open(filename, "rb") as fid:
|
|
94
102
|
d = np.fromfile(fid, float, -1).reshape((-1, 291))
|
|
95
103
|
Yleft, Rleft, Rright, dy = np.moveaxis((d[:, :288:2] + d[:, 1:288:2] * 1j).reshape((-1, 4, 6, 6)), 1, 0)
|
|
@@ -112,15 +120,16 @@ def read_pre_file(filename):
|
|
|
112
120
|
}
|
|
113
121
|
|
|
114
122
|
|
|
115
|
-
def read_lut_file(filename, prelut_folder):
|
|
123
|
+
def read_lut_file(filename: PathLike, prelut_folder: PathLike) -> xr.Dataset:
|
|
124
|
+
filename_path = Path(filename)
|
|
116
125
|
parameters = Parameters(prelut_folder)
|
|
117
|
-
casedata = CaseData(
|
|
118
|
-
var =
|
|
119
|
-
level = int(
|
|
126
|
+
casedata = CaseData(filename_path.parent)
|
|
127
|
+
var = filename_path.name[:2]
|
|
128
|
+
level = int(filename_path.name[-8:-4])
|
|
120
129
|
z = casedata.zhub if level == 9999 else casedata.z0 * np.exp(casedata.ds * level)
|
|
121
130
|
|
|
122
|
-
|
|
123
|
-
lut =
|
|
131
|
+
raw = np.fromfile(filename, complex, -1).reshape((len(parameters.kz0_lst) + 1, len(parameters.beta_lst)))
|
|
132
|
+
lut = raw[1:].T
|
|
124
133
|
return xr.Dataset(
|
|
125
134
|
{
|
|
126
135
|
var: (("beta", "kz0", "level"), lut[:, :, na]),
|
|
@@ -133,7 +142,7 @@ def read_lut_file(filename, prelut_folder):
|
|
|
133
142
|
)
|
|
134
143
|
|
|
135
144
|
|
|
136
|
-
def read_lut_dat_file(filename, nx, ny, dx, dy):
|
|
145
|
+
def read_lut_dat_file(filename: PathLike, nx: int, ny: int, dx: float, dy: float) -> xr.DataArray:
|
|
137
146
|
dat = np.fromfile(filename, np.dtype("<f"), -1).astype(float).reshape((ny // 2, nx))
|
|
138
147
|
x_lst = np.arange(-nx // 4, nx * 3 / 4) * dx # rotor is located 1/4 downstream
|
|
139
148
|
|