Skip to content

Commit 08ad421

Browse files
committed
Draft of lowfreq model in package now
1 parent 0184a10 commit 08ad421

File tree

7 files changed

+299
-30
lines changed

7 files changed

+299
-30
lines changed

.github/workflows/lint.yml

+8-3
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,13 @@ jobs:
77
runs-on: ubuntu-latest
88
steps:
99
- uses: actions/checkout@v4
10-
- uses: psf/black@stable
10+
11+
- uses: chartboost/ruff-action@v1
12+
with:
13+
src: "./drdmannturb"
14+
args: "--fix"
15+
16+
- uses: jpetrucciani/mypy-check@master
1117
with:
12-
# options: "--check --verbose"
1318
src: "./drdmannturb"
14-
version: "~= 22.3.0"
19+
args: "--ignore-missing-imports"

.gitignore

+10-8
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
1-
# csv data
1+
# csv data
22
*.csv
33

4-
# model pickles
5-
*.pkl
4+
# model pickles
5+
*.pkl
66

77
# overrides for data used in examples
8-
# necessary for interpolation data example
9-
!/docs/source/data/*.csv
10-
# necessary for model saving, loading, and viz examples
8+
# necessary for interpolation data example
9+
!/docs/source/data/*.csv
10+
# necessary for model saving, loading, and viz examples
1111
!/docs/source/results/*.pkl
1212

1313
docs/source/auto_examples
1414

1515
examples/runs/*
1616

17-
# runtimes for sphinx-gallery
17+
# runtimes for sphinx-gallery
1818
docs/source/sg_execution_times.rst
1919

2020
test/io/runs
@@ -71,6 +71,7 @@ coverage.xml
7171
*.py,cover
7272
.hypothesis/
7373
.pytest_cache/
74+
.ruff_cache/
7475

7576
# Translations
7677
*.mo
@@ -152,6 +153,7 @@ dmypy.json
152153
# Others
153154
**.vscode
154155
**.DS_Store
156+
examples-unrendered/2d_plus_3d.ipynb
155157

156158
## Data/ Paraview Files
157159

@@ -162,4 +164,4 @@ runs/
162164

163165
## Open Journals compilation intermediates and results
164166
paper/jats
165-
paper/paper.pdf
167+
paper/paper.pdf
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
"""
2+
This module implements the Syed-Mann (2024) low-frequency wind fluctuation model.
3+
"""
4+
5+
from typing import Optional
6+
7+
import numpy as np
8+
from scipy import integrate
9+
10+
11+
def _compute_kappa(k1: float, k2: float, psi: float) -> float:
12+
"""
13+
Subroutine to compute the horizontal wavevector :math:`\kappa`, defined by
14+
15+
.. math::
16+
\kappa = \sqrt{2(k_1^2 \cos^2(\psi) + k_2^2 \sin^2(\psi))}
17+
18+
Parameters
19+
----------
20+
k1 : float
21+
Wavenumber k1
22+
23+
k2 : float
24+
Wavenumber k2
25+
26+
psi : float
27+
"Anisotropy parameter" angle :math:`\psi`, in radians
28+
29+
Returns
30+
-------
31+
float
32+
Computed kappa value
33+
"""
34+
35+
return np.sqrt(2.0 * ((k1**2) * np.cos(psi) ** 2 + (k2**2) * np.sin(psi) ** 2))
36+
37+
38+
def _compute_E(kappa: float, c: float, L2D: float, z_i: float) -> float:
39+
"""
40+
Subroutine to compute the energy spectrum :math:`E(\kappa)` with the attenuation factor,
41+
defined by
42+
43+
.. math::
44+
E(\kappa) = \frac{c \kappa^3}{(L_{2\textrm{D}}^{-2} + \kappa^2)^{7/3}} \cdot
45+
\frac{1}{1 + \kappa^2 z_i^2}
46+
47+
Parameters
48+
----------
49+
kappa : float
50+
Replacement "wavenumber" :math:`\kappa`
51+
52+
c : float
53+
Scaling factor :math:`c` used to correct the variance
54+
55+
L2D : float
56+
Length scale :math:`L_{2\textrm{D}}`
57+
"""
58+
if np.isclose(kappa, 0.0):
59+
return 0.0
60+
61+
denom = (1.0 / (L2D**2) + kappa**2) ** (7.0 / 3.0)
62+
atten = 1.0 / (1.0 + (kappa * z_i) ** 2)
63+
return c * (kappa**3) / denom * atten
64+
65+
66+
def _estimate_c(sigma2: float, L2D: float, z_i: float) -> float:
67+
"""
68+
Subroutine to estimate the scaling factor :math:`c` from the target variance :math:`\sigma^2`.
69+
70+
This is achieved by approximating the integral of :math:`E(\kappa)` from :math:`\kappa=0` to
71+
:math:`\infty` by quadrature, since
72+
.. math::
73+
\int_0^\infty E(\kappa)
74+
= c \int_0^\infty \frac{\kappa^3}{(L_{2\textrm{D}}^{-2} + \kappa^2)^{7/3}} \cdot
75+
\frac{1}{1 + \kappa^2 z_i^2}
76+
= \sigma^2
77+
78+
Parameters
79+
----------
80+
sigma2 : float
81+
Target variance :math:`\sigma^2`
82+
83+
L2D : float
84+
Length scale :math:`L_{2\textrm{D}}`
85+
86+
z_i : float
87+
Height :math:`z_i`
88+
"""
89+
90+
def integrand(kappa: float) -> float:
91+
return kappa**3 / ((1.0 / (L2D**2) + kappa**2) ** (7.0 / 3.0)) * (1.0 / (1.0 + (kappa * z_i) ** 2))
92+
93+
val, err = integrate.quad(integrand, 0, np.inf)
94+
95+
return sigma2 / val
96+
97+
98+
def generate_2D_lowfreq(
99+
Nx: int,
100+
Ny: int,
101+
L1: float,
102+
L2: float,
103+
psi_degs: float,
104+
sigma2: float,
105+
L2D: float,
106+
z_i: float,
107+
c: Optional[float] = None,
108+
) -> np.ndarray:
109+
"""
110+
Generates the 2D low-frequency wind fluctuation component of the Syed-Mann (2024) 2D+3D model.
111+
112+
Parameters
113+
----------
114+
Nx : int
115+
Number of grid points in the x-direction
116+
Ny : int
117+
Number of grid points in the y-direction
118+
L1 : float
119+
Length of the domain in the x-direction
120+
L2 : float
121+
Length of the domain in the y-direction
122+
psi_degs : float
123+
"Anisotropy parameter" angle :math:`\psi`, in degrees
124+
sigma2 : float
125+
Target variance :math:`\sigma^2`
126+
L2D : float
127+
Length scale :math:`L_{2\textrm{D}}`
128+
z_i : float
129+
Height :math:`z_i`
130+
c : float
131+
Scaling factor :math:`c` to use for the energy spectrum. If not provided, it is
132+
estimated by quadrature from the provided target variance :math:`\sigma^2`.
133+
134+
Returns
135+
-------
136+
np.ndarray
137+
Generated 2D low-frequency wind fluctuation component. This is `Nx` by `Ny` by 2,
138+
where the third dimension is the u- (longitudinal) and v-components (transverse).
139+
140+
TODO ^^
141+
"""
142+
143+
assert 0 < psi_degs and psi_degs < 90, "Anisotropy parameter psi_degs must be between 0 and 90 degrees"
144+
145+
psi = np.deg2rad(psi_degs)
146+
147+
if c is None:
148+
c = _estimate_c(sigma2, L2D, z_i)
149+
150+
dx = L1 / Nx
151+
dy = L2 / Ny
152+
153+
kx_arr = 2.0 * np.pi * np.fft.fftfreq(Nx, d=dx)
154+
ky_arr = 2.0 * np.pi * np.fft.fftfreq(Ny, d=dy)
155+
kx_arr = np.fft.fftshift(kx_arr)
156+
ky_arr = np.fft.fftshift(ky_arr)
157+
158+
amp2 = np.zeros((Nx, Ny), dtype=np.float64)
159+
160+
factor_16 = (2.0 * np.pi**2) / L1
161+
162+
for ix in range(Nx):
163+
for iy in range(Ny):
164+
kx = kx_arr[ix]
165+
ky = ky_arr[iy]
166+
167+
kappa = _compute_kappa(kx, ky, psi)
168+
E_val = _compute_E(kappa, c, L2D, z_i)
169+
170+
if kappa < 1e-12:
171+
phi_11 = 0.0
172+
else:
173+
phi_11 = E_val / (np.pi * kappa)
174+
175+
amp2[ix, iy] = factor_16 * phi_11
176+
177+
Uhat = np.zeros((Nx, Ny), dtype=np.complex128)
178+
179+
for ix in range(Nx):
180+
for iy in range(Ny):
181+
amp = np.sqrt(amp2[ix, iy])
182+
phase = (np.random.normal() + 1j * np.random.normal()) / np.sqrt(2.0)
183+
Uhat[ix, iy] = amp * phase
184+
185+
Uhat_unshift = np.fft.ifftshift(Uhat, axes=(0, 1))
186+
u_field_complex = np.fft.ifft2(Uhat_unshift, s=(Nx, Ny))
187+
u_field = np.real(u_field_complex)
188+
189+
var_now = np.var(u_field)
190+
if var_now > 1e-12:
191+
u_field *= np.sqrt(sigma2 / var_now)
192+
193+
return u_field
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import numpy as np
2+
from low_freq_prototype import generate_2D_lowfreq_approx
3+
4+
from drdmannturb.fluctuation_generation import GenerateFluctuationField
5+
6+
# DRDMT 3D turbulence parameters.
7+
Type_Model = "Mann"
8+
9+
z0 = 0.02
10+
zref = 90
11+
uref = 11.4
12+
ustar = uref * 0.41 / np.log(zref / z0)
13+
plexp = 0.2
14+
windprofiletype = "LOG"
15+
16+
L_3D = 50
17+
Gamma = 2.5
18+
sigma = 0.01
19+
20+
Lx = 60_000 # [m] = 60 km
21+
Ly = 15_000 # [m] = 15 km
22+
Lz = 5_000 # [m]
23+
24+
grid_dimensions = np.array([Lx, Ly, Lz])
25+
grid_levels = np.array([6, 4, 4])
26+
27+
nBlocks = 1
28+
29+
seed = None
30+
31+
# Generate 3d Mann box
32+
gen_mann = GenerateFluctuationField(
33+
ustar,
34+
zref,
35+
grid_dimensions,
36+
grid_levels,
37+
length_scale=L_3D,
38+
time_scale=Gamma,
39+
energy_spectrum_scale=sigma,
40+
model=Type_Model,
41+
seed=seed,
42+
)
43+
44+
fluctuation_field = gen_mann.generate_fluctuation_field()
45+
46+
spacing = tuple(grid_dimensions / (2.0**grid_levels + 1))
47+
48+
49+
# 2D field parameters
50+
L_2D = 15_000.0
51+
sigma2 = 0.6
52+
z_i = 500.0
53+
psi_degs = 43.0
54+
55+
L1, L2 = grid_dimensions[:2]
56+
Nx, Ny = 2 ** grid_levels[:2]
57+
58+
_, _, u_field = generate_2D_lowfreq_approx(Nx, Ny, L1, L2, psi_degs, sigma2, L_2D, z_i)
59+
_, _, v_field = generate_2D_lowfreq_approx(Nx, Ny, L1, L2, psi_degs, sigma2, L_2D, z_i)

examples-unrendered/low_freq_prototype.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ def compute_kappa(kx, ky, psi):
77
cos2 = np.cos(psi) ** 2
88
sin2 = np.sin(psi) ** 2
99

10-
return np.sqrt(2.0 * (kx**2) * cos2 + (ky**2) * sin2)
10+
return np.sqrt(2.0 * ((kx**2) * cos2 + (ky**2) * sin2))
1111

1212

1313
def compute_E(kappa, c, L2D, z_i):
@@ -88,7 +88,7 @@ def generate_2D_lowfreq_approx(Nx, Ny, L1, L2, psi_degs, sigma2, L2D, z_i):
8888
if var_now > 1e-12:
8989
u_field *= np.sqrt(sigma2 / var_now)
9090

91-
return u_field
91+
return np.linspace(0, L1, Nx), np.linspace(0, L2, Ny), u_field
9292

9393

9494
if __name__ == "__main__":

examples/08_mann_box_generation_IEC.py

+6-17
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,6 @@
2727
import numpy as np
2828
import torch
2929

30-
from drdmannturb.fluctuation_generation import (
31-
plot_velocity_components, # utility function for plotting each velocity component in the field, not used in this example
32-
)
3330
from drdmannturb.fluctuation_generation import (
3431
GenerateFluctuationField,
3532
plot_velocity_magnitude,
@@ -97,9 +94,7 @@
9794
seed=seed,
9895
)
9996

100-
fluctuation_field_mann = gen_mann.generate(
101-
nBlocks, zref, uref, z0, windprofiletype, plexp
102-
)
97+
fluctuation_field_mann = gen_mann.generate(nBlocks, zref, uref, z0, windprofiletype, plexp)
10398

10499
#######################################################################################
105100
# Adding the mean velocity profile
@@ -113,13 +108,11 @@
113108

114109
spacing = tuple(grid_dimensions / (2.0**grid_levels + 1))
115110

116-
fig_magnitude_mann = plot_velocity_magnitude(
117-
spacing, fluctuation_field_mann, transparent=True
118-
)
111+
fig_magnitude_mann = plot_velocity_magnitude(spacing, fluctuation_field_mann, transparent=True)
119112

120-
# this is a Plotly figure, which can be visualized with the ``.show()`` method in different contexts. While these utilities
121-
# may be useful for quick visualization, we recommend using Paraview to visualize higher resolution output. We will cover
122-
# saving to a portable VTK format further in this example.
113+
# this is a Plotly figure, which can be visualized with the ``.show()`` method in different contexts. While these
114+
# utilities may be useful for quick visualization, we recommend using Paraview to visualize higher resolution output.
115+
# We will cover saving to a portable VTK format further in this example.
123116

124117
fig_magnitude_mann # .show("browser"), or for specific browser, use .show("firefox")
125118

@@ -141,10 +134,6 @@
141134
# -----------------------------------------
142135
# For higher resolution fluctuation fields, we suggest using Paraview. To transfer the generated data
143136
# from our package, we provide the ``.save_to_vtk()`` method.
144-
filename = str(
145-
path / "./outputs/IEC_simple"
146-
if path.name == "examples"
147-
else path / "./outputs/IEC_simple"
148-
)
137+
filename = str(path / "./outputs/IEC_simple" if path.name == "examples" else path / "./outputs/IEC_simple")
149138

150139
gen_mann.save_to_vtk(filename)

0 commit comments

Comments
 (0)