Skip to content

Commit cfb75d9

Browse files
committed
Beginning of fig 2a validation and fixed api
1 parent 34038fe commit cfb75d9

5 files changed

+471
-70
lines changed

examples-unrendered/2d_plus_3d_prototype.py

+74-10
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import numpy as np
2+
import scipy.special as sp
23
from low_freq_prototype import LowFreq2DFieldGenerator
34

45
from drdmannturb import FluctuationFieldGenerator
@@ -69,7 +70,7 @@
6970

7071
## New API
7172

72-
generator = LowFreq2DFieldGenerator(grid_dimensions, grid_levels, L2D=L_2D, sigma2=sigma2, z_i=z_i, psi_degs=psi_degs)
73+
generator = LowFreq2DFieldGenerator(grid_dimensions, grid_levels, L_2D=L_2D, sigma2=sigma2, z_i=z_i, psi_degs=psi_degs)
7374

7475
_, _, u_field = generator.generate()
7576
_, _, v_field = generator.generate()
@@ -78,29 +79,92 @@
7879
fluctuation_field_2d3d[:, :, :, 0] += u_field[:, :, np.newaxis]
7980
fluctuation_field_2d3d[:, :, :, 1] += v_field[:, :, np.newaxis]
8081

82+
#########################################################################################
83+
# Test Mean, Std, Dev, Skewness, and Kurtosis
84+
8185

8286
#########################################################################################
8387
## TESTS
8488
# Helper functions for tests
8589

8690

87-
def analytic_F11_2D(k_1):
88-
# first_num = (sp.gamma(11/6) * (L_2D**(11/3)))
91+
def analytic_F11_2D(k1):
92+
"""
93+
Analytic solution for F11(k1) in 2d.
94+
"""
95+
a = 1 + 2 * (k1 * generator.L_2D * np.cos(generator.psi_rad)) ** 2
96+
b = 1 + 2 * (k1 * generator.z_i * np.cos(generator.psi_rad)) ** 2
97+
p = (L_2D**2 * b) / (z_i**2 * a)
8998

90-
pass
99+
d = 1.0
91100

101+
first_term_numerator = (sp.gamma(11 / 6) * (L_2D ** (11 / 3))) * (
102+
-p * sp.hyp2f1(5 / 6, 1, 1 / 2, p) - 7 * sp.hyp2f1(5 / 6) + 2 * sp.hyp2f1(-1 / 6, 1, 1 / 2, p)
103+
)
104+
first_term_denominator = (2 * np.pi) ** (11 / 3) * (a ** (11 / 6))
92105

93-
def analytic_F22_2D(k_1):
94-
pass
106+
second_term_numerator = L_2D ** (14 / 3) * np.sqrt(b)
107+
second_term_denominator = 2 * np.sqrt(2) * d ** (7 / 3) * (generator.z_i * np.sin(generator.psi_rad))
95108

109+
return generator.c * (
110+
(first_term_numerator / first_term_denominator) + (second_term_numerator / second_term_denominator)
111+
)
112+
113+
114+
def analytic_F22_2D(k1):
115+
a = 1 + 2 * (k1 * generator.L_2D * np.cos(generator.psi_rad)) ** 2
116+
b = 1 + 2 * (k1 * generator.z_i * np.cos(generator.psi_rad)) ** 2
117+
p = (L_2D**2 * b) / (z_i**2 * a)
118+
119+
d = 1.0
120+
121+
leading_factor_num = generator.z_i**4 * a ** (1 / 6) ** generator.L_2D * sp.gamma(17 / 6)
122+
leading_factor_denom = (
123+
55
124+
* np.sqrt(2 * np.pi)
125+
* (generator.L_2D**2 - generator.z_i**2) ** 2
126+
* b
127+
* sp.gamma(7 / 3)
128+
* np.sin(generator.psi_rad)
129+
)
130+
leading_factor = -leading_factor_num / leading_factor_denom
131+
132+
line_1 = -9 - 25 * sp.hyp2f1(-1 / 6, 1, 1 / 2, p)
133+
line_2 = p**2 * (15 - 30 * sp.hyp2f1(-1 / 6, 1, 1 / 2, p) - 59 * sp.hyp2f1(5 / 6, 1, 1 / 2, p))
134+
line_3 = 35 * sp.hyp2f1(5 / 6, 1, 1 / 2, p) + 15 * p**3 * sp.hyp2f1(5 / 6, 1, 1 / 2, p)
135+
line_4 = p * (-54 + 88 * sp.hyp2f1(-1 / 6, 1, 1 / 2, p) + 9 * sp.hyp2f1(5 / 6, 1, 1 / 2, p))
136+
137+
term_1 = leading_factor * (line_1 + line_2 + line_3 + line_4)
138+
139+
term_2 = (L_2D ** (14 / 3)) / (np.sqrt(2 * b) * d ** (7 / 3) * z_i * np.sin(generator.psi_rad))
140+
141+
paren = term_1 - term_2
142+
143+
return generator.c * k1**2 * paren
144+
145+
146+
def estimate_spectra_2d(padded_u_field, padded_v_field) -> tuple[np.ndarray, np.ndarray]:
147+
"""
148+
Estimate the spectra of the 2d fields.
149+
"""
150+
151+
u_fft = np.fft.fft2(padded_u_field)
152+
v_fft = np.fft.fft2(padded_v_field)
153+
154+
F_11 = np.abs(u_fft) ** 2
155+
F_22 = np.abs(v_fft) ** 2
156+
157+
N = padded_u_field.shape[0]
158+
F_11 = F_11 / N
159+
F_22 = F_22 / N
160+
161+
F_11
162+
163+
return np.zeros(N), np.zeros(N)
96164

97-
#########################################################################################
98-
# Test Mean, Std, Dev, Skewness, and Kurtosis
99165

100166
#########################################################################################
101167
# Test energy spectrum
102-
#
103-
# ie,
104168

105169

106170
#########################################################################################

examples-unrendered/low_freq_prototype.py

+64-60
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,16 @@
77
class LowFreq2DFieldGenerator:
88
def __init__(
99
self,
10-
grid_dimensions,
11-
grid_levels,
12-
L2D=15_000.0,
13-
sigma2=0.6,
14-
z_i=500.0,
15-
psi_degs=43.0,
10+
grid_dimensions: np.ndarray,
11+
grid_levels: np.ndarray,
12+
L_2D: float = 15_000.0,
13+
sigma2: float = 0.6,
14+
z_i: float = 500.0,
15+
psi_degs: float = 43.0,
1616
c: Optional[float] = None,
1717
):
1818
# Field parameters
19-
self.L2D = L2D
19+
self.L_2D = L_2D
2020
self.sigma2 = sigma2
2121
self.z_i = z_i
2222
self.psi_degs = psi_degs
@@ -25,11 +25,10 @@ def __init__(
2525
self.L1, self.L2 = grid_dimensions[:2] # Default was 60k x 15k
2626
self.Nx, self.Ny = 2 ** grid_levels[:2] # Default was 1024 x 256
2727

28-
self.c = self._solve_for_c(sigma2, L2D, z_i)
2928
self.psi_rad = np.deg2rad(self.psi_degs)
3029

3130
if c is None:
32-
self.c = self._solve_for_c(self.sigma2, self.L2D, self.z_i)
31+
self.c = self._solve_for_c()
3332
else:
3433
self.c = c
3534

@@ -42,37 +41,33 @@ def _compute_kappa(self, kx, ky):
4241

4342
return np.sqrt(2.0 * ((kx**2) * cos2 + (ky**2) * sin2))
4443

45-
def _compute_E(self, kappa):
44+
def _compute_E(self, kappa: float) -> float:
45+
"""
46+
Compute the energy spectrum E(kappa) for a given kappa.
47+
"""
4648
if kappa < 1e-12:
4749
return 0.0
48-
denom = (1.0 / (self.L2D**2) + kappa**2) ** (7.0 / 3.0)
50+
denom = (1.0 / (self.L_2D**2) + kappa**2) ** (7.0 / 3.0)
4951
atten = 1.0 / (1.0 + (kappa * self.z_i) ** 2)
5052
return self.c * (kappa**3) / denom * atten
5153

52-
def _solve_for_c(self, sigma2, L2D, z_i):
54+
def _solve_for_c(self):
5355
"""
54-
Solve for c so that integral of E(k) from k=0..inf = sigma2.
55-
(1D version for demonstration.)
56+
Solve for scaling constant c so that integral of E(k) from k=0..inf = sigma2.
5657
"""
5758

58-
def integrand(k):
59-
return (k**3 / ((1.0 / (L2D**2) + k**2) ** (7.0 / 3.0))) * (1.0 / (1.0 + (k * z_i) ** 2))
59+
def integrand(k: float) -> float:
60+
return (k**3 / ((1.0 / (self.L_2D**2) + k**2) ** (7.0 / 3.0))) * (1.0 / (1.0 + (k * self.z_i) ** 2))
6061

6162
val, _ = integrate.quad(integrand, 0, np.inf)
62-
self.c = sigma2 / val
63+
return self.sigma2 / val
6364

6465
def generate(
6566
self,
67+
pad: bool = True,
6668
):
6769
"""
68-
Approx approach:
69-
1) compute c from sigma2
70-
2) for each k1, compute phi_11(k1) using e.g. E(kappa)/(pi*kappa)
71-
or the simpler "2D swirl" formula
72-
3) multiply by factor ~ 2 * pi^2 / L1
73-
4) randomize phases in k2
74-
5) iFFT => real field
75-
We'll do just the 'u' field for demonstration, but you can do 'v' similarly.
70+
Generate a 2D low-frequency field.
7671
"""
7772
L1, L2 = self.L1, self.L2
7873
Nx, Ny = self.Nx, self.Ny
@@ -120,48 +115,57 @@ def generate(
120115
if var_now > 1e-12:
121116
u_field *= np.sqrt(self.sigma2 / var_now)
122117

123-
u_field = np.pad(u_field, ((0, 1), (0, 1)), mode="wrap")
118+
if pad:
119+
u_field = np.pad(u_field, ((0, 1), (0, 1)), mode="wrap")
120+
121+
return np.linspace(0, L1 / 1000, Nx), np.linspace(0, L2 / 1000, Ny), u_field
122+
124123

125-
return np.linspace(0, L1, Nx), np.linspace(0, L2, Ny), u_field
124+
if __name__ == "__main__":
125+
import matplotlib.pyplot as plt
126126

127+
# Domain: 60 km x 15 km
128+
L1 = 60_000.0
129+
L2 = 15_000.0
130+
# Nx = 1024 # = 2^10
131+
# Ny = 256 # = 2^8
127132

128-
# TODO: Update this
129-
# if __name__ == "__main__":
130-
# # Domain: 60 km x 15 km
131-
# L1 = 60_000.0
132-
# L2 = 15_000.0
133-
# Nx = 1024
134-
# Ny = 256
133+
# Figure 3 parameters
134+
L2D = 15000.0 # [m]
135+
sigma2 = 0.6 # [m^2/s^2]
136+
z_i = 500.0 # [m]
137+
psi_degs = 43.0 # anisotropy angle
135138

136-
# # Figure 3 parameters
137-
# L2D = 15000.0 # [m]
138-
# sigma2 = 0.6 # [m^2/s^2]
139-
# z_i = 500.0 # [m]
140-
# psi_degs = 43.0 # anisotropy angle
139+
generator = LowFreq2DFieldGenerator(
140+
grid_dimensions=np.array([L1, L2]),
141+
grid_levels=np.array([10, 8]),
142+
L_2D=L2D,
143+
sigma2=sigma2,
144+
z_i=z_i,
145+
psi_degs=psi_degs,
146+
)
141147

142-
# # Generate large-scale u-component
143-
# u_field = generate_2D_lowfreq_approx(Nx, Ny, L1, L2, psi_degs, sigma2, L2D, z_i)
148+
# Generate large-scale u-component
149+
_, _, u_field = generator.generate(pad=False)
144150

145-
# # Generate large-scale v-component similarly
146-
# # (Here, we assume same sigma^2 and same approach.)
147-
# v_field = generate_2D_lowfreq_approx(Nx, Ny, L1, L2, psi_degs, sigma2, L2D, z_i)
151+
# Generate large-scale v-component similarly
152+
# (Here, we assume same sigma^2 and same approach.)
153+
x, y, v_field = generator.generate(pad=False)
148154

149-
# x = np.linspace(0, L1 / 1000, Nx)
150-
# y = np.linspace(0, L2 / 1000, Ny)
151-
# X, Y = np.meshgrid(x, y, indexing="ij")
155+
X, Y = np.meshgrid(x, y, indexing="ij")
152156

153-
# fig, axs = plt.subplots(2, 1, figsize=(10, 8))
154-
# im1 = axs[0].pcolormesh(X, Y, u_field, shading="auto", cmap="RdBu_r")
155-
# cb1 = plt.colorbar(im1, ax=axs[0], label="m/s")
156-
# axs[0].set_title("(a) u")
157-
# axs[0].set_xlabel("x [km]")
158-
# axs[0].set_ylabel("y [km]")
157+
fig, axs = plt.subplots(2, 1, figsize=(10, 8))
158+
im1 = axs[0].pcolormesh(X, Y, u_field, shading="auto", cmap="RdBu_r")
159+
cb1 = plt.colorbar(im1, ax=axs[0], label="m/s")
160+
axs[0].set_title("(a) u")
161+
axs[0].set_xlabel("x [km]")
162+
axs[0].set_ylabel("y [km]")
159163

160-
# im2 = axs[1].pcolormesh(X, Y, v_field, shading="auto", cmap="RdBu_r")
161-
# cb2 = plt.colorbar(im2, ax=axs[1], label="m/s")
162-
# axs[1].set_title("(b) v")
163-
# axs[1].set_xlabel("x [km]")
164-
# axs[1].set_ylabel("y [km]")
164+
im2 = axs[1].pcolormesh(X, Y, v_field, shading="auto", cmap="RdBu_r")
165+
cb2 = plt.colorbar(im2, ax=axs[1], label="m/s")
166+
axs[1].set_title("(b) v")
167+
axs[1].set_xlabel("x [km]")
168+
axs[1].set_ylabel("y [km]")
165169

166-
# plt.tight_layout()
167-
# plt.show()
170+
plt.tight_layout()
171+
plt.show()

0 commit comments

Comments
 (0)