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.
Files changed (39) hide show
  1. pyfuga-0.1.0/LICENSE +21 -0
  2. pyfuga-0.1.0/PKG-INFO +124 -0
  3. pyfuga-0.1.0/README.md +63 -0
  4. pyfuga-0.1.0/pyfuga/__init__.py +20 -0
  5. pyfuga-0.1.0/pyfuga/_fuga.py +230 -0
  6. pyfuga-0.1.0/pyfuga/common.py +81 -0
  7. pyfuga-0.1.0/pyfuga/constants.py +32 -0
  8. pyfuga-0.1.0/pyfuga/file_readers.py +141 -0
  9. pyfuga-0.1.0/pyfuga/flut.py +477 -0
  10. pyfuga-0.1.0/pyfuga/preludium_eq_routines.py +90 -0
  11. pyfuga-0.1.0/pyfuga/preluts.py +217 -0
  12. pyfuga-0.1.0/pyfuga/preluts_generator/__init__.py +10 -0
  13. pyfuga-0.1.0/pyfuga/preluts_generator/generator.py +340 -0
  14. pyfuga-0.1.0/pyfuga/preluts_generator/integration.py +359 -0
  15. pyfuga-0.1.0/pyfuga/preluts_generator/matrices.py +182 -0
  16. pyfuga-0.1.0/pyfuga/preluts_generator/nodes.py +122 -0
  17. pyfuga-0.1.0/pyfuga/preluts_generator/transforms.py +123 -0
  18. pyfuga-0.1.0/pyfuga/profiling.py +86 -0
  19. pyfuga-0.1.0/pyfuga/trafalgar.py +525 -0
  20. pyfuga-0.1.0/pyfuga/typing.py +9 -0
  21. pyfuga-0.1.0/pyfuga/utils.py +228 -0
  22. pyfuga-0.1.0/pyfuga/z0.py +143 -0
  23. pyfuga-0.1.0/pyfuga.egg-info/PKG-INFO +124 -0
  24. pyfuga-0.1.0/pyfuga.egg-info/SOURCES.txt +37 -0
  25. pyfuga-0.1.0/pyfuga.egg-info/dependency_links.txt +1 -0
  26. pyfuga-0.1.0/pyfuga.egg-info/requires.txt +39 -0
  27. pyfuga-0.1.0/pyfuga.egg-info/top_level.txt +1 -0
  28. pyfuga-0.1.0/pyproject.toml +221 -0
  29. pyfuga-0.1.0/setup.cfg +4 -0
  30. pyfuga-0.1.0/tests/test_api.py +102 -0
  31. pyfuga-0.1.0/tests/test_flut.py +312 -0
  32. pyfuga-0.1.0/tests/test_jit_signatures.py +222 -0
  33. pyfuga-0.1.0/tests/test_preluts.py +396 -0
  34. pyfuga-0.1.0/tests/test_preluts_jit.py +273 -0
  35. pyfuga-0.1.0/tests/test_readers.py +117 -0
  36. pyfuga-0.1.0/tests/test_trafalgar.py +55 -0
  37. pyfuga-0.1.0/tests/test_typing.py +27 -0
  38. pyfuga-0.1.0/tests/test_utils.py +19 -0
  39. 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
+ [![pipeline status](https://gitlab.windenergy.dtu.dk/TOPFARM/PyFuga/badges/main/pipeline.svg)](https://gitlab.windenergy.dtu.dk/TOPFARM/PyFuga/-/commits/main)
71
+ [![coverage report](https://gitlab.windenergy.dtu.dk/TOPFARM/PyFuga/badges/main/coverage.svg)](https://gitlab.windenergy.dtu.dk/TOPFARM/PyFuga/-/commits/main)
72
+ [![documentation](https://img.shields.io/badge/docs-latest-brightgreen.svg)](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
+ [![pipeline status](https://gitlab.windenergy.dtu.dk/TOPFARM/PyFuga/badges/main/pipeline.svg)](https://gitlab.windenergy.dtu.dk/TOPFARM/PyFuga/-/commits/main)
10
+ [![coverage report](https://gitlab.windenergy.dtu.dk/TOPFARM/PyFuga/badges/main/coverage.svg)](https://gitlab.windenergy.dtu.dk/TOPFARM/PyFuga/-/commits/main)
11
+ [![documentation](https://img.shields.io/badge/docs-latest-brightgreen.svg)](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