Skip to content

Commit 6b17095

Browse files
authored
Merge pull request #4143 from CliMA/zs/mixing_length_uf
use universal functions in mixing length
2 parents 66ed40b + 199b0b1 commit 6b17095

File tree

5 files changed

+15
-64
lines changed

5 files changed

+15
-64
lines changed

Project.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ SciMLBase = "~2.108, ~2.109, ~2.110, ~2.111, ~2.112"
6868
SparseMatrixColorings = "0.4.20"
6969
StaticArrays = "1.9"
7070
Statistics = "1"
71-
SurfaceFluxes = "0.12.3, 0.13, 0.14.1"
72-
Thermodynamics = "0.14.1, 0.15"
71+
SurfaceFluxes = "0.14.1"
72+
Thermodynamics = "0.14.2, 0.15"
7373
UnrolledUtilities = "0.1.9"
7474
YAML = "0.4"
7575
julia = "1.9"

reproducibility_tests/ref_counter.jl

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
283
1+
284
22

33
# **README**
44
#
@@ -20,6 +20,10 @@
2020

2121

2222
#=
23+
284
24+
- Use universal functions in SurfaceFluxes in the mixing length closure,
25+
which changes the closure in the stable case (from Gryanik to Businger)
26+
2327
283
2428
- Change the Jacobian terms related to dp_drhoq_tot
2529
@@ -39,7 +43,8 @@
3943
- Add ∂/∂q elements to Jacobian
4044
4145
277
42-
- Update to use [email protected]. Supports Charnock-parameterization for aerodynamic roughness (default is still user-prescribed ScalarRoughness). SurfaceFluxes catch for neutrally-stable boundary layers (ζ ≈ 0) removed.
46+
- Update to use [email protected]. Supports Charnock-parameterization for aerodynamic roughness
47+
(default is still user-prescribed ScalarRoughness). SurfaceFluxes catch for neutrally-stable boundary layers (ζ ≈ 0) removed.
4348
4449
276
4550
- Update prognostic EDMF boundary conditions: apply equal surface fluxes to the

src/parameters/Parameters.jl

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,6 @@ Base.@kwdef struct ClimaAtmosParameters{
120120
α_hyperdiff_tracer::FT
121121
# Vertical diffusion
122122
α_vert_diff_tracer::FT
123-
# Gryanik coefficient
124-
coeff_a_m_gryanik::FT
125-
coeff_b_m_gryanik::FT
126123
end
127124

128125
Base.eltype(::ClimaAtmosParameters{FT}) where {FT} = FT
@@ -151,11 +148,6 @@ von_karman_const(ps::ACAP) =
151148

152149
# ------ MOST (Monin–Obukhov) stability-function coefficients ------
153150

154-
# Gryanik
155-
# needed because surface_fluxes_params defaults to BusingerParams
156-
coefficient_a_m_gryanik(ps::ACAP) = ps.coeff_a_m_gryanik
157-
coefficient_b_m_gryanik(ps::ACAP) = ps.coeff_b_m_gryanik
158-
159151
# Insolation parameters
160152
day(ps::ACAP) = IP.day(insolation_params(ps))
161153
tot_solar_irrad(ps::ACAP) = IP.tot_solar_irrad(insolation_params(ps))

src/parameters/create_parameters.jl

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,6 @@ function ClimaAtmosParameters(
4242
SurfaceFluxesParameters(toml_dict, UF.BusingerParams)
4343
SFP = typeof(surface_fluxes_params)
4444

45-
# Fetch Gryanik coefficients (since surface_fluxes_params defaults to BusingerParams)
46-
coeff_a_m_gryanik_val = UF.GryanikParams(FT).a_m
47-
coeff_b_m_gryanik_val = UF.GryanikParams(FT).b_m
48-
4945
surface_temp_params = SurfaceTemperatureParameters(toml_dict)
5046
STP = typeof(surface_temp_params)
5147

@@ -111,8 +107,6 @@ function ClimaAtmosParameters(
111107
surface_temp_params,
112108
vert_diff_params,
113109
external_forcing_params,
114-
coeff_a_m_gryanik = coeff_a_m_gryanik_val,
115-
coeff_b_m_gryanik = coeff_b_m_gryanik_val,
116110
)
117111
end
118112

src/prognostic_equations/eddy_diffusion_closures.jl

Lines changed: 6 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import StaticArrays as SA
66
import Thermodynamics.Parameters as TDP
77
import ClimaCore.Geometry as Geometry
88
import ClimaCore.Fields as Fields
9+
import SurfaceFluxes.UniversalFunctions as UF
910

1011
"""
1112
buoyancy_gradients(
@@ -294,17 +295,9 @@ function mixing_length_lopez_gomez_2020(
294295

295296
c_m = CAP.tke_ed_coeff(turbconv_params)
296297
c_d = CAP.tke_diss_coeff(turbconv_params)
297-
smin_ub = CAP.smin_ub(turbconv_params)
298-
smin_rm = CAP.smin_rm(turbconv_params)
299298
c_b = CAP.static_stab_coeff(turbconv_params)
300299
vkc = CAP.von_karman_const(params)
301300

302-
# MOST stability function coefficients
303-
most_a_m = sf_params.ufp.a_m # Businger a_m
304-
most_b_m = sf_params.ufp.b_m # Businger b_m
305-
gryanik_a_m = CAP.coefficient_a_m_gryanik(params) # Gryanik a_m
306-
gryanik_b_m = CAP.coefficient_b_m_gryanik(params) # Gryanik b_m
307-
308301
# l_z: Geometric distance from the surface
309302
l_z = ᶜz - z_sfc
310303
# Ensure l_z is non-negative when ᶜz is numerically smaller than z_sfc.
@@ -330,45 +323,12 @@ function mixing_length_lopez_gomez_2020(
330323
# and approaches 0 when l_z → 0.
331324
l_W_base = vkc * l_z / l_W_denom
332325

333-
if obukhov_length < FT(0) # Unstable case
334-
obukhov_len_safe = min(obukhov_length, -eps_FT) # Ensure L < 0
335-
zeta = l_z / obukhov_len_safe # Stability parameter zeta = z/L (<0)
336-
337-
# Calculate MOST term (1 - b_m * zeta)
338-
# Since zeta is negative, this term is > 1
339-
inner_term = 1 - most_b_m * zeta
340-
341-
# Numerical safety check – by theory the value is ≥ 1.
342-
inner_term_safe = max(inner_term, eps_FT)
343-
344-
# Unstable-regime correction factor:
345-
# (1 − b_m ζ)^(1/4) = φ_m⁻¹,
346-
# where φ_m is the Businger stability function φ_m = (1 − b_m ζ)^(-1/4).
347-
stability_correction = sqrt(sqrt(inner_term_safe))
348-
l_W = l_W_base * stability_correction
326+
obukhov_len_safe =
327+
obukhov_length < FT(0) ? min(obukhov_length, -eps_FT) : max(obukhov_length, eps_FT)
328+
zeta = l_z / obukhov_len_safe # Stability parameter zeta
329+
phi_m = UF.phi(sf_params.ufp, zeta, UF.MomentumTransport())
330+
l_W = l_W_base / max(phi_m, eps_FT)
349331

350-
else # Neutral or stable case
351-
# Ensure L > 0 for Monin-Obukhov length
352-
obukhov_len_safe_stable = max(obukhov_length, eps_FT)
353-
zeta = l_z / obukhov_len_safe_stable # zeta >= 0
354-
355-
# Stable/neutral-regime correction after Gryanik (2020):
356-
# φ_m = 1 + a_m ζ / (1 + b_m ζ)^(2/3),
357-
# a nonlinear refinement to the Businger formulation.
358-
# Optimization: (1+b_m*ζ)^(2/3) -> cbrt((1+b_m*ζ)^2)
359-
# Guard against a negative base in the fractional power
360-
# (theoretically impossible for ζ ≥ 0 and b_m > 0, retained for robustness).
361-
phi_m_denom = max(cbrt((1 + gryanik_b_m * zeta)^2), eps_FT)
362-
363-
phi_m = 1 + (gryanik_a_m * zeta) / phi_m_denom
364-
365-
# Stable-regime correction factor: 1 / φ_m.
366-
# phi_m should be >= 1 for stable/neutral
367-
stability_correction = 1 / max(phi_m, eps_FT)
368-
369-
# Apply the correction factor
370-
l_W = l_W_base * stability_correction
371-
end
372332
l_W = max(l_W, FT(0)) # Ensure non-negative
373333

374334
# --- l_TKE: TKE production-dissipation balance scale ---

0 commit comments

Comments
 (0)