pyfuga 0.1.0__py3-none-any.whl
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/__init__.py +20 -0
- pyfuga/_fuga.py +230 -0
- pyfuga/common.py +81 -0
- pyfuga/constants.py +32 -0
- pyfuga/file_readers.py +141 -0
- pyfuga/flut.py +477 -0
- pyfuga/preludium_eq_routines.py +90 -0
- pyfuga/preluts.py +217 -0
- pyfuga/preluts_generator/__init__.py +10 -0
- pyfuga/preluts_generator/generator.py +340 -0
- pyfuga/preluts_generator/integration.py +359 -0
- pyfuga/preluts_generator/matrices.py +182 -0
- pyfuga/preluts_generator/nodes.py +122 -0
- pyfuga/preluts_generator/transforms.py +123 -0
- pyfuga/profiling.py +86 -0
- pyfuga/trafalgar.py +525 -0
- pyfuga/typing.py +9 -0
- pyfuga/utils.py +228 -0
- pyfuga/z0.py +143 -0
- pyfuga-0.1.0.dist-info/METADATA +124 -0
- pyfuga-0.1.0.dist-info/RECORD +24 -0
- pyfuga-0.1.0.dist-info/WHEEL +5 -0
- pyfuga-0.1.0.dist-info/licenses/LICENSE +21 -0
- pyfuga-0.1.0.dist-info/top_level.txt +1 -0
pyfuga/__init__.py
ADDED
|
@@ -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)
|
pyfuga/_fuga.py
ADDED
|
@@ -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()
|
pyfuga/common.py
ADDED
|
@@ -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)
|
pyfuga/constants.py
ADDED
|
@@ -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
|
pyfuga/file_readers.py
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""file_readers.py
|
|
2
|
+
================
|
|
3
|
+
Read Fortran binary output files from the original FUGA code.
|
|
4
|
+
|
|
5
|
+
This module provides readers for legacy .bin output files containing
|
|
6
|
+
parameters, case data, and preLUT tables saved by the Fortran FUGA implementation.
|
|
7
|
+
Used for initialising preLUTs from existing FUGA runs and for compatibility
|
|
8
|
+
with legacy simulation data.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import struct
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
import numpy as np
|
|
16
|
+
import xarray as xr
|
|
17
|
+
from numpy import newaxis as na
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class BinaryReader:
|
|
21
|
+
def s(self, n):
|
|
22
|
+
return self.fid.read(n).decode().strip()
|
|
23
|
+
|
|
24
|
+
def i(self):
|
|
25
|
+
return struct.unpack("i", self.fid.read(4))[0]
|
|
26
|
+
|
|
27
|
+
def d(self):
|
|
28
|
+
return struct.unpack("d", self.fid.read(8))[0]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Parameters(BinaryReader):
|
|
32
|
+
def __init__(self, folder):
|
|
33
|
+
i, d, s = self.i, self.d, self.s
|
|
34
|
+
with open(Path(folder) / "parameters.bin", "rb") as self.fid:
|
|
35
|
+
self.prelutname = s(127)
|
|
36
|
+
self.closure = i()
|
|
37
|
+
self.kz0min = d()
|
|
38
|
+
self.kz0max = d()
|
|
39
|
+
self.nkz0 = i()
|
|
40
|
+
self.jmin = i()
|
|
41
|
+
self.jmax = i()
|
|
42
|
+
self.nbeta = i()
|
|
43
|
+
self.mbeta = i()
|
|
44
|
+
self.dummy2 = d()
|
|
45
|
+
self.ds = d()
|
|
46
|
+
self.kzmax = d()
|
|
47
|
+
self.accgoal = d()
|
|
48
|
+
self.beta_lst = np.fromfile(self.fid, float, self.nbeta + self.mbeta + 1)
|
|
49
|
+
self.kz0_lst = np.fromfile(self.fid, float, self.jmax - self.jmin + 1)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class CaseData(BinaryReader):
|
|
53
|
+
def __init__(self, folder):
|
|
54
|
+
i, d, s = self.i, self.d, self.s
|
|
55
|
+
|
|
56
|
+
with open(Path(folder) / "CaseData.bin", "rb") as self.fid:
|
|
57
|
+
self.case_name = s(127)
|
|
58
|
+
self.radius = d()
|
|
59
|
+
self.zhub = d()
|
|
60
|
+
self.low_level_out = i()
|
|
61
|
+
self.high_level_out = i()
|
|
62
|
+
self.z0 = d()
|
|
63
|
+
self.zi = d()
|
|
64
|
+
self.ds = d()
|
|
65
|
+
self.closure = i()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def eof(fid):
|
|
69
|
+
fid.seek(-1, 2) # go to the file end.
|
|
70
|
+
eof = fid.tell() + 1 # get the end of file location
|
|
71
|
+
fid.seek(0, 0) # go back to file beginning
|
|
72
|
+
return eof
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def read_prelut_list(folder, dict=True):
|
|
76
|
+
|
|
77
|
+
folder = Path(folder)
|
|
78
|
+
with open(folder / "prelut_list.lst", "rb") as fid:
|
|
79
|
+
fid.seek(18)
|
|
80
|
+
n = 24 if fid.read(6) == b"\x00\x00\x00\x00\x00\x00" else 18
|
|
81
|
+
|
|
82
|
+
N = eof(fid) // (n + 48)
|
|
83
|
+
|
|
84
|
+
# list of filename (18 or 18+6xNull), ds, smaxx, kz0, beta, kzmax, accgoal
|
|
85
|
+
if dict:
|
|
86
|
+
return {fid.read(n)[:18].decode(): struct.unpack("d" * 6, fid.read(8 * 6)) for _ in range(N)}
|
|
87
|
+
|
|
88
|
+
else:
|
|
89
|
+
return [(fid.read(n)[:18].decode(),) + struct.unpack("d" * 6, fid.read(8 * 6)) for _ in range(N)]
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def read_pre_file(filename):
|
|
93
|
+
with open(filename, "rb") as fid:
|
|
94
|
+
d = np.fromfile(fid, float, -1).reshape((-1, 291))
|
|
95
|
+
Yleft, Rleft, Rright, dy = np.moveaxis((d[:, :288:2] + d[:, 1:288:2] * 1j).reshape((-1, 4, 6, 6)), 1, 0)
|
|
96
|
+
sleft, sright = d[:, 288:290].T
|
|
97
|
+
fid.seek(0, 0)
|
|
98
|
+
level = np.fromfile(fid, np.int32, -1).reshape((-1, 291 * 2))[:, -2]
|
|
99
|
+
return {
|
|
100
|
+
"Yleft": (["i", "j", "k"], Yleft),
|
|
101
|
+
"Rleft": (["i", "j", "k"], Rleft),
|
|
102
|
+
"Rright": (["i", "j", "k"], Rright),
|
|
103
|
+
"dyxu0": (["i", "j"], dy[:, 0]),
|
|
104
|
+
"dyxu1": (["i", "j"], dy[:, 1]),
|
|
105
|
+
"dyxv0": (["i", "j"], dy[:, 2]),
|
|
106
|
+
"dyxv1": (["i", "j"], dy[:, 3]),
|
|
107
|
+
"dyxw0": (["i", "j"], dy[:, 4]),
|
|
108
|
+
"dyxw1": (["i", "j"], dy[:, 5]),
|
|
109
|
+
"sleft": (["i"], sleft),
|
|
110
|
+
"sright": (["i"], sright),
|
|
111
|
+
"level": (["i"], level),
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def read_lut_file(filename, prelut_folder):
|
|
116
|
+
parameters = Parameters(prelut_folder)
|
|
117
|
+
casedata = CaseData(os.path.dirname(filename))
|
|
118
|
+
var = os.path.basename(filename)[:2]
|
|
119
|
+
level = int(filename[-8:-4])
|
|
120
|
+
z = casedata.zhub if level == 9999 else casedata.z0 * np.exp(casedata.ds * level)
|
|
121
|
+
|
|
122
|
+
lut = np.fromfile(filename, complex, -1).reshape((len(parameters.kz0_lst) + 1, len(parameters.beta_lst)))
|
|
123
|
+
lut = lut[1:].T
|
|
124
|
+
return xr.Dataset(
|
|
125
|
+
{
|
|
126
|
+
var: (("beta", "kz0", "level"), lut[:, :, na]),
|
|
127
|
+
"z": (("level"), [z]),
|
|
128
|
+
"diameter": casedata.radius * 2,
|
|
129
|
+
"hubheight": casedata.zhub,
|
|
130
|
+
"z0": casedata.z0,
|
|
131
|
+
},
|
|
132
|
+
coords={"kz0": parameters.kz0_lst, "beta": parameters.beta_lst, "level": [level]},
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def read_lut_dat_file(filename, nx, ny, dx, dy):
|
|
137
|
+
dat = np.fromfile(filename, np.dtype("<f"), -1).astype(float).reshape((ny // 2, nx))
|
|
138
|
+
x_lst = np.arange(-nx // 4, nx * 3 / 4) * dx # rotor is located 1/4 downstream
|
|
139
|
+
|
|
140
|
+
y_lst = np.arange(ny // 2) * dy
|
|
141
|
+
return xr.DataArray(dat, dims=("y", "x"), coords={"x": x_lst, "y": y_lst})
|