7
7
class LowFreq2DFieldGenerator :
8
8
def __init__ (
9
9
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 ,
16
16
c : Optional [float ] = None ,
17
17
):
18
18
# Field parameters
19
- self .L2D = L2D
19
+ self .L_2D = L_2D
20
20
self .sigma2 = sigma2
21
21
self .z_i = z_i
22
22
self .psi_degs = psi_degs
@@ -25,11 +25,10 @@ def __init__(
25
25
self .L1 , self .L2 = grid_dimensions [:2 ] # Default was 60k x 15k
26
26
self .Nx , self .Ny = 2 ** grid_levels [:2 ] # Default was 1024 x 256
27
27
28
- self .c = self ._solve_for_c (sigma2 , L2D , z_i )
29
28
self .psi_rad = np .deg2rad (self .psi_degs )
30
29
31
30
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 ()
33
32
else :
34
33
self .c = c
35
34
@@ -42,37 +41,33 @@ def _compute_kappa(self, kx, ky):
42
41
43
42
return np .sqrt (2.0 * ((kx ** 2 ) * cos2 + (ky ** 2 ) * sin2 ))
44
43
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
+ """
46
48
if kappa < 1e-12 :
47
49
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 )
49
51
atten = 1.0 / (1.0 + (kappa * self .z_i ) ** 2 )
50
52
return self .c * (kappa ** 3 ) / denom * atten
51
53
52
- def _solve_for_c (self , sigma2 , L2D , z_i ):
54
+ def _solve_for_c (self ):
53
55
"""
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.
56
57
"""
57
58
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 ))
60
61
61
62
val , _ = integrate .quad (integrand , 0 , np .inf )
62
- self .c = sigma2 / val
63
+ return self .sigma2 / val
63
64
64
65
def generate (
65
66
self ,
67
+ pad : bool = True ,
66
68
):
67
69
"""
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.
76
71
"""
77
72
L1 , L2 = self .L1 , self .L2
78
73
Nx , Ny = self .Nx , self .Ny
@@ -120,48 +115,57 @@ def generate(
120
115
if var_now > 1e-12 :
121
116
u_field *= np .sqrt (self .sigma2 / var_now )
122
117
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
+
124
123
125
- return np .linspace (0 , L1 , Nx ), np .linspace (0 , L2 , Ny ), u_field
124
+ if __name__ == "__main__" :
125
+ import matplotlib .pyplot as plt
126
126
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
127
132
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
135
138
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
+ )
141
147
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 )
144
150
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 )
148
154
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" )
152
156
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]" )
159
163
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]" )
165
169
166
- # plt.tight_layout()
167
- # plt.show()
170
+ plt .tight_layout ()
171
+ plt .show ()
0 commit comments