Skip to content

Commit b442df0

Browse files
authored
Merge pull request #684 from CliMA/ts/0M-sat-excess
Expose 0M microphysics tendency based on saturation excess; bump patch release
2 parents ed3c40a + 84ed758 commit b442df0

5 files changed

Lines changed: 128 additions & 24 deletions

File tree

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "CloudMicrophysics"
22
uuid = "6a9e3e04-43cd-43ba-94b9-e8782df3c71b"
33
authors = ["Climate Modeling Alliance"]
4-
version = "0.31.6"
4+
version = "0.31.7"
55

66
[deps]
77
ClimaParams = "5c42b081-d73a-476f-9059-fd94b934656c"

src/BulkMicrophysicsTendencies.jl

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -296,14 +296,21 @@ end
296296
# --- 0-Moment Microphysics ---
297297

298298
"""
299-
bulk_microphysics_tendencies(
300-
::Microphysics0Moment,
301-
mp,
302-
tps,
303-
T,
304-
q_lcl,
305-
q_icl,
306-
)
299+
_precip_energy(tps, T, q_lcl, q_icl)
300+
301+
Internal energy of removed condensate, weighted by the liquid fraction.
302+
Shared helper for the two 0-moment `bulk_microphysics_tendencies` methods.
303+
"""
304+
@inline function _precip_energy(tps, T, q_lcl, q_icl)
305+
λ = TDI.liquid_fraction(tps, T, q_lcl, q_icl)
306+
I_liq = TDI.internal_energy_liquid(tps, T)
307+
I_ice = TDI.internal_energy_ice(tps, T)
308+
return λ * I_liq + (1 - λ) * I_ice
309+
end
310+
311+
"""
312+
bulk_microphysics_tendencies(::Microphysics0Moment, mp, tps, T, q_lcl, q_icl)
313+
bulk_microphysics_tendencies(::Microphysics0Moment, mp, tps, T, q_lcl, q_icl, q_vap_sat)
307314
308315
Compute 0-moment microphysics tendencies in one fused call.
309316
@@ -313,13 +320,16 @@ Returns a NamedTuple with:
313320
314321
Caller adds geopotential Φ for energy tendency: `e_tot = dq_tot_dt * (e_int_precip + Φ)`
315322
323+
The first form uses the fixed condensate threshold `qc_0`;
324+
the second form uses the supersaturation threshold `S_0 * q_vap_sat`.
325+
316326
# Arguments
317-
- `mp`: NamedTuple with microphysics parameters:
318-
- `params_0M`: Parameters0M struct (contains τ_precip, qc_0 or S_0)
327+
- `mp`: Microphysics0MParams (contains τ_precip, qc_0, S_0)
319328
- `tps`: Thermodynamics parameters
320329
- `T`: Temperature [K]
321330
- `q_lcl`: Cloud liquid specific content [kg/kg]
322331
- `q_icl`: Cloud ice specific content [kg/kg]
332+
- `q_vap_sat`: (second method only) Saturation specific humidity [kg/kg]
323333
324334
# Notes
325335
- Does NOT apply limiters (caller applies based on timestep)
@@ -333,22 +343,26 @@ Caller adds geopotential Φ for energy tendency: `e_tot = dq_tot_dt * (e_int_pre
333343
q_lcl,
334344
q_icl,
335345
)
336-
# Clamp negative specific contents to zero (robustness against numerical errors)
337346
q_lcl = UT.clamp_to_nonneg(q_lcl)
338347
q_icl = UT.clamp_to_nonneg(q_icl)
348+
dq_tot_dt = CM0.remove_precipitation(mp.precip, q_lcl, q_icl)
349+
e_int_precip = _precip_energy(tps, T, q_lcl, q_icl)
350+
return (; dq_tot_dt, e_int_precip)
351+
end
339352

340-
# Unpack microphysics parameters
341-
params_0M = mp.precip
342-
343-
# Precipitation removal rate
344-
dq_tot_dt = CM0.remove_precipitation(params_0M, q_lcl, q_icl)
345-
346-
# Internal energy of removed condensate (liquid fraction weighted)
347-
λ = TDI.liquid_fraction(tps, T, q_lcl, q_icl)
348-
I_liq = TDI.internal_energy_liquid(tps, T)
349-
I_ice = TDI.internal_energy_ice(tps, T)
350-
e_int_precip = λ * I_liq + (1 - λ) * I_ice
351-
353+
@inline function bulk_microphysics_tendencies(
354+
::Microphysics0Moment,
355+
mp::CMP.Microphysics0MParams,
356+
tps,
357+
T,
358+
q_lcl,
359+
q_icl,
360+
q_vap_sat,
361+
)
362+
q_lcl = UT.clamp_to_nonneg(q_lcl)
363+
q_icl = UT.clamp_to_nonneg(q_icl)
364+
dq_tot_dt = CM0.remove_precipitation(mp.precip, q_lcl, q_icl, q_vap_sat)
365+
e_int_precip = _precip_energy(tps, T, q_lcl, q_icl)
352366
return (; dq_tot_dt, e_int_precip)
353367
end
354368

test/bulk_tendencies_tests.jl

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,53 @@ function test_bulk_microphysics_0m_tendencies(FT)
548548
)
549549
@test tendencies isa NamedTuple{(:dq_tot_dt, :e_int_precip), NTuple{2, FT}}
550550
end
551+
552+
@testset "BulkMicrophysicsTendencies 0M - S_0 precipitation removal" begin
553+
ρ = FT(1.2)
554+
T = T_freeze + FT(10)
555+
q_lcl = FT(2e-3)
556+
q_icl = FT(0)
557+
q_vap_sat = TDI.saturation_vapor_specific_content_over_liquid(tps, T, ρ)
558+
559+
tendencies = BMT.bulk_microphysics_tendencies(
560+
BMT.Microphysics0Moment(),
561+
mp, tps, T, q_lcl, q_icl, q_vap_sat,
562+
)
563+
564+
# Should be negative (removing condensate above S_0 * q_vap_sat)
565+
@test tendencies.dq_tot_dt < FT(0)
566+
@test isfinite(tendencies.e_int_precip)
567+
end
568+
569+
@testset "BulkMicrophysicsTendencies 0M - S_0 below threshold" begin
570+
ρ = FT(1.2)
571+
T = T_freeze + FT(10)
572+
q_vap_sat = TDI.saturation_vapor_specific_content_over_liquid(tps, T, ρ)
573+
# Condensate below S_0 * q_vap_sat
574+
q_lcl = FT(1e-8)
575+
q_icl = FT(0)
576+
577+
tendencies = BMT.bulk_microphysics_tendencies(
578+
BMT.Microphysics0Moment(),
579+
mp, tps, T, q_lcl, q_icl, q_vap_sat,
580+
)
581+
582+
@test tendencies.dq_tot_dt == FT(0)
583+
end
584+
585+
@testset "BulkMicrophysicsTendencies 0M - S_0 type stability" begin
586+
ρ = FT(1.2)
587+
T = T_freeze + FT(5)
588+
q_lcl = FT(1e-3)
589+
q_icl = FT(5e-4)
590+
q_vap_sat = TDI.saturation_vapor_specific_content_over_liquid(tps, T, ρ)
591+
592+
tendencies = @inferred BMT.bulk_microphysics_tendencies(
593+
BMT.Microphysics0Moment(),
594+
mp, tps, T, q_lcl, q_icl, q_vap_sat,
595+
)
596+
@test tendencies isa NamedTuple{(:dq_tot_dt, :e_int_precip), NTuple{2, FT}}
597+
end
551598
end
552599

553600
function test_bulk_microphysics_2m_tendencies(FT)

test/gpu_tests.jl

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,16 @@ end
314314
output[i] = BMT.bulk_microphysics_tendencies(CM0M, mp, tps, T[i], ql, qi)
315315
end
316316

317+
@kernel inbounds = true function test_bulk_tendencies_0m_S0_kernel!(
318+
mp, tps, output, liquid_frac, qc, T, q_vap_sat,
319+
)
320+
i = @index(Global, Linear)
321+
ql = qc[i] * liquid_frac[i]
322+
qi = (1 - liquid_frac[i]) * qc[i]
323+
CM0M = BMT.Microphysics0Moment()
324+
output[i] = BMT.bulk_microphysics_tendencies(CM0M, mp, tps, T[i], ql, qi, q_vap_sat[i])
325+
end
326+
317327
@kernel inbounds = true function test_bulk_tendencies_1m_kernel!(
318328
mp, tps, output, ρ, T, q_tot, q_lcl, q_icl, q_rai, q_sno,
319329
)
@@ -934,6 +944,18 @@ function test_gpu(FT)
934944
TT.@test isfinite(tendencies.e_int_precip)
935945
end
936946

947+
# 0M S_0 tests (with q_vap_sat)
948+
(; output) = setup_output(ndrange, DT)
949+
q_vap_sat = constant_data(FT(0.01); ndrange)
950+
kernel_s0! = test_bulk_tendencies_0m_S0_kernel!(backend, work_groups)
951+
TT.@testset "0M S_0" begin
952+
kernel_s0!(mp_0m, tps, output, liquid_frac, qc, T, q_vap_sat; ndrange)
953+
TT.@test allequal(Array(output))
954+
tendencies = Array(output)[1]
955+
TT.@test tendencies.dq_tot_dt 0
956+
TT.@test isfinite(tendencies.e_int_precip)
957+
end
958+
937959

938960
# 1M tests
939961
DT = @NamedTuple{dq_lcl_dt::FT, dq_icl_dt::FT, dq_rai_dt::FT, dq_sno_dt::FT}

test/type_stability_tests.jl

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,27 @@ function run_type_stability_tests()
4040
@test getproperty(val0, k) isa FT
4141
end
4242

43+
# --- 0-Moment (S_0 mode) ---
44+
ρ_0M = fill(FT(1.2), N)
45+
q_vap_sat_0M = fill(FT(0.01), N)
46+
tendencies_0M_S0 =
47+
BMT.bulk_microphysics_tendencies.(
48+
Ref(BMT.Microphysics0Moment()),
49+
Ref(mp0),
50+
Ref(tps),
51+
T_0M,
52+
q_lcl_0M,
53+
q_icl_0M,
54+
q_vap_sat_0M,
55+
)
56+
57+
@test tendencies_0M_S0 isa Vector
58+
@test eltype(tendencies_0M_S0) <: NamedTuple
59+
val0s = tendencies_0M_S0[1]
60+
for k in keys(val0s)
61+
@test getproperty(val0s, k) isa FT
62+
end
63+
4364
# --- 1-Moment ---
4465
mp1 = CMP.Microphysics1MParams(FT)
4566
ρ = fill(FT(1.2), N)

0 commit comments

Comments
 (0)