Skip to content

Commit ec34417

Browse files
committed
fix: 2M tendencies, params
- rename returned NamedTuple for rain_evaporation tendency to (; ∂ₜρn_rai, ∂ₜq_rai) - add 2M convenience constructors, update docs - update tests accordingly
1 parent d720a15 commit ec34417

File tree

8 files changed

+229
-273
lines changed

8 files changed

+229
-273
lines changed

docs/src/plots/RainEvapoartionSB2006.jl

Lines changed: 44 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -7,49 +7,53 @@ import CloudMicrophysics.ThermodynamicsInterface as TDI
77
import CloudMicrophysics.Parameters as CMP
88
import CloudMicrophysics.Common as CO
99
import CloudMicrophysics.Microphysics2M as CM2
10+
import CloudMicrophysics.Utilities as UT
1011

1112
FT = Float64
1213

13-
const tps = TDI.TD.Parameters.ThermodynamicsParameters(FT)
14-
const aps = CMP.AirProperties(FT)
15-
const SB2006 = CMP.SB2006(FT)
14+
tps = TDI.TD.Parameters.ThermodynamicsParameters(FT)
15+
aps = CMP.AirProperties(FT)
16+
SB2006 = CMP.SB2006(FT)
1617

1718
function rain_evaporation_CPU(SB2006, aps, tps, qₜ, qₗ, qᵢ, qᵣ, qₛ, ρ, Nᵣ, T)
19+
ϵₘ, ϵₙ = UT.ϵ_numberics_2M_MN_tuple(FT)
1820

19-
evap_rate_0 = FT(0)
20-
evap_rate_1 = FT(0)
21+
∂ₜρn_rai = zero(qᵣ)
22+
∂ₜq_rai = zero(qᵣ)
2123
S = TDI.supersaturation_over_liquid(tps, qₜ, qₗ + qᵣ, qᵢ + qₛ, ρ, T)
2224

23-
if (qᵣ > FT(0) && S < FT(0))
25+
# If there are no raindrops or conditions are supersaturated, no evaporation occurs
26+
(Nᵣ ϵₙ || S 0) && return (; ∂ₜρn_rai, ∂ₜq_rai)
2427

25-
(; ν_air, D_vapor) = aps
26-
(; av, bv, α, β, ρ0) = SB2006.evap
27-
ρw = SB2006.pdf_r.ρw
28-
x_star = SB2006.pdf_r.xr_min
29-
G = CO.G_func_liquid(aps, tps, T)
28+
(; ν_air, D_vapor) = aps
29+
(; av, bv, α, β, ρ0) = SB2006.evap
30+
ρw = SB2006.pdf_r.ρw
31+
x_star = SB2006.pdf_r.xr_min
32+
G = CO.G_func_liquid(aps, tps, T)
3033

31-
(; xr_mean) = CM2.pdf_rain_parameters(SB2006.pdf_r, qᵣ, ρ, Nᵣ)
32-
Dr = (FT(6) / FT(π) / ρw)^FT(1 / 3) * xr_mean^FT(1 / 3)
34+
(; xr_mean) = CM2.pdf_rain_parameters(SB2006.pdf_r, qᵣ, ρ, Nᵣ)
35+
Dr = cbrt(6 * xr_mean / * ρw))
3336

34-
t_star = (FT(6) * x_star / xr_mean)^FT(1 / 3)
35-
a_vent_0 = av * FT(SF.gamma(-1, t_star)) / FT(6)^FT(-2 / 3)
36-
b_vent_0 =
37-
bv * FT(SF.gamma((-1 / 2) + (3 / 2) * β, t_star)) /
38-
FT(6)^FT/ 2 - 1 / 2)
37+
t_star = cbrt(6 * x_star / xr_mean)
38+
a_vent_0 = av * SF.gamma(-1, t_star) / FT(6)^(-2 // 3)
39+
b_vent_0 = bv * SF.gamma((-1 // 2) + 3 // 2 * β, t_star) / FT(6)^// 2 - 1 // 2)
3940

40-
a_vent_1 = av * SF.gamma(FT(2)) / FT(6)^FT(1 / 3)
41-
b_vent_1 =
42-
bv * SF.gamma(FT(5 / 2) + FT(3 / 2) * β) / FT(6)^FT/ 2 + 1 / 2)
41+
a_vent_1 = av * SF.gamma(FT(2)) / cbrt(FT(6))
42+
b_vent_1 = bv * SF.gamma(5 // 2 + 3 // 2 * β) / FT(6)^// 2 + 1 // 2)
4343

44-
N_Re = α * xr_mean^β * sqrt(ρ0 / ρ) * Dr / ν_air
45-
Fv0 = a_vent_0 + b_vent_0 * (ν_air / D_vapor)^FT(1 / 3) * sqrt(N_Re)
46-
Fv1 = a_vent_1 + b_vent_1 * (ν_air / D_vapor)^FT(1 / 3) * sqrt(N_Re)
44+
N_Re = α * xr_mean^β * sqrt(ρ0 / ρ) * Dr / ν_air
45+
Fv0 = a_vent_0 + b_vent_0 * cbrt(ν_air / D_vapor) * sqrt(N_Re)
46+
Fv1 = a_vent_1 + b_vent_1 * cbrt(ν_air / D_vapor) * sqrt(N_Re)
4747

48-
evap_rate_0 = min(FT(0), FT(2) * FT(π) * G * S * Nᵣ * Dr * Fv0 / xr_mean)
49-
evap_rate_1 = min(FT(0), FT(2) * FT(π) * G * S * Nᵣ * Dr * Fv1 / ρ)
50-
end
48+
∂ₜρn_rai = min(0, 2 * FT(π) * G * S * Nᵣ * Dr * Fv0 / xr_mean)
49+
∂ₜq_rai = min(0, 2 * FT(π) * G * S * Nᵣ * Dr * Fv1 / ρ)
5150

52-
return (; evap_rate_0, evap_rate_1)
51+
# When xr = 0 ∂ₜρn_rai becomes NaN. We replace NaN with 0 which is the limit of
52+
# ∂ₜρn_rai for xr -> 0.
53+
∂ₜρn_rai = ifelse(xr_mean / x_star < eps(FT), FT(0), ∂ₜρn_rai)
54+
∂ₜq_rai = ifelse(qᵣ < ϵₘ, FT(0), ∂ₜq_rai)
55+
56+
return (; ∂ₜρn_rai, ∂ₜq_rai)
5357
end
5458

5559
qᵥ = FT(1e-2)
@@ -67,21 +71,21 @@ Nᵣ_range = range(1e6, stop = 1e9, length = 1000)
6771
T_range = range(273.15, stop = 273.15 + 50, length = 1000)
6872

6973
#! format: off
70-
evap_qᵣ_0 = [rain_evaporation_CPU(SB2006, aps, tps, qₜ, qₗ, qᵢ, _qᵣ, qₛ, ρ, Nᵣ, T).evap_rate_0 for _qᵣ in qᵣ_range]
71-
evap_Nᵣ_0 = [rain_evaporation_CPU(SB2006, aps, tps, qₜ, qₗ, qᵢ, qᵣ, qₛ, ρ, _Nᵣ, T).evap_rate_0 for _Nᵣ in Nᵣ_range]
72-
evap_T_0 = [rain_evaporation_CPU(SB2006, aps, tps, qₜ, qₗ, qᵢ, qᵣ, qₛ, ρ, Nᵣ, _T).evap_rate_0 for _T in T_range]
74+
evap_qᵣ_0 = [rain_evaporation_CPU(SB2006, aps, tps, qₜ, qₗ, qᵢ, _qᵣ, qₛ, ρ, Nᵣ, T).∂ₜρn_rai for _qᵣ in qᵣ_range]
75+
evap_Nᵣ_0 = [rain_evaporation_CPU(SB2006, aps, tps, qₜ, qₗ, qᵢ, qᵣ, qₛ, ρ, _Nᵣ, T).∂ₜρn_rai for _Nᵣ in Nᵣ_range]
76+
evap_T_0 = [rain_evaporation_CPU(SB2006, aps, tps, qₜ, qₗ, qᵢ, qᵣ, qₛ, ρ, Nᵣ, _T).∂ₜρn_rai for _T in T_range]
7377

74-
evap_qᵣ_0n = [CM2.rain_evaporation(SB2006, aps, tps, qₜ, qₗ, qᵢ, _qᵣ, qₛ, ρ, Nᵣ, T).evap_rate_0 for _qᵣ in qᵣ_range]
75-
evap_Nᵣ_0n = [CM2.rain_evaporation(SB2006, aps, tps, qₜ, qₗ, qᵢ, qᵣ, qₛ, ρ, _Nᵣ, T).evap_rate_0 for _Nᵣ in Nᵣ_range]
76-
evap_T_0n = [CM2.rain_evaporation(SB2006, aps, tps, qₜ, qₗ, qᵢ, qᵣ, qₛ, ρ, Nᵣ, _T).evap_rate_0 for _T in T_range]
78+
evap_qᵣ_0n = [CM2.rain_evaporation(SB2006, aps, tps, qₜ, qₗ, qᵢ, _qᵣ, qₛ, ρ, Nᵣ, T).∂ₜρn_rai for _qᵣ in qᵣ_range]
79+
evap_Nᵣ_0n = [CM2.rain_evaporation(SB2006, aps, tps, qₜ, qₗ, qᵢ, qᵣ, qₛ, ρ, _Nᵣ, T).∂ₜρn_rai for _Nᵣ in Nᵣ_range]
80+
evap_T_0n = [CM2.rain_evaporation(SB2006, aps, tps, qₜ, qₗ, qᵢ, qᵣ, qₛ, ρ, Nᵣ, _T).∂ₜρn_rai for _T in T_range]
7781

78-
evap_qᵣ_3 = [rain_evaporation_CPU(SB2006, aps, tps, qₜ, qₗ, qᵢ, _qᵣ, qₛ, ρ, Nᵣ, T).evap_rate_1 for _qᵣ in qᵣ_range]
79-
evap_Nᵣ_3 = [rain_evaporation_CPU(SB2006, aps, tps, qₜ, qₗ, qᵢ, qᵣ, qₛ, ρ, _Nᵣ, T).evap_rate_1 for _Nᵣ in Nᵣ_range]
80-
evap_T_3 = [rain_evaporation_CPU(SB2006, aps, tps, qₜ, qₗ, qᵢ, qᵣ, qₛ, ρ, Nᵣ, _T).evap_rate_1 for _T in T_range]
82+
evap_qᵣ_3 = [rain_evaporation_CPU(SB2006, aps, tps, qₜ, qₗ, qᵢ, _qᵣ, qₛ, ρ, Nᵣ, T).∂ₜq_rai for _qᵣ in qᵣ_range]
83+
evap_Nᵣ_3 = [rain_evaporation_CPU(SB2006, aps, tps, qₜ, qₗ, qᵢ, qᵣ, qₛ, ρ, _Nᵣ, T).∂ₜq_rai for _Nᵣ in Nᵣ_range]
84+
evap_T_3 = [rain_evaporation_CPU(SB2006, aps, tps, qₜ, qₗ, qᵢ, qᵣ, qₛ, ρ, Nᵣ, _T).∂ₜq_rai for _T in T_range]
8185

82-
evap_qᵣ_3n = [CM2.rain_evaporation(SB2006, aps, tps, qₜ, qₗ, qᵢ, _qᵣ, qₛ, ρ, Nᵣ, T).evap_rate_1 for _qᵣ in qᵣ_range]
83-
evap_Nᵣ_3n = [CM2.rain_evaporation(SB2006, aps, tps, qₜ, qₗ, qᵢ, qᵣ, qₛ, ρ, _Nᵣ, T).evap_rate_1 for _Nᵣ in Nᵣ_range]
84-
evap_T_3n = [CM2.rain_evaporation(SB2006, aps, tps, qₜ, qₗ, qᵢ, qᵣ, qₛ, ρ, Nᵣ, _T).evap_rate_1 for _T in T_range]
86+
evap_qᵣ_3n = [CM2.rain_evaporation(SB2006, aps, tps, qₜ, qₗ, qᵢ, _qᵣ, qₛ, ρ, Nᵣ, T).∂ₜq_rai for _qᵣ in qᵣ_range]
87+
evap_Nᵣ_3n = [CM2.rain_evaporation(SB2006, aps, tps, qₜ, qₗ, qᵢ, qᵣ, qₛ, ρ, _Nᵣ, T).∂ₜq_rai for _Nᵣ in Nᵣ_range]
88+
evap_T_3n = [CM2.rain_evaporation(SB2006, aps, tps, qₜ, qₗ, qᵢ, qᵣ, qₛ, ρ, Nᵣ, _T).∂ₜq_rai for _T in T_range]
8589

8690
fig = MK.Figure(resolution = (800, 600))
8791

src/BulkMicrophysicsTendencies.jl

Lines changed: 40 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -100,16 +100,8 @@ end
100100

101101
"""
102102
bulk_microphysics_tendencies(
103-
::Microphysics1Moment,
104-
mp,
105-
tps,
106-
ρ,
107-
T,
108-
q_tot,
109-
q_lcl,
110-
q_icl,
111-
q_rai,
112-
q_sno,
103+
::Microphysics1Moment, mp, tps,
104+
ρ, T, q_tot, q_lcl, q_icl, q_rai, q_sno,
113105
)
114106
115107
Compute all 1-moment microphysics tendencies in one fused call.
@@ -156,17 +148,8 @@ This is a pure function of local thermodynamic state, suitable for:
156148
Limiters depend on timestep `dt` and should be applied by the caller after computing tendencies.
157149
"""
158150
@inline function bulk_microphysics_tendencies(
159-
::Microphysics1Moment,
160-
mp::CMP.Microphysics1MParams,
161-
tps,
162-
ρ,
163-
T,
164-
q_tot,
165-
q_lcl,
166-
q_icl,
167-
q_rai,
168-
q_sno,
169-
N_lcl = zero(ρ),
151+
::Microphysics1Moment, mp::CMP.Microphysics1MParams, tps,
152+
ρ, T, q_tot, q_lcl, q_icl, q_rai, q_sno, N_lcl = zero(ρ),
170153
)
171154
# Clamp negative inputs to zero (robustness against numerical errors)
172155
ρ = UT.clamp_to_nonneg(ρ)
@@ -543,12 +526,8 @@ the second form uses the supersaturation threshold `S_0 * q_vap_sat`.
543526
- Does NOT include geopotential (caller adds Φ for energy tendency)
544527
"""
545528
@inline function bulk_microphysics_tendencies(
546-
::Microphysics0Moment,
547-
mp::CMP.Microphysics0MParams,
548-
tps,
549-
T,
550-
q_lcl,
551-
q_icl,
529+
::Microphysics0Moment, mp::CMP.Microphysics0MParams, tps,
530+
T, q_lcl, q_icl,
552531
)
553532
q_lcl = UT.clamp_to_nonneg(q_lcl)
554533
q_icl = UT.clamp_to_nonneg(q_icl)
@@ -598,7 +577,7 @@ Used by both warm-only and warm+ice dispatch methods to reduce code duplication.
598577
- `dn_lcl_dt`: Cloud number tendency (1/kg/s)
599578
- `dn_rai_dt`: Rain number tendency (1/kg/s)
600579
"""
601-
@inline function warm_rain_tendencies_2m(sb, q_lcl, q_rai, ρ, n_lcl, n_rai)
580+
@inline function warm_rain_tendencies_2m(sb, q_tot, q_lcl, q_rai, q_ice, ρ, n_lcl, n_rai)
602581
# Convert to number densities for CM2 functions
603582
N_lcl = ρ * n_lcl
604583
N_rai = ρ * n_rai
@@ -610,6 +589,17 @@ Used by both warm-only and warm+ice dispatch methods to reduce code duplication.
610589
dn_lcl_dt = zero(FT)
611590
dn_rai_dt = zero(FT)
612591

592+
# --- Condensation of vapor / evaporation of cloud liquid water ---
593+
∂ₜq_lcl_cond = CMNonEq.conv_q_vap_to_q_lcl_icl_MM2015(lcl, tps, q_tot, q_lcl, q_ice, q_rai, zero(q_ice), ρ, T)
594+
∂ₜn_lcl_cond = zero(∂ₜq_lcl_cond) # neglect number change from condensation/evaporation
595+
dq_lcl_dt += ∂ₜq_lcl_cond
596+
dn_lcl_dt += ∂ₜn_lcl_cond
597+
598+
# --- Evaporation of rain ---
599+
evap = CM2.rain_evaporation(sb, aps, tps, q_tot, q_lcl, q_ice, q_rai, zero(q_ice), ρ, N_rai, T)
600+
dq_rai_dt += evap.∂ₜq_rai
601+
dn_rai_dt += evap.∂ₜρn_rai / ρ
602+
613603
# --- Autoconversion ---
614604
acnv = CM2.autoconversion(sb.acnv, sb.pdf_c, q_lcl, q_rai, ρ, N_lcl)
615605
dq_lcl_dt += acnv.dq_lcl_dt
@@ -635,6 +625,17 @@ Used by both warm-only and warm+ice dispatch methods to reduce code duplication.
635625
dn_rai_br = CM2.rain_breakup(sb.pdf_r, sb.brek, q_rai, ρ, N_rai, dn_rai_sc)
636626
dn_rai_dt += dn_rai_br / ρ
637627

628+
# --- Number adjustment for mass limits ---
629+
# Cloud liquid
630+
dn_lcl_inc = CM2.number_increase_for_mass_limit(sb.numadj, sb.pdf_c.xc_max, q_lcl, ρ, N_lcl)
631+
dn_lcl_dec = CM2.number_decrease_for_mass_limit(sb.numadj, sb.pdf_c.xc_min, q_lcl, ρ, N_lcl)
632+
dn_lcl_dt += (dn_lcl_inc + dn_lcl_dec) / ρ
633+
634+
# Rain
635+
dn_rai_inc = CM2.number_increase_for_mass_limit(sb.numadj, sb.pdf_r.xr_max, q_rai, ρ, N_rai)
636+
dn_rai_dec = CM2.number_decrease_for_mass_limit(sb.numadj, sb.pdf_r.xr_min, q_rai, ρ, N_rai)
637+
dn_rai_dt += (dn_rai_inc + dn_rai_dec) / ρ
638+
638639
return (; dq_lcl_dt, dq_rai_dt, dn_lcl_dt, dn_rai_dt)
639640
end
640641

@@ -645,7 +646,7 @@ end
645646
bulk_microphysics_tendencies(
646647
::Microphysics2Moment,
647648
mp::Microphysics2MParams{FT, WR, Nothing},
648-
...
649+
ρ, T, q_tot, q_lcl, n_lcl, q_rai, n_rai,
649650
)
650651
651652
Compute 2-moment **warm rain only** microphysics tendencies (Seifert-Beheng 2006).
@@ -675,21 +676,9 @@ For warm rain + P3 ice, see the method that accepts `Microphysics2MParams{FT, WR
675676
- `db_rim_dt`: Rime volume tendency (always zero for warm-only)
676677
"""
677678
@inline function bulk_microphysics_tendencies(
678-
::Microphysics2Moment,
679-
mp::CMP.Microphysics2MParams{FT, WR, Nothing},
680-
tps,
681-
ρ,
682-
T,
683-
q_tot,
684-
q_lcl,
685-
n_lcl,
686-
q_rai,
687-
n_rai,
688-
q_ice = zero(ρ),
689-
n_ice = zero(ρ),
690-
q_rim = zero(ρ),
691-
b_rim = zero(ρ),
692-
logλ = zero(ρ),
679+
::Microphysics2Moment, mp::CMP.Microphysics2MParams{FT, WR, Nothing}, tps,
680+
ρ, T, q_tot, q_lcl, n_lcl, q_rai, n_rai,
681+
q_ice = zero(ρ), n_ice = zero(ρ), q_rim = zero(ρ), b_rim = zero(ρ), logλ = zero(ρ),
693682
) where {FT, WR}
694683
# Clamp negative inputs to zero (robustness against numerical errors)
695684
ρ = UT.clamp_to_nonneg(ρ)
@@ -705,48 +694,28 @@ For warm rain + P3 ice, see the method that accepts `Microphysics2MParams{FT, WR
705694

706695
# Unpack warm rain parameters
707696
sb = mp.warm_rain.seifert_beheng
708-
aps = mp.warm_rain.air_properties
709697

710698
# Initialize ice-related tendencies (always zero for warm-only)
711699
dq_ice_dt = zero(ρ)
712700
dq_rim_dt = zero(ρ)
713701
db_rim_dt = zero(ρ)
714702

715703
# --- Core Warm Rain Processes (shared helper) ---
716-
warm = warm_rain_tendencies_2m(sb, q_lcl, q_rai, ρ, n_lcl, n_rai)
704+
warm = warm_rain_tendencies_2m(sb, q_tot, q_lcl, q_rai, q_ice, ρ, n_lcl, n_rai)
717705
dq_lcl_dt = warm.dq_lcl_dt
718706
dn_lcl_dt = warm.dn_lcl_dt
719707
dq_rai_dt = warm.dq_rai_dt
720708
dn_rai_dt = warm.dn_rai_dt
721709

722-
# Convert to number densities for remaining functions
723-
N_lcl = ρ * n_lcl
724-
N_rai = ρ * n_rai
725-
726-
# --- Rain evaporation ---
727-
evap = CM2.rain_evaporation(sb, aps, tps, zero(ρ), q_lcl, zero(ρ), q_rai, zero(ρ), ρ, N_rai, T)
728-
dq_rai_dt += evap.evap_rate_1
729-
dn_rai_dt += evap.evap_rate_0 / ρ
730-
731-
# --- Number adjustment for mass limits ---
732-
# Cloud liquid
733-
dn_lcl_inc = CM2.number_increase_for_mass_limit(sb.numadj, sb.pdf_c.xc_max, q_lcl, ρ, N_lcl)
734-
dn_lcl_dec = CM2.number_decrease_for_mass_limit(sb.numadj, sb.pdf_c.xc_min, q_lcl, ρ, N_lcl)
735-
dn_lcl_dt += (dn_lcl_inc + dn_lcl_dec) / ρ
736-
737-
# Rain
738-
dn_rai_inc = CM2.number_increase_for_mass_limit(sb.numadj, sb.pdf_r.xr_max, q_rai, ρ, N_rai)
739-
dn_rai_dec = CM2.number_decrease_for_mass_limit(sb.numadj, sb.pdf_r.xr_min, q_rai, ρ, N_rai)
740-
dn_rai_dt += (dn_rai_inc + dn_rai_dec) / ρ
741-
742710
return (; dq_lcl_dt, dn_lcl_dt, dq_rai_dt, dn_rai_dt, dq_ice_dt, dq_rim_dt, db_rim_dt)
743711
end
744712

745713
"""
746714
bulk_microphysics_tendencies(
747715
::Microphysics2Moment,
748716
mp::Microphysics2MParams{FT, WR, <:P3IceParams},
749-
...
717+
ρ, T, q_tot, q_lcl, n_lcl, q_rai, n_rai,
718+
q_ice, n_ice, q_rim, b_rim, logλ,
750719
)
751720
752721
Compute 2-moment **warm rain + P3 ice** microphysics tendencies.
@@ -784,21 +753,9 @@ to be non-Nothing, eliminating runtime type checks and dynamic dispatch.
784753
- `db_rim_dt`: Rime volume tendency (m³/kg/s)
785754
"""
786755
@inline function bulk_microphysics_tendencies(
787-
::Microphysics2Moment,
788-
mp::CMP.Microphysics2MParams{FT, WR, ICE},
789-
tps,
790-
ρ,
791-
T,
792-
q_tot,
793-
q_lcl,
794-
n_lcl,
795-
q_rai,
796-
n_rai,
797-
q_ice = zero(ρ),
798-
n_ice = zero(ρ),
799-
q_rim = zero(ρ),
800-
b_rim = zero(ρ),
801-
logλ = zero(ρ),
756+
::Microphysics2Moment, mp::CMP.Microphysics2MParams{FT, WR, ICE}, tps,
757+
ρ, T, q_tot, q_lcl, n_lcl, q_rai, n_rai,
758+
q_ice = zero(ρ), n_ice = zero(ρ), q_rim = zero(ρ), b_rim = zero(ρ), logλ = zero(ρ),
802759
) where {FT, WR, ICE <: CMP.P3IceParams}
803760
# Clamp negative inputs to zero (robustness against numerical errors)
804761
ρ = UT.clamp_to_nonneg(ρ)
@@ -824,7 +781,7 @@ to be non-Nothing, eliminating runtime type checks and dynamic dispatch.
824781
db_rim_dt = zero(ρ)
825782

826783
# --- Core Warm Rain Processes (shared helper) ---
827-
warm = warm_rain_tendencies_2m(sb, q_lcl, q_rai, ρ, n_lcl, n_rai)
784+
warm = warm_rain_tendencies_2m(sb, q_tot, q_lcl, q_rai, q_ice, ρ, n_lcl, n_rai)
828785
dq_lcl_dt = warm.dq_lcl_dt
829786
dn_lcl_dt = warm.dn_lcl_dt
830787
dq_rai_dt = warm.dq_rai_dt
@@ -834,26 +791,7 @@ to be non-Nothing, eliminating runtime type checks and dynamic dispatch.
834791
N_lcl = ρ * n_lcl
835792
N_rai = ρ * n_rai
836793

837-
# --- Rain evaporation ---
838-
evap = CM2.rain_evaporation(sb, aps, tps, zero(ρ), q_lcl, zero(ρ), q_rai, zero(ρ), ρ, N_rai, T)
839-
dq_rai_dt += evap.evap_rate_1
840-
dn_rai_dt += evap.evap_rate_0 / ρ
841-
842-
# --- Number adjustment for mass limits ---
843-
# Cloud liquid
844-
dn_lcl_inc = CM2.number_increase_for_mass_limit(sb.numadj, sb.pdf_c.xc_max, q_lcl, ρ, N_lcl)
845-
dn_lcl_dec = CM2.number_decrease_for_mass_limit(sb.numadj, sb.pdf_c.xc_min, q_lcl, ρ, N_lcl)
846-
dn_lcl_dt += (dn_lcl_inc + dn_lcl_dec) / ρ
847-
848-
# Rain
849-
dn_rai_inc = CM2.number_increase_for_mass_limit(sb.numadj, sb.pdf_r.xr_max, q_rai, ρ, N_rai)
850-
dn_rai_dec = CM2.number_decrease_for_mass_limit(sb.numadj, sb.pdf_r.xr_min, q_rai, ρ, N_rai)
851-
dn_rai_dt += (dn_rai_inc + dn_rai_dec) / ρ
852-
853794
# --- P3 Ice Processes ---
854-
# NOTE: P3 uses gamma_inc_inv from SpecialFunctions which is NOT GPU-compatible
855-
# (it uses string formatting for errors). We must keep if-branches to skip
856-
# P3 code when there is no ice, otherwise GPU compilation fails.
857795
p3 = mp.ice.scheme
858796
vel = mp.ice.terminal_velocity
859797
pdf_c = mp.ice.cloud_pdf

src/Common.jl

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,22 @@ See e.g., Seifert and Beheng (2006), https://doi.org/10.1007/s00703-005-0112-4,
463463
464464
# Returns
465465
- `F_v(D)`: ventilation factor function (dimensionless)
466+
467+
# Extended help
468+
469+
The ventilation factor is given by
470+
471+
```math
472+
F_v(D) = a_v + b_v ⋅ ∛Sc ⋅ √Re(D)
473+
```
474+
475+
where
476+
477+
```math
478+
Sc = ν_{air} / D_{vapor}
479+
Re(D) = D * v_{term}(D) / ν_{air}
480+
```
481+
466482
"""
467483
@inline function ventilation_factor(vent, aps, v_term)
468484
(; aᵥ, bᵥ) = vent

0 commit comments

Comments
 (0)