turbx 1.0.2__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.
turbx/mvp.py ADDED
@@ -0,0 +1,385 @@
1
+ import numpy as np
2
+ import scipy as sp
3
+ from scipy.integrate import cumulative_trapezoid
4
+ from scipy.special import exp1
5
+
6
+ from turbx import gradient
7
+
8
+ # ======================================================================
9
+
10
+ def calc_Iww(y,u,ue,utau,j_edge):
11
+ '''
12
+ Second moment of the velocity defect
13
+ Integral of squared velocity defect
14
+ Defect energy integral
15
+ Iww = C′ = ∫ (W+)^2 d(y/Δ)
16
+ Monkewitz Chauhan Nagib (2007) : https://doi.org/10.1063/1.2780196
17
+ Nagib Chauhan Monkewitz (2007) : https://doi.org/10.1098/rsta.2006.1948
18
+ '''
19
+ uplus_defect = ( ue - u ) / utau ## W+(y)
20
+ dRC_integral = cumulative_trapezoid(y=uplus_defect, x=y, initial=0.)
21
+ dRC = dRC_integral[j_edge]
22
+ yovdRC = y/dRC
23
+ Iww_integrand = uplus_defect**2 ## (W+)^2
24
+ Iww_integral = cumulative_trapezoid(y=Iww_integrand, x=yovdRC, initial=0.)
25
+ Iww = Iww_integral[j_edge]
26
+ return Iww
27
+
28
+ def comp_transform_TL(y,u,utau,rho,mu,**kwargs):
29
+ '''
30
+ Trettel & Larsson (2016)
31
+ https://doi.org/10.1063/1.4942022
32
+ '''
33
+ acc = kwargs.get('acc',6)
34
+ edge_stencil = kwargs.get('edge_stencil','full')
35
+ ddy_rho = kwargs.get('ddy_rho',None)
36
+ ddy_mu = kwargs.get('ddy_mu',None)
37
+
38
+ if ddy_rho is None:
39
+ ddy_rho = gradient(rho, y, acc=acc, edge_stencil=edge_stencil)
40
+ if ddy_mu is None:
41
+ ddy_mu = gradient(mu, y, acc=acc, edge_stencil=edge_stencil)
42
+
43
+ rho_w = rho[0]
44
+ mu_w = mu[0]
45
+ u_plus = u / utau
46
+ tau_wall = rho_w * utau**2
47
+ sc_l_in = mu_w / ( utau * rho_w ) ## δν
48
+ #y_plus = y * utau * rho_w / mu_w
49
+
50
+ ## Eq.28
51
+ bracket = 1. + ((1./(2.*rho))*ddy_rho*y) - ((1/mu)*ddy_mu*y)
52
+ integrand = np.sqrt(rho/rho_w) * bracket
53
+ uTL_plus = cumulative_trapezoid(y=integrand, x=u_plus, initial=0.) ## U+
54
+
55
+ u_sl = np.sqrt( tau_wall / rho ) ## semilocal friction velocity (NOT transformed U+)
56
+ y_sl = y * u_sl * rho / mu ## semilocal coordinate
57
+
58
+ ## Re-dimensionalize first two returned profiles by uτ,δν
59
+ return uTL_plus*utau, y_sl*sc_l_in, uTL_plus, y_sl
60
+
61
+ def comp_transform_VD(y,u,rho):
62
+ '''
63
+ van Driest velocity transform
64
+ uVD(y) = ∫ √(ρ/ρ_w) du
65
+ van Driest (1951)
66
+ https://doi.org/10.2514/8.1895
67
+ '''
68
+ rho_w = rho[0]
69
+ integrand = np.sqrt(rho/rho_w)
70
+ uVD = cumulative_trapezoid(y=integrand, x=u, initial=0.)
71
+ return uVD
72
+
73
+ def comp_transform_VIPL(y,u,rho,mu,a=1.5,b=0.5):
74
+ '''
75
+ Volpiani et al. (2020)
76
+ https://doi.org/10.1103/PhysRevFluids.5.052602
77
+ '''
78
+ rho_w = rho[0]
79
+ mu_w = mu[0]
80
+ M = mu/mu_w
81
+ R = rho/rho_w
82
+ F = R**b * M**(-a)
83
+ yVP = cumulative_trapezoid(y=F, x=y, initial=0.)
84
+ G = R**b * M**(1-a)
85
+ uVP = cumulative_trapezoid(y=G, x=u, initial=0.)
86
+ return uVP,yVP
87
+
88
+ def comp_transform_GFM(y,uFv,rho,mu,r_uII_vII,utau,ddy_uFv=None,acc=6,edge_stencil='full'):
89
+ '''
90
+ Griffin, Fu & Moin (2021)
91
+ https://doi.org/10.1073/pnas.2111144118
92
+ '''
93
+ if ddy_uFv is None:
94
+ ddy_uFv = gradient(uFv, y, acc=acc, edge_stencil=edge_stencil)
95
+
96
+ ny = y.shape[0]
97
+ rho_w = rho[0]
98
+ mu_w = mu[0]
99
+ tau_wall = utau**2 * rho_w
100
+ sc_l_in = mu_w / ( utau * rho_w ) ## δν
101
+ y_plus = y * utau * rho_w / mu_w
102
+ uFv_plus = uFv / utau
103
+ mu_plus = mu / mu_w
104
+
105
+ tau_visc_plus = mu * ddy_uFv / tau_wall ## τv+
106
+ tau_R_plus = -1. * r_uII_vII / tau_wall ## τR+
107
+ tau_plus = tau_visc_plus + tau_R_plus ## τ+
108
+
109
+ u_sl = np.sqrt( tau_wall / rho ) ## u_{sl} : semilocal friction velocity
110
+ #sc_l_sl = mu / ( u_sl * rho ) ## ℓ_{sl} : semilocal length scale
111
+ y_star = y * u_sl * rho / mu ## y* = y_{sl} = y/ℓ_{sl} : semilocal coordinate
112
+
113
+ duFvplus_dyplus = gradient(uFv_plus, y_plus, acc=acc, edge_stencil=edge_stencil)
114
+ duFvplus_dystar = gradient(uFv_plus, y_star, acc=acc, edge_stencil=edge_stencil)
115
+
116
+ #duFvplus_dyplus = ddy_uFv / (utau / sc_l_in) ## ∂[uFv+]/∂y+ -- OK, sc_l_in is a constant over [y]
117
+ #duFvplus_dystar = ddy_uFv / (utau / sc_l_sl) ## ∂[uFv+]/∂y* -- WRONG, sc_l_sl is NOT constant over [y]
118
+
119
+ # np.testing.assert_allclose(
120
+ # duFvplus_dyplus,
121
+ # gradient(uFv_plus, y_plus, acc=acc, edge_stencil=edge_stencil),
122
+ # rtol=1e-6,
123
+ # atol=1e-12,
124
+ # )
125
+
126
+ # np.testing.assert_allclose(
127
+ # duFvplus_dystar,
128
+ # gradient(uFv_plus, y_star, acc=acc, edge_stencil=edge_stencil),
129
+ # rtol=1e-6,
130
+ # atol=1e-12,
131
+ # )
132
+
133
+ S_eq_plus = (1./mu_plus) * duFvplus_dystar ## Eq.1
134
+ S_TL_plus = mu_plus * duFvplus_dyplus ## Eq.2
135
+
136
+ ## Eq.4 St+ = [τ+·Seq+] / [ τ+ + Seq+ - STL+ ]
137
+ A = tau_plus * S_eq_plus
138
+ B = tau_plus + S_eq_plus - S_TL_plus
139
+ B = np.where(np.abs(B) < 1e-12, 1e-12, B)
140
+ S_t_plus = A / B
141
+
142
+ # ## Eq.5
143
+ # A = S_eq_plus
144
+ # B = 1. + S_eq_plus - S_TL_plus
145
+ # B = np.where(np.abs(B) < 1e-12, 1e-12, B)
146
+ # S_t_plus = A / B
147
+
148
+ ## Eq.3 Assertion
149
+ if False:
150
+ ratio_v = np.full((ny,),0.,dtype=np.float64)
151
+ ratio_R = np.full((ny,),0.,dtype=np.float64)
152
+ np.divide(tau_visc_plus, S_TL_plus, out=ratio_v, where=np.abs(S_TL_plus) > 1e-12)
153
+ np.divide(tau_R_plus, S_eq_plus, out=ratio_R, where=np.abs(S_eq_plus) > 1e-12)
154
+ tau_plus_chk = S_t_plus * (ratio_v + ratio_R)
155
+ mask = ( (np.abs(S_TL_plus) > 1e-12) & (np.abs(S_eq_plus) > 1e-12) )
156
+ np.testing.assert_allclose(
157
+ tau_plus[mask],
158
+ tau_plus_chk[mask],
159
+ rtol=1e-6,
160
+ )
161
+
162
+ ## Ut+[y*] = ∫St+ dy*
163
+ uGFM_plus = cumulative_trapezoid(y=S_t_plus, x=y_star, initial=0.)
164
+
165
+ ## Re-dimensionalize first two returned profiles by uτ,δν
166
+ return uGFM_plus*utau, y_star*sc_l_in, uGFM_plus, y_star
167
+
168
+ # ======================================================================
169
+
170
+ def asymptotic_Wplus_eta(eta, y_plus, Re_theta):
171
+ '''
172
+ Monkewitz et al. (2007)
173
+ Full-boundary-layer wake function
174
+ -----
175
+ This function generates a Re-dependent full-layer W+
176
+ (not just W+_outer) using the U+ composite profile
177
+ '''
178
+ kappa = 0.384
179
+ Cstar = 3.354
180
+
181
+ ## Shape factor
182
+ H12 = asymptotic_H12_Retheta(Re_theta)
183
+
184
+ ## Coles-Fernholz
185
+ ue_plus = (1./kappa)*np.log(H12*Re_theta) + Cstar
186
+
187
+ ## Composite velocity
188
+ uplus_composite = asymptotic_uplus_composite(y_plus, eta, Re_theta)
189
+
190
+ ## Wake (velocity defect)
191
+ return ue_plus - uplus_composite
192
+
193
+ def asymptotic_uplus_inner(y_plus):
194
+ '''
195
+ Monkewitz et al. (2007)
196
+ Inner mean velocity U^+_{inner}(y^+)
197
+ Obtained by integrating the Padé indicator function
198
+ Eqs. (5)-(7)
199
+ '''
200
+ y_plus = np.asarray(y_plus)
201
+
202
+ k = 0.384
203
+
204
+ ## Padé P23 coefficients (Eq. 5)
205
+ b0 = 1.000e-2 / k
206
+ b1 = 1.100e-2
207
+ b2 = 1.100e-4
208
+
209
+ P23 = b0 * (1. + b1*y_plus + b2*y_plus**2) / (
210
+ 1. + b1*y_plus + b2*y_plus**2 + k*b0*b2*y_plus**3
211
+ )
212
+
213
+ ## Padé P25 coefficients (Eq. 6)
214
+ h1 = -1.000e-2
215
+ h2 = 6.000e-3
216
+ h3 = 9.977e-4
217
+ h4 = 2.200e-5
218
+ h5 = 1.000e-6
219
+
220
+ P25 = (1. - b0) * (1. + h1*y_plus + h2*y_plus**2) / (
221
+ 1. + h1*y_plus + h2*y_plus**2 + h3*y_plus**3 + h4*y_plus**4 + h5*y_plus**5
222
+ )
223
+
224
+ d_uplus_d_yplus = P23 + P25 ## d[u+]/dy+
225
+
226
+ ## scipy.integrate.quad() could be used for continuous integration
227
+
228
+ ## discrete trapezoidal integration
229
+ uplus_inner = cumulative_trapezoid(y=d_uplus_d_yplus, x=y_plus, initial=0.)
230
+ return uplus_inner
231
+
232
+ def asymptotic_uplus_composite(y_plus, eta, Re_theta):
233
+ '''
234
+ Monkewitz et al. (2007)
235
+ Composite mean velocity U^+(y^+,η)
236
+ Eq. (15), with U_e^+ from H12(Re_θ)
237
+ '''
238
+ y_plus = np.asarray(y_plus)
239
+ eta = np.asarray(eta)
240
+ n = y_plus.shape[0]
241
+
242
+ kappa = 0.384
243
+ B = 4.17 ## log-law intercept
244
+ Cstar = 3.354 ## C* (Monkewitz / Nagib)
245
+
246
+ ## Inner velocity (Padé-based)
247
+ uplus_inner = asymptotic_uplus_inner(y_plus)
248
+
249
+ ## Shape factor and edge velocity
250
+ H12 = asymptotic_H12_Retheta(Re_theta)
251
+ ue_plus = (1.0/kappa)*np.log(H12*Re_theta) + Cstar
252
+
253
+ ## Outer velocity
254
+ Wplus_outer = asymptotic_Wplus_eta_outer(eta)
255
+ uplus_outer = ue_plus - Wplus_outer
256
+
257
+ ## Overlap (log law)
258
+ uplus_log = np.full((n,),0.,dtype=np.float64)
259
+ uplus_log[1:] = (1.0/kappa)*np.log(y_plus[1:]) + B
260
+ uplus_log[0] = B
261
+
262
+ ## Additive composite
263
+ uplus_composite = uplus_inner + uplus_outer - uplus_log
264
+ uplus_composite[0] = 0.
265
+
266
+ return uplus_composite
267
+
268
+ def asymptotic_Wplus_eta_outer(eta):
269
+ '''
270
+ Monkewitz et al. (2007)
271
+ https://doi.org/10.1063/1.2780196
272
+ Eq.9
273
+ '''
274
+ ny = eta.shape[0]
275
+ k = 0.384 ## κ
276
+ w0 = 0.6332
277
+ wn1 = -0.096 ## w_{-1}
278
+ w2 = 28.5
279
+ w8 = 33000.
280
+
281
+ Wplus_outer = np.full((ny,),np.nan,dtype=np.float64)
282
+
283
+ ii = np.where(eta>0)[0]
284
+ η = eta[ii]
285
+
286
+ ## W_+ = ((1/κ) E1(η) + w0) * 0.5 * [1 - tanh(w_{-1}/η + w2 η^2 + w8 η^8)]
287
+ Wplus_outer[ii] = ( (1./k)*exp1(eta[ii]) + w0 ) \
288
+ * 0.5 \
289
+ * ( 1. - np.tanh( (wn1/η) + w2*η**2 + w8*η**8 ) )
290
+
291
+ return Wplus_outer
292
+
293
+ def asymptotic_H12_Retheta(Re_theta):
294
+ '''
295
+ H12 = δ1/δ2 = δ*/θ
296
+ Reθ = θ·Ue·ρe/μe
297
+ -----
298
+ Monkewitz et al. (2007)
299
+ https://doi.org/10.1063/1.2780196
300
+ Nagib et al. (2007)
301
+ https://doi.org/10.1098/rsta.2006.1948
302
+ '''
303
+
304
+ ## Asymptotic constants
305
+ Cs = 3.354 ## C*
306
+ k = 0.384 ## κ
307
+ Iww = 7.135 ## Iww (Monkewitz) | C′ (Nagib)
308
+
309
+ if isinstance(Re_theta,(int,np.int32,np.int64,float,np.float32,np.float64)):
310
+ Re_theta = np.array([Re_theta,],np.float64)
311
+ elif isinstance(Re_theta, np.ndarray):
312
+ pass
313
+ else:
314
+ raise ValueError
315
+
316
+ def __f_root_nonlin_H12(H12,Re_theta):
317
+ ue_plus = (1/k)*np.log(H12*Re_theta) + Cs ## Coles-Fernholz
318
+ H12_test = 1. / ( 1. - (Iww/ue_plus) )
319
+ root = H12_test - H12
320
+ return root
321
+
322
+ def __f_H12_Retheta(Re_theta):
323
+ n = Re_theta.shape[0]
324
+ H12_out = np.zeros((n,),dtype=np.float64)
325
+ for i in range(n):
326
+ sol = sp.optimize.least_squares(
327
+ fun=__f_root_nonlin_H12,
328
+ args=(Re_theta[i],),
329
+ x0=1.35,
330
+ xtol=1e-15,
331
+ ftol=1e-15,
332
+ gtol=1e-15,
333
+ method='dogbox',
334
+ bounds=(1,2),
335
+ )
336
+ if not sol.success:
337
+ raise ValueError
338
+ H12_out[i] = float(sol.x[0])
339
+ return H12_out
340
+
341
+ H12 = __f_H12_Retheta(Re_theta)
342
+
343
+ if H12.shape[0]==1:
344
+ H12 = float(H12)
345
+
346
+ return H12
347
+
348
+ def asymptotic_H32_Retheta(Re_theta,I3):
349
+ '''
350
+ H32 = δ3/δ2
351
+ Reθ = θ·Ue·ρe/μe
352
+ '''
353
+
354
+ ## Asymptotic constants (same as H12 function)
355
+ Cs = 3.354 ## C*
356
+ k = 0.384 ## κ
357
+ Iww = 7.135 ## Iww (Monkewitz) | C′ (Nagib)
358
+ I2 = Iww
359
+
360
+ if isinstance(Re_theta,(int,np.int32,np.int64,float,np.float32,np.float64)):
361
+ Re_theta = np.array([Re_theta,],np.float64)
362
+ elif isinstance(Re_theta, np.ndarray):
363
+ pass
364
+ elif isinstance(Re_theta, list):
365
+ Re_theta = np.array(Re_theta,np.float64)
366
+ else:
367
+ raise ValueError
368
+
369
+ ## H12(Reθ)
370
+ H12 = asymptotic_H12_Retheta(Re_theta)
371
+
372
+ if isinstance(H12,float):
373
+ H12 = np.array([H12,],np.float64)
374
+
375
+ n = Re_theta.shape[0]
376
+ H32 = np.zeros((n,),dtype=np.float64)
377
+
378
+ for i in range(n):
379
+ ue_plus = (1/k)*np.log(H12[i]*Re_theta[i]) + Cs ## Coles–Fernholz
380
+ H32[i] = (2. - 3.*(I2/ue_plus) + I3/(ue_plus**2)) / (1. - (I2/ue_plus))
381
+
382
+ if H32.shape[0]==1:
383
+ H32 = float(H32)
384
+
385
+ return H32