pyfuga 0.2.0__tar.gz → 0.3.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.3.0}/PKG-INFO +20 -6
- {pyfuga-0.2.0 → pyfuga-0.3.0}/README.md +16 -5
- {pyfuga-0.2.0 → pyfuga-0.3.0}/pyfuga/__init__.py +7 -6
- {pyfuga-0.2.0 → pyfuga-0.3.0}/pyfuga/_fuga.py +79 -48
- {pyfuga-0.2.0 → pyfuga-0.3.0}/pyfuga/common.py +12 -6
- pyfuga-0.3.0/pyfuga/constants.py +33 -0
- {pyfuga-0.2.0 → pyfuga-0.3.0}/pyfuga/file_readers.py +27 -18
- {pyfuga-0.2.0 → pyfuga-0.3.0}/pyfuga/flut.py +219 -143
- {pyfuga-0.2.0 → pyfuga-0.3.0}/pyfuga/paths.py +37 -4
- {pyfuga-0.2.0 → pyfuga-0.3.0}/pyfuga/preludium_eq_routines.py +9 -6
- {pyfuga-0.2.0 → pyfuga-0.3.0}/pyfuga/preluts.py +76 -42
- {pyfuga-0.2.0 → pyfuga-0.3.0}/pyfuga/preluts_generator/generator.py +23 -5
- {pyfuga-0.2.0 → pyfuga-0.3.0}/pyfuga/preluts_generator/integration.py +22 -4
- {pyfuga-0.2.0 → pyfuga-0.3.0}/pyfuga/preluts_generator/matrices.py +40 -40
- {pyfuga-0.2.0 → pyfuga-0.3.0}/pyfuga/preluts_generator/nodes.py +5 -5
- pyfuga-0.3.0/pyfuga/profiling.py +109 -0
- {pyfuga-0.2.0 → pyfuga-0.3.0}/pyfuga/trafalgar.py +35 -21
- pyfuga-0.3.0/pyfuga/utils.py +351 -0
- {pyfuga-0.2.0 → pyfuga-0.3.0}/pyfuga/z0.py +11 -10
- {pyfuga-0.2.0 → pyfuga-0.3.0}/pyfuga.egg-info/PKG-INFO +20 -6
- {pyfuga-0.2.0 → pyfuga-0.3.0}/pyfuga.egg-info/SOURCES.txt +2 -0
- {pyfuga-0.2.0 → pyfuga-0.3.0}/pyfuga.egg-info/requires.txt +3 -0
- {pyfuga-0.2.0 → pyfuga-0.3.0}/pyproject.toml +97 -8
- pyfuga-0.3.0/tests/test_api.py +410 -0
- pyfuga-0.3.0/tests/test_common.py +6 -0
- {pyfuga-0.2.0 → pyfuga-0.3.0}/tests/test_flut.py +131 -16
- {pyfuga-0.2.0 → pyfuga-0.3.0}/tests/test_preluts_jit.py +1 -1
- pyfuga-0.3.0/tests/test_profiling.py +12 -0
- pyfuga-0.3.0/tests/test_trafalgar.py +110 -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 → pyfuga-0.3.0}/LICENSE +0 -0
- {pyfuga-0.2.0 → pyfuga-0.3.0}/pyfuga/preluts_generator/__init__.py +0 -0
- {pyfuga-0.2.0 → pyfuga-0.3.0}/pyfuga/preluts_generator/transforms.py +0 -0
- {pyfuga-0.2.0 → pyfuga-0.3.0}/pyfuga/typing.py +0 -0
- {pyfuga-0.2.0 → pyfuga-0.3.0}/pyfuga.egg-info/dependency_links.txt +0 -0
- {pyfuga-0.2.0 → pyfuga-0.3.0}/pyfuga.egg-info/top_level.txt +0 -0
- {pyfuga-0.2.0 → pyfuga-0.3.0}/setup.cfg +0 -0
- {pyfuga-0.2.0 → pyfuga-0.3.0}/tests/test_jit_signatures.py +0 -0
- {pyfuga-0.2.0 → pyfuga-0.3.0}/tests/test_paths.py +0 -0
- {pyfuga-0.2.0 → pyfuga-0.3.0}/tests/test_preluts.py +0 -0
- {pyfuga-0.2.0 → pyfuga-0.3.0}/tests/test_readers.py +0 -0
- {pyfuga-0.2.0 → pyfuga-0.3.0}/tests/test_typing.py +0 -0
- {pyfuga-0.2.0 → pyfuga-0.3.0}/tests/test_utils.py +0 -0
- {pyfuga-0.2.0 → pyfuga-0.3.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.3.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.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,6 +123,7 @@ 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
|
|
|
@@ -121,60 +133,79 @@ def get_luts(
|
|
|
121
133
|
luts.attrs["name"] = luts_path.stem
|
|
122
134
|
return luts
|
|
123
135
|
|
|
124
|
-
fluts = get_fluts(
|
|
125
|
-
|
|
136
|
+
fluts = get_fluts(
|
|
137
|
+
folder,
|
|
138
|
+
zeta0,
|
|
139
|
+
nkz0,
|
|
140
|
+
nbeta,
|
|
141
|
+
diameter,
|
|
142
|
+
zhub,
|
|
143
|
+
z0,
|
|
144
|
+
zi,
|
|
145
|
+
zlow,
|
|
146
|
+
zhigh,
|
|
147
|
+
lut_vars,
|
|
148
|
+
jit,
|
|
149
|
+
resolved_n_cpu["fluts"],
|
|
150
|
+
n_cpu_stage={"preluts": resolved_n_cpu["preluts"]},
|
|
151
|
+
)
|
|
152
|
+
luts = Trafalgar(fluts, nx, ny, dx, dy).make_luts(interpolation_order=3, n_cpu=resolved_n_cpu["trafalgar"])
|
|
126
153
|
luts.to_netcdf(luts_path)
|
|
127
154
|
luts.attrs["name"] = luts_path.stem
|
|
128
155
|
return luts
|
|
129
156
|
|
|
130
157
|
|
|
131
158
|
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
|
-
|
|
159
|
+
folder: str | Path,
|
|
160
|
+
zeta0: float,
|
|
161
|
+
nkz0: int,
|
|
162
|
+
nbeta: int,
|
|
163
|
+
diameter: float,
|
|
164
|
+
zhub: float,
|
|
165
|
+
z0: float,
|
|
166
|
+
zi: float,
|
|
167
|
+
zlow: float,
|
|
168
|
+
zhigh: float,
|
|
169
|
+
lut_vars: list[str] = UVW_LT,
|
|
170
|
+
jit: bool = True,
|
|
171
|
+
n_cpu: int | None = None,
|
|
172
|
+
n_cpu_stage: NCpuStageConfig | None = None,
|
|
173
|
+
) -> ComplexXRDataset: # pragma: no cover
|
|
146
174
|
folder = Path(folder)
|
|
147
175
|
folder.mkdir(parents=True, exist_ok=True)
|
|
176
|
+
resolved_n_cpu = utils.resolve_n_cpu_stage(n_cpu, n_cpu_stage)
|
|
148
177
|
|
|
149
178
|
fluts_path = get_fluts_path(folder, zeta0, nkz0, nbeta, diameter, zhub, z0, zi, zlow, zhigh, lut_vars)
|
|
150
179
|
if fluts_path.exists():
|
|
151
180
|
return ComplexXRDataset.from_netcdf(fluts_path)
|
|
152
181
|
|
|
153
182
|
utils.compile(jit)
|
|
154
|
-
preluts = get_preluts(folder, zeta0, nkz0, nbeta, jit,
|
|
183
|
+
preluts = get_preluts(folder, zeta0, nkz0, nbeta, jit, resolved_n_cpu["preluts"], None)
|
|
155
184
|
|
|
156
185
|
low_level_out, high_level_out = get_level_range(zlow, zhigh, zhub, z0)
|
|
157
186
|
|
|
158
187
|
flut_generator = FourierLUTGenerator(preluts, zhub, diameter, zi)
|
|
159
188
|
if low_level_out == high_level_out == 9999:
|
|
160
|
-
fluts = flut_generator.make_hubheight_luts(z0, lut_vars, n_cpu=
|
|
189
|
+
fluts = flut_generator.make_hubheight_luts(z0, lut_vars, n_cpu=resolved_n_cpu["fluts"])
|
|
161
190
|
else:
|
|
162
|
-
fluts = flut_generator.make_lut(z0, low_level_out, high_level_out, lut_vars, n_cpu=
|
|
191
|
+
fluts = flut_generator.make_lut(z0, low_level_out, high_level_out, lut_vars, n_cpu=resolved_n_cpu["fluts"])
|
|
163
192
|
|
|
164
193
|
fluts.save(fluts_path)
|
|
165
194
|
return fluts
|
|
166
195
|
|
|
167
196
|
|
|
168
197
|
def get_preluts(
|
|
169
|
-
folder,
|
|
170
|
-
zeta0,
|
|
171
|
-
nkz0,
|
|
172
|
-
nbeta,
|
|
173
|
-
jit=True,
|
|
174
|
-
n_cpu=
|
|
175
|
-
|
|
198
|
+
folder: str | Path,
|
|
199
|
+
zeta0: float,
|
|
200
|
+
nkz0: int,
|
|
201
|
+
nbeta: int,
|
|
202
|
+
jit: bool = True,
|
|
203
|
+
n_cpu: int | None = None,
|
|
204
|
+
n_cpu_stage: NCpuStageConfig | None = None,
|
|
205
|
+
) -> PreLUTs: # pragma: no cover
|
|
176
206
|
folder = Path(folder)
|
|
177
207
|
folder.mkdir(parents=True, exist_ok=True)
|
|
208
|
+
resolved_n_cpu = utils.resolve_n_cpu_stage(n_cpu, n_cpu_stage)
|
|
178
209
|
|
|
179
210
|
preluts_path = get_preluts_path(folder, zeta0, nkz0, nbeta)
|
|
180
211
|
if preluts_path.exists():
|
|
@@ -189,7 +220,7 @@ def get_preluts(
|
|
|
189
220
|
ds=DS,
|
|
190
221
|
accgoal=0.0001,
|
|
191
222
|
jit=jit,
|
|
192
|
-
n_cpu=
|
|
223
|
+
n_cpu=resolved_n_cpu["preluts"],
|
|
193
224
|
)
|
|
194
225
|
preluts.attrs["nkz0"] = nkz0
|
|
195
226
|
preluts.attrs["nbeta"] = nbeta
|
|
@@ -197,7 +228,7 @@ def get_preluts(
|
|
|
197
228
|
return preluts
|
|
198
229
|
|
|
199
230
|
|
|
200
|
-
def main():
|
|
231
|
+
def main() -> None:
|
|
201
232
|
if __name__ == "__main__": # pragma: no cover
|
|
202
233
|
luts = get_luts(
|
|
203
234
|
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,33 @@
|
|
|
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
|
+
# Other constants
|
|
29
|
+
UVW_LT: list[str] = ["UL", "UT", "VL", "VT", "WL", "WT", "PL", "PT"]
|
|
30
|
+
|
|
31
|
+
# Coordinate system indicators
|
|
32
|
+
COORD_T: int = 1 # t = u0 * KAPPA
|
|
33
|
+
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,11 +120,12 @@ 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
131
|
lut = np.fromfile(filename, complex, -1).reshape((len(parameters.kz0_lst) + 1, len(parameters.beta_lst)))
|
|
@@ -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
|
|