pyfuga 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.
- pyfuga-0.1.0/LICENSE +21 -0
- pyfuga-0.1.0/PKG-INFO +124 -0
- pyfuga-0.1.0/README.md +63 -0
- pyfuga-0.1.0/pyfuga/__init__.py +20 -0
- pyfuga-0.1.0/pyfuga/_fuga.py +230 -0
- pyfuga-0.1.0/pyfuga/common.py +81 -0
- pyfuga-0.1.0/pyfuga/constants.py +32 -0
- pyfuga-0.1.0/pyfuga/file_readers.py +141 -0
- pyfuga-0.1.0/pyfuga/flut.py +477 -0
- pyfuga-0.1.0/pyfuga/preludium_eq_routines.py +90 -0
- pyfuga-0.1.0/pyfuga/preluts.py +217 -0
- pyfuga-0.1.0/pyfuga/preluts_generator/__init__.py +10 -0
- pyfuga-0.1.0/pyfuga/preluts_generator/generator.py +340 -0
- pyfuga-0.1.0/pyfuga/preluts_generator/integration.py +359 -0
- pyfuga-0.1.0/pyfuga/preluts_generator/matrices.py +182 -0
- pyfuga-0.1.0/pyfuga/preluts_generator/nodes.py +122 -0
- pyfuga-0.1.0/pyfuga/preluts_generator/transforms.py +123 -0
- pyfuga-0.1.0/pyfuga/profiling.py +86 -0
- pyfuga-0.1.0/pyfuga/trafalgar.py +525 -0
- pyfuga-0.1.0/pyfuga/typing.py +9 -0
- pyfuga-0.1.0/pyfuga/utils.py +228 -0
- pyfuga-0.1.0/pyfuga/z0.py +143 -0
- pyfuga-0.1.0/pyfuga.egg-info/PKG-INFO +124 -0
- pyfuga-0.1.0/pyfuga.egg-info/SOURCES.txt +37 -0
- pyfuga-0.1.0/pyfuga.egg-info/dependency_links.txt +1 -0
- pyfuga-0.1.0/pyfuga.egg-info/requires.txt +39 -0
- pyfuga-0.1.0/pyfuga.egg-info/top_level.txt +1 -0
- pyfuga-0.1.0/pyproject.toml +221 -0
- pyfuga-0.1.0/setup.cfg +4 -0
- pyfuga-0.1.0/tests/test_api.py +102 -0
- pyfuga-0.1.0/tests/test_flut.py +312 -0
- pyfuga-0.1.0/tests/test_jit_signatures.py +222 -0
- pyfuga-0.1.0/tests/test_preluts.py +396 -0
- pyfuga-0.1.0/tests/test_preluts_jit.py +273 -0
- pyfuga-0.1.0/tests/test_readers.py +117 -0
- pyfuga-0.1.0/tests/test_trafalgar.py +55 -0
- pyfuga-0.1.0/tests/test_typing.py +27 -0
- pyfuga-0.1.0/tests/test_utils.py +19 -0
- pyfuga-0.1.0/tests/test_z0.py +364 -0
pyfuga-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2018 DTU Wind And Energy Systems
|
|
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.
|
pyfuga-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyfuga
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Linearised CFD wake model (PyFuga)
|
|
5
|
+
Author-email: PyFuga team <lalc@dtu.dk>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Repository, https://gitlab.windenergy.dtu.dk/TOPFARM/PyFuga
|
|
8
|
+
Project-URL: Documentation, https://topfarm.pages.windenergy.dtu.dk/PyFuga/main/index.html
|
|
9
|
+
Project-URL: Issues, https://gitlab.windenergy.dtu.dk/TOPFARM/PyFuga/-/issues
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
13
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
14
|
+
Classifier: Programming Language :: Python
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering
|
|
22
|
+
Requires-Python: <3.15,>=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: numpy>=1.26
|
|
26
|
+
Requires-Dist: scipy>=1.10
|
|
27
|
+
Requires-Dist: matplotlib>=3.10.8
|
|
28
|
+
Requires-Dist: xarray>=2025.6.1
|
|
29
|
+
Requires-Dist: tqdm>=4.67.3
|
|
30
|
+
Requires-Dist: h5netcdf>=1.8.1
|
|
31
|
+
Requires-Dist: h5py>=3.15.1
|
|
32
|
+
Requires-Dist: numba>=0.63.1
|
|
33
|
+
Provides-Extra: tests
|
|
34
|
+
Requires-Dist: pytest>=8.3; extra == "tests"
|
|
35
|
+
Requires-Dist: pytest-cov>=6.0; extra == "tests"
|
|
36
|
+
Requires-Dist: pytest-xdist>=3.0; extra == "tests"
|
|
37
|
+
Requires-Dist: memory_profiler>=0.61.0; extra == "tests"
|
|
38
|
+
Requires-Dist: line_profiler>=5.0.0; extra == "tests"
|
|
39
|
+
Requires-Dist: nbconvert<8,>=7.16.6; extra == "tests"
|
|
40
|
+
Requires-Dist: ipykernel<7,>=6.29; extra == "tests"
|
|
41
|
+
Requires-Dist: jupyter_client<9,>=8.6; extra == "tests"
|
|
42
|
+
Provides-Extra: lint
|
|
43
|
+
Requires-Dist: ruff>0.14; extra == "lint"
|
|
44
|
+
Requires-Dist: black[jupyter]>=26.1.0; extra == "lint"
|
|
45
|
+
Requires-Dist: mypy>=1.19.1; extra == "lint"
|
|
46
|
+
Provides-Extra: docs
|
|
47
|
+
Requires-Dist: sphinx<9,>=8.1; extra == "docs"
|
|
48
|
+
Requires-Dist: sphinx-autobuild>=2024.10; extra == "docs"
|
|
49
|
+
Requires-Dist: sphinx_rtd_theme>=3.0; extra == "docs"
|
|
50
|
+
Requires-Dist: sphinx-rtd-dark-mode>=1.3.0; extra == "docs"
|
|
51
|
+
Requires-Dist: nbsphinx>=0.9.7; extra == "docs"
|
|
52
|
+
Requires-Dist: sphinxcontrib-bibtex>=2.6; extra == "docs"
|
|
53
|
+
Requires-Dist: sphinx-multiversion>=0.2.4; extra == "docs"
|
|
54
|
+
Requires-Dist: sphinx-copybutton>=0.5.2; extra == "docs"
|
|
55
|
+
Requires-Dist: ipykernel>=6.29; extra == "docs"
|
|
56
|
+
Provides-Extra: dev
|
|
57
|
+
Requires-Dist: pyfuga[docs,lint,tests]; extra == "dev"
|
|
58
|
+
Requires-Dist: pre-commit; extra == "dev"
|
|
59
|
+
Requires-Dist: build<2,>=1.4.0; extra == "dev"
|
|
60
|
+
Dynamic: license-file
|
|
61
|
+
|
|
62
|
+
<p align="center">
|
|
63
|
+
<img width="460" height="300" src="https://gitlab.windenergy.dtu.dk/TOPFARM/PyFuga/-/raw/main/docs/_static/logo_icon_text.svg">
|
|
64
|
+
</p>
|
|
65
|
+
|
|
66
|
+
**PyFuga** is the Python implementation of the **Fuga** look-up table (LUT) generator for wind turbine wakes, previously computed in FORTRAN and with the Windows Fuga GUI.
|
|
67
|
+
|
|
68
|
+
PyFuga is part of the [**PyWake**](https://gitlab.windenergy.dtu.dk/TOPFARM/PyWake) ecosystem and provides the LUTs required to run PyWake's Fuga wake deficit model.
|
|
69
|
+
|
|
70
|
+
[](https://gitlab.windenergy.dtu.dk/TOPFARM/PyFuga/-/commits/main)
|
|
71
|
+
[](https://gitlab.windenergy.dtu.dk/TOPFARM/PyFuga/-/commits/main)
|
|
72
|
+
[](https://topfarm.pages.windenergy.dtu.dk/PyFuga/main/index.html)
|
|
73
|
+
|
|
74
|
+
## Installation
|
|
75
|
+
|
|
76
|
+
Install via pip:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pip install pyfuga
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
or from conda-forge with conda:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
conda install conda-forge:pyfuga
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
or Pixi:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
pixi add pyfuga
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
A minimal example to get you started is available in **[the QuickStart Jupyter notebook](docs/examples/01_QuickStart.ipynb)**.
|
|
95
|
+
|
|
96
|
+
## Documentation
|
|
97
|
+
|
|
98
|
+
Learn more about PyFuga at the **[official documentation](https://topfarm.pages.windenergy.dtu.dk/PyFuga/main/index.html)**.
|
|
99
|
+
|
|
100
|
+
## Release history
|
|
101
|
+
|
|
102
|
+
See **[CHANGELOG.md](CHANGELOG.md)** for release notes and version history.
|
|
103
|
+
|
|
104
|
+
## Contributing
|
|
105
|
+
|
|
106
|
+
Read more at **[CONTRIBUTING.md](CONTRIBUTING.md)**.
|
|
107
|
+
|
|
108
|
+
## Support
|
|
109
|
+
|
|
110
|
+
Issues and feature requests can be submitted through the project's [**GitLab issue tracker**](https://gitlab.windenergy.dtu.dk/TOPFARM/PyFuga/-/issues).
|
|
111
|
+
|
|
112
|
+
## Authors and acknowledgements
|
|
113
|
+
|
|
114
|
+
PyFuga is developed at **DTU Wind and Energy Systems** and builds on the Fuga model described in the technical report by Søren Ott, Mads Mølgaard Pedersen, Gunnar Chr. Larsen, Leonardo Alcayaga, Nils Gaukroger, Elvira Jarmbæk Jacobsen, and colleagues.
|
|
115
|
+
|
|
116
|
+
The PyFuga project would like to acknowledge **Equinor ASA** for their support of the project over many years.
|
|
117
|
+
|
|
118
|
+
## Licence
|
|
119
|
+
|
|
120
|
+
This project is released under the terms of the licence in the [`LICENSE`](LICENSE) file.
|
|
121
|
+
|
|
122
|
+
## Project status
|
|
123
|
+
|
|
124
|
+
Active development as part of the PyWake suite. PyFuga continues to evolve alongside ongoing improvements to the Fuga model and its numerical implementation.
|
pyfuga-0.1.0/README.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img width="460" height="300" src="https://gitlab.windenergy.dtu.dk/TOPFARM/PyFuga/-/raw/main/docs/_static/logo_icon_text.svg">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
**PyFuga** is the Python implementation of the **Fuga** look-up table (LUT) generator for wind turbine wakes, previously computed in FORTRAN and with the Windows Fuga GUI.
|
|
6
|
+
|
|
7
|
+
PyFuga is part of the [**PyWake**](https://gitlab.windenergy.dtu.dk/TOPFARM/PyWake) ecosystem and provides the LUTs required to run PyWake's Fuga wake deficit model.
|
|
8
|
+
|
|
9
|
+
[](https://gitlab.windenergy.dtu.dk/TOPFARM/PyFuga/-/commits/main)
|
|
10
|
+
[](https://gitlab.windenergy.dtu.dk/TOPFARM/PyFuga/-/commits/main)
|
|
11
|
+
[](https://topfarm.pages.windenergy.dtu.dk/PyFuga/main/index.html)
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
Install via pip:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install pyfuga
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
or from conda-forge with conda:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
conda install conda-forge:pyfuga
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
or Pixi:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pixi add pyfuga
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
A minimal example to get you started is available in **[the QuickStart Jupyter notebook](docs/examples/01_QuickStart.ipynb)**.
|
|
34
|
+
|
|
35
|
+
## Documentation
|
|
36
|
+
|
|
37
|
+
Learn more about PyFuga at the **[official documentation](https://topfarm.pages.windenergy.dtu.dk/PyFuga/main/index.html)**.
|
|
38
|
+
|
|
39
|
+
## Release history
|
|
40
|
+
|
|
41
|
+
See **[CHANGELOG.md](CHANGELOG.md)** for release notes and version history.
|
|
42
|
+
|
|
43
|
+
## Contributing
|
|
44
|
+
|
|
45
|
+
Read more at **[CONTRIBUTING.md](CONTRIBUTING.md)**.
|
|
46
|
+
|
|
47
|
+
## Support
|
|
48
|
+
|
|
49
|
+
Issues and feature requests can be submitted through the project's [**GitLab issue tracker**](https://gitlab.windenergy.dtu.dk/TOPFARM/PyFuga/-/issues).
|
|
50
|
+
|
|
51
|
+
## Authors and acknowledgements
|
|
52
|
+
|
|
53
|
+
PyFuga is developed at **DTU Wind and Energy Systems** and builds on the Fuga model described in the technical report by Søren Ott, Mads Mølgaard Pedersen, Gunnar Chr. Larsen, Leonardo Alcayaga, Nils Gaukroger, Elvira Jarmbæk Jacobsen, and colleagues.
|
|
54
|
+
|
|
55
|
+
The PyFuga project would like to acknowledge **Equinor ASA** for their support of the project over many years.
|
|
56
|
+
|
|
57
|
+
## Licence
|
|
58
|
+
|
|
59
|
+
This project is released under the terms of the licence in the [`LICENSE`](LICENSE) file.
|
|
60
|
+
|
|
61
|
+
## Project status
|
|
62
|
+
|
|
63
|
+
Active development as part of the PyWake suite. PyFuga continues to evolve alongside ongoing improvements to the Fuga model and its numerical implementation.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import inspect
|
|
3
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
4
|
+
|
|
5
|
+
from . import _fuga
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
__version__ = version("pyfuga")
|
|
9
|
+
except PackageNotFoundError:
|
|
10
|
+
__version__ = "0+unknown"
|
|
11
|
+
|
|
12
|
+
__all__ = ["get_luts", "__version__"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@functools.wraps(_fuga.get_luts)
|
|
16
|
+
def get_luts(*args, **kwargs):
|
|
17
|
+
return _fuga.get_luts(*args, **kwargs)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
get_luts.__signature__ = inspect.signature(_fuga.get_luts)
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"""_fuga.py
|
|
2
|
+
==========
|
|
3
|
+
Main API entry point for PyFuga — generate wake look-up tables.
|
|
4
|
+
|
|
5
|
+
This module orchestrates the full three-stage PyFuga pipeline:
|
|
6
|
+
|
|
7
|
+
1. **Preliminary LUTs (preLUTs)** — via :mod:`pyfuga.preluts_generator`
|
|
8
|
+
Integrates atmospheric boundary layer equations between vertical stations.
|
|
9
|
+
|
|
10
|
+
2. **Fourier LUTs** — via :mod:`pyfuga.flut`
|
|
11
|
+
Solves for spectral wake response to all forcing directions.
|
|
12
|
+
|
|
13
|
+
3. **Physical-space LUTs** — via :mod:`pyfuga.trafalgar`
|
|
14
|
+
Inverse Fourier transform to obtain wake fields in physical x-y-z space.
|
|
15
|
+
|
|
16
|
+
The entry point is :func:`get_luts`, which orchestrates all three stages,
|
|
17
|
+
handling file caching and data I/O.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import os
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
import numpy as np
|
|
24
|
+
import xarray as xr
|
|
25
|
+
|
|
26
|
+
from pyfuga import utils
|
|
27
|
+
from pyfuga.constants import UVW_LT
|
|
28
|
+
from pyfuga.flut import FourierLUTGenerator
|
|
29
|
+
from pyfuga.preluts import PreLUTs
|
|
30
|
+
from pyfuga.trafalgar import Trafalgar
|
|
31
|
+
from pyfuga.utils import ComplexXRDataset, get_beta_lst, get_kz0_lst
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_luts(
|
|
35
|
+
folder,
|
|
36
|
+
zeta0,
|
|
37
|
+
nkz0,
|
|
38
|
+
nbeta,
|
|
39
|
+
diameter,
|
|
40
|
+
zhub,
|
|
41
|
+
z0,
|
|
42
|
+
zi,
|
|
43
|
+
zlow,
|
|
44
|
+
zhigh,
|
|
45
|
+
lut_vars=UVW_LT,
|
|
46
|
+
nx=2048,
|
|
47
|
+
ny=512,
|
|
48
|
+
dx=None,
|
|
49
|
+
dy=None,
|
|
50
|
+
jit=True,
|
|
51
|
+
n_cpu=1,
|
|
52
|
+
): # pragma: no cover
|
|
53
|
+
"""Generate physical-space wake look-up tables via the full PyFuga pipeline.
|
|
54
|
+
|
|
55
|
+
This function orchestrates the three-stage PyFuga pipeline:
|
|
56
|
+
1. Preliminary LUTs (preLUTs) via pyfuga.preluts_generator
|
|
57
|
+
2. Fourier LUTs via pyfuga.flut
|
|
58
|
+
3. Physical-space LUTs via pyfuga.trafalgar
|
|
59
|
+
|
|
60
|
+
Caches intermediate results (preLUTs and Fourier LUTs) as NetCDF files in
|
|
61
|
+
the specified folder, skipping recomputation if files exist.
|
|
62
|
+
|
|
63
|
+
Parameters
|
|
64
|
+
----------
|
|
65
|
+
folder : str or Path
|
|
66
|
+
Output folder for all generated files (preLUTs, Fourier LUTs, LUTs).
|
|
67
|
+
zeta0 : float
|
|
68
|
+
Monin-Obukhov stability parameter (z0/L). Positive=stable, negative=unstable,
|
|
69
|
+
zero=neutral.
|
|
70
|
+
nkz0 : int
|
|
71
|
+
Spectral density: number of wavenumbers per log decade.
|
|
72
|
+
Total count = 9*nkz0 - (nkz0-1); typically 8--16.
|
|
73
|
+
nbeta : int
|
|
74
|
+
Angular resolution for azimuthal averaging. Total angle count is nbeta + 1.
|
|
75
|
+
diameter : float
|
|
76
|
+
Wind turbine rotor diameter (m).
|
|
77
|
+
zhub : float
|
|
78
|
+
Wind turbine hub height (m).
|
|
79
|
+
z0 : float
|
|
80
|
+
Aerodynamic roughness length (m); defines vertical grid via s = log(z/z0).
|
|
81
|
+
zi : float
|
|
82
|
+
Atmospheric inversion height (m); upper domain boundary.
|
|
83
|
+
zlow : float
|
|
84
|
+
Lowest output height (m). If zlow == zhigh == zhub, only hub-height solution.
|
|
85
|
+
zhigh : float
|
|
86
|
+
Highest output height (m). If zlow == zhigh == zhub, only hub-height solution.
|
|
87
|
+
lut_vars : sequence of str, optional
|
|
88
|
+
Variable names to compute. Default is all 8: 'UL', 'UT', 'VL', 'VT', 'WL',
|
|
89
|
+
'WT', 'PL', 'PT' (L=longitudinal, T=transverse forcing).
|
|
90
|
+
nx : int, optional
|
|
91
|
+
Streamwise grid points, default 2048. Turbine at x = nx/4 * dx.
|
|
92
|
+
ny : int, optional
|
|
93
|
+
Cross-stream half-grid size, default 512.
|
|
94
|
+
dx : float or None, optional
|
|
95
|
+
Streamwise grid spacing (m). If None (default), set to diameter / 4.
|
|
96
|
+
dy : float or None, optional
|
|
97
|
+
Cross-stream grid spacing (m). If None (default), set to diameter / 16.
|
|
98
|
+
jit : bool, optional
|
|
99
|
+
Enable JIT compilation (Numba). Default is True.
|
|
100
|
+
n_cpu : int or None, optional
|
|
101
|
+
CPU cores for parallel computation. None uses all; 1 disables parallelisation.
|
|
102
|
+
|
|
103
|
+
Returns
|
|
104
|
+
-------
|
|
105
|
+
xarray.Dataset
|
|
106
|
+
Physical-space wake fields with dimensions (z, x, y).
|
|
107
|
+
|
|
108
|
+
See Also
|
|
109
|
+
--------
|
|
110
|
+
pyfuga.preluts_generator.PreLUTGenerator.make_prelut : First pipeline stage.
|
|
111
|
+
pyfuga.flut.FourierLUTGenerator.make_lut : Second pipeline stage.
|
|
112
|
+
pyfuga.trafalgar.Trafalgar.make_luts : Third pipeline stage.
|
|
113
|
+
"""
|
|
114
|
+
utils.compile(jit)
|
|
115
|
+
folder = Path(folder)
|
|
116
|
+
os.makedirs(folder, exist_ok=True)
|
|
117
|
+
dx = dx or diameter / 4
|
|
118
|
+
dy = dy or diameter / 16
|
|
119
|
+
|
|
120
|
+
preluts_id = f"Zeta0={zeta0:3.2e}_{nkz0}_{nbeta}"
|
|
121
|
+
|
|
122
|
+
L_vars = [v[0] for v in lut_vars if v[1] == "L"]
|
|
123
|
+
T_vars = [v[0] for v in lut_vars if v[1] == "T"]
|
|
124
|
+
lut_vars_id = ""
|
|
125
|
+
if L_vars:
|
|
126
|
+
lut_vars_id += f"_{''.join(L_vars)}L"
|
|
127
|
+
if T_vars:
|
|
128
|
+
lut_vars_id += f"_{''.join(T_vars)}T"
|
|
129
|
+
|
|
130
|
+
ds = 0.05
|
|
131
|
+
if zlow == zhigh == zhub:
|
|
132
|
+
low_level_out = high_level_out = 9999
|
|
133
|
+
z_id = f"z{zhub:.1f}"
|
|
134
|
+
else:
|
|
135
|
+
low_level_out = int(np.floor(np.log(zlow / z0) / ds))
|
|
136
|
+
high_level_out = int(np.ceil(np.log(zhigh / z0) / ds))
|
|
137
|
+
zlow = z0 * np.exp(low_level_out * ds)
|
|
138
|
+
zhigh = z0 * np.exp(high_level_out * ds)
|
|
139
|
+
z_id = f"z{zlow:.1f}-{zhigh:.1f}"
|
|
140
|
+
|
|
141
|
+
fluts_id = preluts_id + f"_D{diameter}_zhub{zhub}_zi{zi}_z0={z0:.8f}_{z_id}{lut_vars_id}"
|
|
142
|
+
|
|
143
|
+
luts_id = fluts_id + f"_nx{nx}_ny{ny}_dx{dx}_dy{dy}"
|
|
144
|
+
|
|
145
|
+
luts_path = folder / f"LUTs_{luts_id}.nc"
|
|
146
|
+
if not luts_path.exists():
|
|
147
|
+
# LUTs are missing (run Trafalgar)
|
|
148
|
+
fluts_path = folder / f"fLUTs_{fluts_id}.nc"
|
|
149
|
+
if not fluts_path.exists():
|
|
150
|
+
# FourierLUTs are missing (run lut)
|
|
151
|
+
|
|
152
|
+
preluts_path = folder / f"preLUTs_{preluts_id}.nc"
|
|
153
|
+
if not preluts_path.exists():
|
|
154
|
+
# Preluts are missing (run prelut)
|
|
155
|
+
preluts = PreLUTs.make_preluts(
|
|
156
|
+
zeta0=zeta0,
|
|
157
|
+
kz0_lst=get_kz0_lst(nkz0, 1e-9, 1e-1),
|
|
158
|
+
beta_lst=get_beta_lst(nbeta),
|
|
159
|
+
kzmax=300,
|
|
160
|
+
ds=ds,
|
|
161
|
+
accgoal=0.0001,
|
|
162
|
+
jit=jit,
|
|
163
|
+
n_cpu=n_cpu,
|
|
164
|
+
)
|
|
165
|
+
preluts.attrs["nkz0"] = nkz0
|
|
166
|
+
preluts.attrs["nbeta"] = nbeta
|
|
167
|
+
preluts.save(preluts_path)
|
|
168
|
+
else:
|
|
169
|
+
preluts = PreLUTs.from_netcdf(preluts_path)
|
|
170
|
+
|
|
171
|
+
# preluts loaded make fourier luts
|
|
172
|
+
flut_generator = FourierLUTGenerator(preluts, zhub, diameter, zi)
|
|
173
|
+
if low_level_out == high_level_out == 9999:
|
|
174
|
+
fluts = flut_generator.make_hubheight_luts(z0, lut_vars, n_cpu=n_cpu)
|
|
175
|
+
else:
|
|
176
|
+
fluts = flut_generator.make_lut(z0, low_level_out, high_level_out, lut_vars, n_cpu=n_cpu)
|
|
177
|
+
|
|
178
|
+
fluts.save(fluts_path)
|
|
179
|
+
else:
|
|
180
|
+
fluts = ComplexXRDataset.from_netcdf(fluts_path)
|
|
181
|
+
|
|
182
|
+
# fourier luts loaded make luts
|
|
183
|
+
luts = Trafalgar(fluts, nx, ny, dx, dy).make_luts(3)
|
|
184
|
+
luts.to_netcdf(luts_path)
|
|
185
|
+
else:
|
|
186
|
+
luts = xr.load_dataset(luts_path)
|
|
187
|
+
luts.attrs["name"] = luts_path.stem
|
|
188
|
+
return luts
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def main():
|
|
192
|
+
if __name__ == "__main__": # pragma: no cover
|
|
193
|
+
luts = get_luts(
|
|
194
|
+
folder=Path(__file__).parent.parent,
|
|
195
|
+
zeta0=0,
|
|
196
|
+
nkz0=16,
|
|
197
|
+
nbeta=32,
|
|
198
|
+
diameter=80,
|
|
199
|
+
zhub=70,
|
|
200
|
+
z0=0.00001,
|
|
201
|
+
zi=400,
|
|
202
|
+
zlow=70,
|
|
203
|
+
zhigh=70,
|
|
204
|
+
lut_vars=["UL"],
|
|
205
|
+
)
|
|
206
|
+
import matplotlib.pyplot as plt
|
|
207
|
+
|
|
208
|
+
luts.UL.sel(z=70)[502:542, :40].plot(x="x")
|
|
209
|
+
plt.axis("equal")
|
|
210
|
+
plt.figure()
|
|
211
|
+
luts = get_luts(
|
|
212
|
+
folder=Path(__file__).parent.parent,
|
|
213
|
+
zeta0=0,
|
|
214
|
+
nkz0=16,
|
|
215
|
+
nbeta=32,
|
|
216
|
+
diameter=80,
|
|
217
|
+
zhub=70,
|
|
218
|
+
z0=0.00001,
|
|
219
|
+
zi=400,
|
|
220
|
+
zlow=30,
|
|
221
|
+
zhigh=110,
|
|
222
|
+
lut_vars=["UL"],
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
luts.UL.sel(x=500)[:, :50].plot(x="y")
|
|
226
|
+
plt.axis("equal")
|
|
227
|
+
plt.show()
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
main()
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
r"""common.py
|
|
2
|
+
===========
|
|
3
|
+
Numerical helper functions for atmospheric stability relations in PyFuga.
|
|
4
|
+
|
|
5
|
+
This module provides JIT-accelerated utilities used by the preLUT and Fourier
|
|
6
|
+
LUT stages.
|
|
7
|
+
|
|
8
|
+
Key components:
|
|
9
|
+
|
|
10
|
+
- ``get_cdivkL(zeta0, kz0)``: Stability parameter :math:`c/(kL)`.
|
|
11
|
+
- ``get_phi_inverse(kz, cdivkL)``: Inverse of :math:`\phi_m` for unstable flow.
|
|
12
|
+
- ``get_psi(zeta0, kz, cdivkL)``: Stability correction function :math:`-\psi_m`.
|
|
13
|
+
- ``get_phi(zeta0, kz, cdivkL)``: Stability function :math:`\phi_m`.
|
|
14
|
+
- ``complex_norm(arr)``: Euclidean norm for complex arrays.
|
|
15
|
+
- ``get_new_h2(h, acc, Yerr, Y)``: Adaptive integration step-size update.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import numpy as np
|
|
19
|
+
|
|
20
|
+
from pyfuga.utils import jit
|
|
21
|
+
|
|
22
|
+
from .constants import CM_STABLE, CM_UNSTABLE
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@jit("double(double,double)")
|
|
26
|
+
def get_cdivkL(zeta0, kz0):
|
|
27
|
+
"""c/(k*L), c is a stability constant: depending on the stability (0, Cm1, Cm2)"""
|
|
28
|
+
if abs(zeta0) < 1e-10: # Neutral
|
|
29
|
+
return 0.0
|
|
30
|
+
else:
|
|
31
|
+
if zeta0 < 0: # Unstable
|
|
32
|
+
return zeta0 / kz0 * CM_UNSTABLE
|
|
33
|
+
else: # Stable
|
|
34
|
+
return zeta0 / kz0 * CM_STABLE
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@jit("double(double,double)")
|
|
38
|
+
def get_phi_inverse(kz, cdivkl):
|
|
39
|
+
"""
|
|
40
|
+
Inverse of stability function phi_m
|
|
41
|
+
for unstable conditions
|
|
42
|
+
phi_m=(1+Cm1*z/L)**(1/4)
|
|
43
|
+
"""
|
|
44
|
+
return (1.0 + cdivkl * kz) ** 0.25
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@jit("double(double,double,double)")
|
|
48
|
+
def get_psi(zeta0, kz, cdivkl):
|
|
49
|
+
"""Stability function -psi_m"""
|
|
50
|
+
if zeta0 < 0:
|
|
51
|
+
# Unstable: -psi_m=-ln(1/8*(1+phi_m**-2)*(1+phi_m**-1)**2)+2*atan(phi_m**-1)-pi/2
|
|
52
|
+
|
|
53
|
+
aux = get_phi_inverse(kz, cdivkl)
|
|
54
|
+
aux2 = (1.0 + aux) ** 2 * (1 + aux**2)
|
|
55
|
+
return np.log(8.0 / aux2) + 2.0 * np.arctan(cdivkl * kz / aux2)
|
|
56
|
+
else: # Stable: -psi_m=Cm2*z/L
|
|
57
|
+
return cdivkl * kz
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@jit("double(double,double,double)")
|
|
61
|
+
def get_phi(zeta0, kz, cdivkl):
|
|
62
|
+
"""Stability function phi_m"""
|
|
63
|
+
if zeta0 < 0: # Unstable: phi_m=(1+Cm1*z/L)**(-1/4)
|
|
64
|
+
return (1.0 + cdivkl * kz) ** (-0.25)
|
|
65
|
+
else: # Stable: phi_m=1+Cm2*z/L
|
|
66
|
+
return 1.0 + cdivkl * kz
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@jit("double[:](complex128[:,:],int32)")
|
|
70
|
+
def complex_norm(arr, axis=0):
|
|
71
|
+
"""Compute the norm of a complex array along the first axis"""
|
|
72
|
+
return np.sqrt((np.abs(arr) ** 2).sum(0))
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@jit("double(double,double,complex128[:,:],complex128[:,:])")
|
|
76
|
+
def get_new_h2(h, acc, Yerr, Y):
|
|
77
|
+
"""Get new step size based on error between integration estimates"""
|
|
78
|
+
err = np.max(complex_norm(Yerr, axis=0) / complex_norm(Y, axis=0))
|
|
79
|
+
return np.maximum(np.minimum(0.9 * h * (acc / err) ** (1 / 3), 4.0e-1), 1.0e-4)
|
|
80
|
+
|
|
81
|
+
# return np.clip(0.9 * h * (acc / err)**(1 / 3), 1.0E-4, 4.0E-1)
|
|
@@ -0,0 +1,32 @@
|
|
|
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 = 5.0 # MO Stability constant in phi_m (stable)
|
|
16
|
+
CM_UNSTABLE = -19.0 # MO Stability constant in phi_m (unstable) Hogstrom (1988)
|
|
17
|
+
N_EQ = 6 # ! Number of equations
|
|
18
|
+
KAPPA = 0.4 # Von Karman constant
|
|
19
|
+
KAPPA_SQUARED = 0.16 # Square of kappa
|
|
20
|
+
P_SCALE = 1 # Pressure normalisation variable
|
|
21
|
+
MAX_NODES = 400
|
|
22
|
+
Y_NORM_THRESHOLD = 100 # Max value of Ynorm before a sublevel is made
|
|
23
|
+
|
|
24
|
+
# LUT constant
|
|
25
|
+
FIRST_Z_OUTPUT = 1.0 # First height level in output
|
|
26
|
+
|
|
27
|
+
# Other constants
|
|
28
|
+
UVW_LT = ["UL", "UT", "VL", "VT", "WL", "WT", "PL", "PT"]
|
|
29
|
+
|
|
30
|
+
# Coordinate system indicators
|
|
31
|
+
COORD_T = 1 # t = u0 * KAPPA
|
|
32
|
+
COORD_S = 2 # s = kz
|