@@ -172,26 +172,48 @@ function lamb_smooth_minimum(l, smoothness_param, λ_floor)
172
172
return numerator / max (eps (FT), denominator)
173
173
end
174
174
175
+ function blend_scales (
176
+ method:: SmoothMinimumBlending ,
177
+ l:: SA.SVector ,
178
+ turbconv_params,
179
+ )
180
+ FT = eltype (l)
181
+ smin_ub = CAP. smin_ub (turbconv_params)
182
+ smin_rm = CAP. smin_rm (turbconv_params)
183
+ l_final = lamb_smooth_minimum (l, smin_ub, smin_rm)
184
+ return max (l_final, FT (0 ))
185
+ end
186
+
187
+ function blend_scales (
188
+ method:: HardMinimumBlending ,
189
+ l:: SA.SVector ,
190
+ turbconv_params,
191
+ )
192
+ FT = eltype (l)
193
+ return max (minimum (l), FT (0 ))
194
+ end
195
+
175
196
"""
176
- mixing_length(params, ustar, ᶜz, sfc_tke, ᶜlinear_buoygrad, ᶜtke, obukhov_length, ᶜstrain_rate_norm, ᶜPr, ᶜtke_exch)
197
+ mixing_length(params, ustar, ᶜz, z_sfc, ᶜdz,
198
+ sfc_tke, ᶜlinear_buoygrad, ᶜtke, obukhov_length,
199
+ ᶜstrain_rate_norm, ᶜPr, ᶜtke_exch, scale_blending_method)
177
200
178
201
where:
179
- - `params`: set with model parameters
180
- - `ustar`: friction velocity
181
- - `ᶜz`: height
182
- - `tke_sfc`: env kinetic energy at first cell center
183
- - `ᶜlinear_buoygrad`: buoyancy gradient
184
- - `ᶜtke`: env turbulent kinetic energy
185
- - `obukhov_length`: surface Monin Obukhov length
186
- - `ᶜstrain_rate_norm`: Frobenius norm of strain rate tensor
187
- - `ᶜPr`: Prandtl number
188
- - `ᶜtke_exch`: subdomain exchange term
189
-
190
- Returns mixing length as a smooth minimum between
191
- wall-constrained length scale,
192
- production-dissipation balanced length scale,
193
- effective static stability length scale, and
194
- Smagorinsky length scale.
202
+ - `params`: Parameter set (e.g., CLIMAParameters.AbstractParameterSet).
203
+ - `ustar`: Friction velocity [m/s].
204
+ - `ᶜz`: Cell center height [m].
205
+ - `z_sfc`: Surface elevation [m].
206
+ - `ᶜdz`: Cell vertical thickness [m].
207
+ - `sfc_tke`: TKE near the surface (e.g., first cell center) [m^2/s^2].
208
+ - `ᶜlinear_buoygrad`: N^2, Brunt-Väisälä frequency squared [1/s^2].
209
+ - `ᶜtke`: Turbulent kinetic energy at cell center [m^2/s^2].
210
+ - `obukhov_length`: Surface Monin-Obukhov length [m].
211
+ - `ᶜstrain_rate_norm`: Frobenius norm of strain rate tensor [1/s].
212
+ - `ᶜPr`: Turbulent Prandtl number [-].
213
+ - `ᶜtke_exch`: TKE exchange term [m^2/s^3].
214
+ - `scale_blending_method`: The method to use for blending physical scales.
215
+
216
+ Calculates the turbulent mixing length, limited by physical constraints and grid resolution.
195
217
"""
196
218
function mixing_length (
197
219
params,
@@ -206,78 +228,187 @@ function mixing_length(
206
228
ᶜstrain_rate_norm,
207
229
ᶜPr,
208
230
ᶜtke_exch,
231
+ scale_blending_method,
209
232
)
210
233
211
234
FT = eltype (params)
235
+ eps_FT = eps (FT)
236
+
237
+
212
238
turbconv_params = CAP. turbconv_params (params)
239
+ sf_params = CAP. surface_fluxes_params (params) # Businger params
240
+
213
241
c_m = CAP. tke_ed_coeff (turbconv_params)
214
242
c_d = CAP. tke_diss_coeff (turbconv_params)
215
243
smin_ub = CAP. smin_ub (turbconv_params)
216
244
smin_rm = CAP. smin_rm (turbconv_params)
217
245
c_b = CAP. static_stab_coeff (turbconv_params)
218
246
vkc = CAP. von_karman_const (params)
219
247
220
- # compute the maximum mixing length at height z
221
- l_z = ᶜz - z_sfc
248
+ # MOST stability function coefficients
249
+ most_a_m = sf_params. ufp. a_m # Businger a_m
250
+ most_b_m = sf_params. ufp. b_m # Businger b_m
251
+ most_g_m = CAP. coefficient_b_m_gryanik (params) # Gryanik b_m
222
252
223
- # compute the l_W - the wall constraint mixing length
224
- # which imposes an upper limit on the size of eddies near the surface
225
- # kz scale (surface layer)
226
- if obukhov_length < 0.0 # unstable
227
- l_W =
228
- vkc * (ᶜz - z_sfc) /
229
- max (sqrt (sfc_tke / ustar / ustar) * c_m, eps (FT)) *
230
- min ((1 - 100 * (ᶜz - z_sfc) / obukhov_length)^ FT (0.2 ), 1 / vkc)
231
- else # neutral or stable
232
- l_W =
233
- vkc * (ᶜz - z_sfc) /
234
- max (sqrt (sfc_tke / ustar / ustar) * c_m, eps (FT))
253
+ # l_z: Geometric distance from the surface
254
+ l_z = ᶜz - z_sfc
255
+ # Ensure l_z is non-negative when ᶜz is numerically smaller than z_sfc.
256
+ l_z = max (l_z, FT (0 ))
257
+
258
+ # l_W: Wall-constrained length scale (near-surface limit, to match
259
+ # Monin-Obukhov Similarity Theory in the surface layer, with Businger-Dyer
260
+ # type stability functions)
261
+ tke_sfc_safe = max (sfc_tke, eps_FT)
262
+ ustar_sq_safe = max (ustar * ustar, eps_FT) # ustar^2 may vanish in certain LES setups
263
+
264
+ # Denominator of the base length scale (always positive):
265
+ # c_m * √(tke_sfc / u_*²) = c_m * √(e_sfc) / u_*
266
+ # The value increases when u_* is small and decreases when e_sfc is small.
267
+ l_W_denom_factor = sqrt (tke_sfc_safe / ustar_sq_safe)
268
+ l_W_denom = max (c_m * l_W_denom_factor, eps_FT)
269
+
270
+ # Base length scale (neutral, but adjusted for TKE level)
271
+ # l_W_base = κ * l_z / (c_m * sqrt(e_sfc) / u_star)
272
+ # This can be Inf if l_W_denom is eps_FT and l_z is large.
273
+ # This can be 0 if l_z is 0.
274
+ # The expression approaches ∞ when l_W_denom ≈ eps_FT and l_z > eps_FT,
275
+ # and approaches 0 when l_z → 0.
276
+ l_W_base = vkc * l_z / l_W_denom
277
+
278
+ if obukhov_length < FT (0 ) # Unstable case
279
+ obukhov_len_safe = min (obukhov_length, - eps_FT) # Ensure L < 0
280
+ zeta = l_z / obukhov_len_safe # Stability parameter zeta = z/L (<0)
281
+
282
+ # Calculate MOST term (1 - b_m * zeta)
283
+ # Since zeta is negative, this term is > 1
284
+ inner_term = 1 - most_b_m * zeta
285
+
286
+ # Numerical safety check – by theory the value is ≥ 1.
287
+ inner_term_safe = max (inner_term, eps_FT)
288
+
289
+ # Unstable-regime correction factor:
290
+ # (1 − b_m ζ)^(1/4) = φ_m⁻¹,
291
+ # where φ_m is the Businger stability function φ_m = (1 − b_m ζ)^(-1/4).
292
+ stability_correction = sqrt (sqrt (inner_term_safe))
293
+ l_W = l_W_base * stability_correction
294
+
295
+ else # Neutral or stable case
296
+ # Ensure L > 0 for Monin-Obukhov length
297
+ obukhov_len_safe_stable = max (obukhov_length, eps_FT)
298
+ zeta = l_z / obukhov_len_safe_stable # zeta >= 0
299
+
300
+ # Stable/neutral-regime correction after Gryanik (2020):
301
+ # φ_m = 1 + a_m ζ / (1 + g_m ζ)^(2/3),
302
+ # a nonlinear refinement to the Businger formulation.
303
+ phi_m_denom_term = (1 + most_g_m * zeta)
304
+ # Guard against a negative base in the fractional power
305
+ # (theoretically impossible for ζ ≥ 0 and g_m > 0, retained for robustness).
306
+ phi_m_denom_cubed_sqrt = cbrt (phi_m_denom_term)
307
+ phi_m_denom =
308
+ max (phi_m_denom_cubed_sqrt * phi_m_denom_cubed_sqrt, eps_FT) # (val)^(2/3)
309
+
310
+ phi_m = 1 + (most_a_m * zeta) / phi_m_denom
311
+
312
+ # Stable-regime correction factor: 1 / φ_m.
313
+ # phi_m should be >= 1 for stable/neutral
314
+ stability_correction = 1 / max (phi_m, eps_FT)
315
+
316
+ # Apply the correction factor
317
+ l_W = l_W_base * stability_correction
235
318
end
236
-
237
- # compute l_TKE - the production-dissipation balanced length scale
238
- a_pd = c_m * (2 * ᶜstrain_rate_norm - ᶜlinear_buoygrad / ᶜPr) * sqrt (ᶜtke)
239
- # Dissipation term
240
- c_neg = c_d * ᶜtke * sqrt (ᶜtke)
241
- if abs (a_pd) > eps (FT) && 4 * a_pd * c_neg > - (ᶜtke_exch * ᶜtke_exch)
242
- l_TKE = max (
243
- - (ᶜtke_exch / 2 / a_pd) +
244
- sqrt (ᶜtke_exch * ᶜtke_exch + 4 * a_pd * c_neg) / 2 / a_pd,
245
- 0 ,
246
- )
247
- elseif abs (a_pd) < eps (FT) && abs (ᶜtke_exch) > eps (FT)
248
- l_TKE = c_neg / ᶜtke_exch
249
- else
250
- l_TKE = FT (0 )
319
+ l_W = max (l_W, FT (0 )) # Ensure non-negative
320
+
321
+ # --- l_TKE: TKE production-dissipation balance scale ---
322
+ tke_pos = max (ᶜtke, FT (0 )) # Ensure TKE is not negative
323
+ sqrt_tke_pos = sqrt (tke_pos)
324
+
325
+ # Net production of TKE from shear and buoyancy is approximated by
326
+ # (S² − N²/Pr_t) · √TKE · l,
327
+ # where S² denotes the gradient involved in shear production and
328
+ # N²/Pr_t denotes the gradient involved in buoyancy production.
329
+ # The factor below corresponds to that production term normalised by l.
330
+ a_pd = c_m * (2 * ᶜstrain_rate_norm - ᶜlinear_buoygrad / ᶜPr) * sqrt_tke_pos
331
+
332
+ # Dissipation is modelled as c_d · k^{3/2} / l.
333
+ # For the quadratic expression below, c_neg ≡ c_d · k^{3/2}.
334
+ c_neg = c_d * tke_pos * sqrt_tke_pos
335
+
336
+ l_TKE = FT (0 )
337
+ # Solve for l_TKE in
338
+ # a_pd · l_TKE − c_neg / l_TKE + ᶜtke_exch = 0
339
+ # ⇒ a_pd · l_TKE² + ᶜtke_exch · l_TKE − c_neg = 0
340
+ # yielding
341
+ # l_TKE = (−ᶜtke_exch ± √(ᶜtke_exch² + 4 a_pd c_neg)) / (2 a_pd).
342
+ if abs (a_pd) > eps_FT # If net of shear and buoyancy production (a_pd) is non-zero
343
+ discriminant = ᶜtke_exch * ᶜtke_exch + 4 * a_pd * c_neg
344
+ if discriminant >= FT (0 ) # Ensure real solution exists
345
+ # Select the physically admissible (positive) root for l_TKE.
346
+ # When a_pd > 0 (production exceeds dissipation) the root
347
+ # (−ᶜtke_exch + √D) / (2 a_pd)
348
+ # is positive. For a_pd < 0 the opposite root is required.
349
+ l_TKE_sol1 = (- (ᶜtke_exch) + sqrt (discriminant)) / (2 * a_pd)
350
+ # For a_pd < 0 (local destruction exceeds production) use
351
+ # (−ᶜtke_exch − √D) / (2 a_pd).
352
+ if a_pd > FT (0 )
353
+ l_TKE = l_TKE_sol1
354
+ else # a_pd < FT(0)
355
+ l_TKE = (- (ᶜtke_exch) - sqrt (discriminant)) / (2 * a_pd)
356
+ end
357
+ l_TKE = max (l_TKE, FT (0 )) # Ensure it's non-negative
358
+ end
359
+ elseif abs (ᶜtke_exch) > eps_FT # If a_pd is zero, balance is between exchange and dissipation
360
+ # ᶜtke_exch = c_neg / l_TKE => l_TKE = c_neg / ᶜtke_exch
361
+ # Ensure division is safe and result is positive
362
+ if ᶜtke_exch > eps_FT # Assuming positive exchange means TKE sink from env perspective
363
+ l_TKE = c_neg / ᶜtke_exch # if c_neg is positive, l_TKE is positive
364
+ elseif ᶜtke_exch < - eps_FT # Negative exchange means TKE source for env
365
+ # -|ᶜtke_exch| = c_neg / l_TKE. If c_neg > 0, this implies l_TKE < 0, which is unphysical.
366
+ # This case (a_pd=0, tke_exch < 0, c_neg > 0) implies TKE source and dissipation, no production.
367
+ # Dissipation = Source. So, c_d * k_sqrt_k / l = -tke_exch. l = c_d * k_sqrt_k / (-tke_exch)
368
+ l_TKE = c_neg / (- (ᶜtke_exch))
369
+ end
370
+ l_TKE = max (l_TKE, FT (0 ))
251
371
end
252
-
253
- # compute l_N - the effective static stability length scale.
254
- N_eff = sqrt (max (ᶜlinear_buoygrad, 0 ))
255
- if N_eff > 0.0
256
- l_N = min (sqrt (max (c_b * ᶜtke, 0 )) / N_eff, l_z)
257
- else
258
- l_N = l_z
372
+ # If a_pd = 0 and ᶜtke_exch = 0 (or c_neg = 0), l_TKE remains zero.
373
+
374
+ # --- l_N: Static-stability length scale (buoyancy limit), constrained by l_z ---
375
+ N_eff_sq = max (ᶜlinear_buoygrad, FT (0 )) # Use N^2 only if stable (N^2 > 0)
376
+ l_N = l_z # Default to wall distance if not stably stratified or TKE is zero
377
+ if N_eff_sq > eps_FT && tke_pos > eps_FT
378
+ N_eff = sqrt (N_eff_sq)
379
+ # l_N ~ sqrt(c_b * TKE) / N_eff
380
+ l_N_physical = sqrt (c_b * tke_pos) / N_eff
381
+ # Limit by distance from wall
382
+ l_N = min (l_N_physical, l_z)
259
383
end
384
+ l_N = max (l_N, FT (0 )) # Ensure non-negative
260
385
261
- # compute l_smag - smagorinsky length scale
262
- l_smag = smagorinsky_lilly_length (
263
- CAP. c_smag (params),
264
- N_eff,
265
- ᶜdz,
266
- ᶜPr,
267
- ᶜstrain_rate_norm,
268
- )
269
386
270
- # add limiters
271
- l = SA. SVector (
272
- l_N > l_z ? l_z : l_N,
273
- l_TKE > l_z ? l_z : l_TKE,
274
- l_W > l_z ? l_z : l_W,
275
- )
276
- # get soft minimum
277
- l_smin = lamb_smooth_minimum (l, smin_ub, smin_rm)
278
- l_limited = max (l_smag, min (l_smin, l_z))
387
+ # --- Combine Scales ---
388
+
389
+ # Vector of *physical* scales (wall, TKE, stability)
390
+ # These scales (l_W, l_TKE, l_N) are already ensured to be non-negative.
391
+ # l_N is already limited by l_z. l_W and l_TKE are not necessarily.
392
+ l_physical_scales = SA. SVector (l_W, l_TKE, l_N)
393
+
394
+ l_smin =
395
+ blend_scales (scale_blending_method, l_physical_scales, turbconv_params)
396
+
397
+ # 1. Limit the combined physical scale by the distance from the wall.
398
+ # This step mitigates excessive values of l_W or l_TKE.
399
+ l_limited_phys_wall = min (l_smin, l_z)
400
+
401
+ # 2. Impose the grid-scale limit
402
+ l_final = min (l_limited_phys_wall, ᶜdz)
403
+
404
+ # Final check: guarantee that the mixing length is at least a small positive
405
+ # value. This prevents division-by-zero in
406
+ # ε_d = C_d · TKE^{3/2} / l_mix
407
+ # when TKE > 0. When TKE = 0, l_mix is inconsequential, but eps_FT
408
+ # provides a conservative lower bound.
409
+ l_final = max (l_final, eps_FT)
279
410
280
- return MixingLength {FT} (l_limited , l_W, l_TKE, l_N)
411
+ return MixingLength {FT} (l_final , l_W, l_TKE, l_N)
281
412
end
282
413
283
414
"""
0 commit comments