Skip to content

Commit d720a15

Browse files
authored
Added sink derivatives (#691)
* Added sink derivatives * Replace ternary by GPU performant safe division in derivatives
1 parent 58f72c4 commit d720a15

File tree

4 files changed

+180
-9
lines changed

4 files changed

+180
-9
lines changed

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.10"
4+
version = "0.31.11"
55

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

src/BulkMicrophysicsTendencies.jl

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ This is a pure function of local thermodynamic state, suitable for:
234234
dq_sno_dt += ifelse(is_warm, zero(T), S_accr_lcl_sno)
235235
dq_rai_dt += ifelse(is_warm, S_accr_lcl_sno, zero(T))
236236
# Thermal melting (α=0 when cold, so these terms vanish)
237+
# Physical consistency: melt is mass of snow melted, which is α * mass of warm liquid
237238
α = warm_accretion_melt_factor(tps, sno, T)
238239
S_accr_melt = α * S_accr_lcl_sno
239240
dq_sno_dt -= S_accr_melt
@@ -269,7 +270,8 @@ This is a pure function of local thermodynamic state, suitable for:
269270
dq_sno_dt -= ifelse(is_warm, S_accr_sno_rai, zero(T))
270271
dq_rai_dt += ifelse(is_warm, S_accr_sno_rai, zero(T))
271272
# Thermal melting from warm rain (α=0 when cold)
272-
S_accr_melt = α * S_accr_sno_rai
273+
# Physical consistency: melt is mass of snow melted, which is α * mass of warm rain
274+
S_accr_melt = α * S_accr_rai_sno
273275
dq_sno_dt -= ifelse(is_warm, S_accr_melt, zero(T))
274276
dq_rai_dt += ifelse(is_warm, S_accr_melt, zero(T))
275277

@@ -350,21 +352,61 @@ as `bulk_microphysics_tendencies`.
350352
aps = mp.air_properties
351353
vel = mp.terminal_velocity
352354

353-
# Condensation/evaporation of cloud liquid
355+
# --- Cloud liquid: condensation + sink self-derivatives ---
354356
∂tendency_∂q_lcl = CMNonEq.∂conv_q_vap_to_q_lcl_icl_MM2015_∂q_cld(lcl, tps, q_tot, q_lcl, q_icl, q_rai, q_sno, ρ, T)
357+
# Autoconversion q_lcl → q_rai (sink, derivative ∂/∂q_lcl is conservative and safe for linear-above-threshold)
358+
S_acnv_lcl = CM1.conv_q_lcl_to_q_rai(rai.acnv1M, q_lcl, true)
359+
# Accretion q_lcl + q_rai → q_rai (rate is exactly linear in q_lcl)
360+
S_accr_lcl_rai = CM1.accretion(lcl, rai, vel.rain, ce, q_lcl, q_rai, ρ)
361+
# Accretion q_lcl + q_sno → q_sno (rate is exactly linear in q_lcl)
362+
S_accr_lcl_sno = CM1.accretion(lcl, sno, vel.snow, ce, q_lcl, q_sno, ρ)
363+
∂tendency_∂q_lcl -=
364+
(S_acnv_lcl + S_accr_lcl_rai + S_accr_lcl_sno) /
365+
max(q_lcl, UT.ϵ_numerics(typeof(q_lcl)))
355366

356-
# Deposition/sublimation of cloud ice (INP limiter applied inside ∂conv_q_vap_to_q_lcl_icl_MM2015_∂q_cld)
367+
# --- Cloud ice: deposition + sink self-derivatives ---
357368
∂tendency_∂q_icl = CMNonEq.∂conv_q_vap_to_q_lcl_icl_MM2015_∂q_cld(icl, tps, q_tot, q_lcl, q_icl, q_rai, q_sno, ρ, T)
369+
# Autoconversion q_icl → q_sno
370+
S_acnv_icl = CM1.conv_q_icl_to_q_sno_no_supersat(sno.acnv1M, q_icl, true)
371+
# Accretion q_icl + q_rai → q_sno (rate is exactly linear in q_icl)
372+
S_accr_icl_rai = CM1.accretion(icl, rai, vel.rain, ce, q_icl, q_rai, ρ)
373+
# Accretion q_icl + q_sno → q_sno (rate is exactly linear in q_icl)
374+
S_accr_icl_sno = CM1.accretion(icl, sno, vel.snow, ce, q_icl, q_sno, ρ)
375+
∂tendency_∂q_icl -=
376+
(S_acnv_icl + S_accr_icl_rai + S_accr_icl_sno) /
377+
max(q_icl, UT.ϵ_numerics(typeof(q_icl)))
358378

359-
# Rain evaporation
379+
# --- Rain: evaporation + sink self-derivatives ---
360380
∂tendency_∂q_rai =
361381
CM1.∂evaporation_sublimation_∂q_precip(rai, vel.rain, aps, tps, q_tot, q_lcl, q_icl, q_rai, q_sno, ρ, T)
382+
# Accretion rain_sink: ice + rain → snow (rate/q_rai approximation)
383+
S_accr_rai_icl = CM1.accretion_rain_sink(rai, icl, vel.rain, ce, q_icl, q_rai, ρ)
384+
# Snow-rain accretion (cold): rain → snow
385+
is_warm = T >= sno.T_freeze
386+
S_accr_rai_sno = ifelse(is_warm, zero(T),
387+
CM1.accretion_snow_rain(sno, rai, vel.snow, vel.rain, ce, q_sno, q_rai, ρ))
388+
∂tendency_∂q_rai -=
389+
(S_accr_rai_icl + S_accr_rai_sno) /
390+
max(q_rai, UT.ϵ_numerics(typeof(q_rai)))
362391

363-
# Snow sublimation/deposition and melting
392+
# --- Snow: sublimation/deposition + melting + sink self-derivatives ---
364393
dS_subl_sno =
365394
CM1.∂evaporation_sublimation_∂q_precip(sno, vel.snow, aps, tps, q_tot, q_lcl, q_icl, q_rai, q_sno, ρ, T)
366395
dS_melt_sno = CM1.∂snow_melt_∂q_sno(sno, vel.snow, aps, tps, q_sno, ρ, T)
367396
∂tendency_∂q_sno = dS_subl_sno - dS_melt_sno
397+
# Snow-rain accretion (warm): snow → rain
398+
S_accr_sno_rai = ifelse(is_warm,
399+
CM1.accretion_snow_rain(rai, sno, vel.rain, vel.snow, ce, q_rai, q_sno, ρ), zero(T))
400+
# Thermal melting from warm rain collision -> snow sink proportional to S_accr_rai_sno
401+
# (Since this is a diagonal derivative, we approximate ∂/∂q_sno by rate/q_sno, using the rain sink form S_accr_rai_sno)
402+
S_accr_sno_rai_melt = ifelse(is_warm,
403+
CM1.accretion_snow_rain(sno, rai, vel.snow, vel.rain, ce, q_sno, q_rai, ρ), zero(T))
404+
# Accretion melt from warm liquid-snow collision
405+
α = warm_accretion_melt_factor(tps, sno, T)
406+
S_accr_lcl_sno_melt = α * CM1.accretion(lcl, sno, vel.snow, ce, q_lcl, q_sno, ρ)
407+
∂tendency_∂q_sno -=
408+
(S_accr_sno_rai + α * S_accr_sno_rai_melt + S_accr_lcl_sno_melt) /
409+
max(q_sno, UT.ϵ_numerics(typeof(q_sno)))
368410

369411
return (; ∂tendency_∂q_lcl, ∂tendency_∂q_icl, ∂tendency_∂q_rai, ∂tendency_∂q_sno)
370412
end

src/Microphysics1M.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -722,7 +722,7 @@ The approximation used is `∂(rate)/∂q_precip ≈ rate / q_precip`.
722722
T::FT,
723723
) where {FT}
724724
rate = evaporation_sublimation(rain_params, vel, aps, tps, q_tot, q_lcl, q_icl, q_rai, q_sno, ρ, T)
725-
return q_rai > UT.ϵ_numerics(FT) ? rate / q_rai : zero(rate)
725+
return rate / max(q_rai, UT.ϵ_numerics(FT))
726726
end
727727

728728
@inline function ∂evaporation_sublimation_∂q_precip(
@@ -739,7 +739,7 @@ end
739739
T::FT,
740740
) where {FT}
741741
rate = evaporation_sublimation(snow_params, vel, aps, tps, q_tot, q_lcl, q_icl, q_rai, q_sno, ρ, T)
742-
return q_sno > UT.ϵ_numerics(FT) ? rate / q_sno : zero(rate)
742+
return rate / max(q_sno, UT.ϵ_numerics(FT))
743743
end
744744

745745
"""
@@ -815,7 +815,7 @@ The approximation used is `∂(rate)/∂q_sno ≈ rate / q_sno`.
815815
T::FT,
816816
) where {FT}
817817
rate = snow_melt(snow_params, vel, aps, tps, q_sno, ρ, T)
818-
return q_sno > UT.ϵ_numerics(FT) ? rate / q_sno : zero(rate)
818+
return rate / max(q_sno, UT.ϵ_numerics(FT))
819819
end
820820

821821
end #module Microphysics1M.jl

test/bulk_tendencies_tests.jl

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,135 @@ function test_bulk_microphysics_1m_derivatives(FT)
710710
)
711711
@test all(isfinite, derivs)
712712
end
713+
714+
@testset "BulkMicrophysicsDerivatives 1M - Cloud liquid sink derivatives" begin
715+
# With cloud liquid above autoconversion threshold and rain/snow present,
716+
# the derivative should be negative (sinks from autoconversion + accretion)
717+
ρ = FT(1.2)
718+
T = T_freeze + FT(5)
719+
q_lcl = FT(2e-3) # above autoconversion threshold
720+
q_icl = FT(0)
721+
q_rai = FT(5e-4) # rain present for accretion
722+
q_sno = FT(5e-4) # snow present for accretion
723+
q_tot = get_saturated_q_tot(tps, T, ρ, q_lcl, q_icl, q_rai, q_sno)
724+
725+
derivs = BMT.bulk_microphysics_derivatives(
726+
BMT.Microphysics1Moment(),
727+
mp, tps, ρ, T, q_tot, q_lcl, q_icl, q_rai, q_sno,
728+
)
729+
# Derivative should be negative (sink terms dominate at saturation)
730+
@test derivs.∂tendency_∂q_lcl < FT(0)
731+
732+
# Compare with no-sink case (no rain, no snow, below autoconv threshold)
733+
q_lcl_small = FT(1e-5) # below threshold
734+
q_tot_small = get_saturated_q_tot(tps, T, ρ, q_lcl_small, q_icl, FT(0), FT(0))
735+
derivs_nosink = BMT.bulk_microphysics_derivatives(
736+
BMT.Microphysics1Moment(),
737+
mp, tps, ρ, T, q_tot_small, q_lcl_small, q_icl, FT(0), FT(0),
738+
)
739+
# With sinks, derivative should be more negative
740+
@test derivs.∂tendency_∂q_lcl < derivs_nosink.∂tendency_∂q_lcl
741+
end
742+
743+
@testset "BulkMicrophysicsDerivatives 1M - Cloud ice sink derivatives" begin
744+
# With cloud ice above autoconversion threshold and snow present,
745+
# the derivative should include sink contributions
746+
ρ = FT(1.2)
747+
T = T_freeze - FT(20) # cold
748+
q_lcl = FT(0)
749+
q_icl = FT(1e-3) # above autoconversion threshold
750+
q_rai = FT(0)
751+
q_sno = FT(5e-4) # snow present for accretion
752+
q_tot = FT(0.012)
753+
754+
derivs = BMT.bulk_microphysics_derivatives(
755+
BMT.Microphysics1Moment(),
756+
mp, tps, ρ, T, q_tot, q_lcl, q_icl, q_rai, q_sno,
757+
)
758+
# Derivative should be negative (autoconversion + accretion sinks)
759+
@test derivs.∂tendency_∂q_icl < FT(0)
760+
end
761+
762+
@testset "BulkMicrophysicsDerivatives 1M - Rain sink derivatives (cold)" begin
763+
# In cold conditions with both rain and snow, rain is lost to snow
764+
ρ = FT(1.2)
765+
T = T_freeze - FT(5) # below freezing
766+
q_lcl = FT(0)
767+
q_icl = FT(1e-4) # ice present for accretion_rain_sink
768+
q_rai = FT(5e-4)
769+
q_sno = FT(5e-4)
770+
q_tot = FT(0.012)
771+
772+
derivs = BMT.bulk_microphysics_derivatives(
773+
BMT.Microphysics1Moment(),
774+
mp, tps, ρ, T, q_tot, q_lcl, q_icl, q_rai, q_sno,
775+
)
776+
# Rain derivative should be negative (evaporation not active in subsaturated,
777+
# plus accretion sinks from ice and snow collisions)
778+
@test derivs.∂tendency_∂q_rai < FT(0)
779+
end
780+
781+
@testset "BulkMicrophysicsDerivatives 1M - Snow sink derivatives (warm)" begin
782+
# In warm conditions with snow, melting and accretion sinks should make
783+
# the derivative strongly negative
784+
ρ = FT(1.2)
785+
T = T_freeze + FT(5) # above freezing
786+
q_lcl = FT(5e-4) # liquid present for accretion melt
787+
q_icl = FT(0)
788+
q_rai = FT(5e-4) # rain present for snow-rain accretion
789+
q_sno = FT(1e-3)
790+
q_tot = FT(0.015)
791+
792+
derivs = BMT.bulk_microphysics_derivatives(
793+
BMT.Microphysics1Moment(),
794+
mp, tps, ρ, T, q_tot, q_lcl, q_icl, q_rai, q_sno,
795+
)
796+
# Snow derivative should be strongly negative (melting + accretion sinks)
797+
@test derivs.∂tendency_∂q_sno < FT(0)
798+
799+
# Compare: snow derivative should be more negative in warm than cold
800+
T_cold = T_freeze - FT(10)
801+
derivs_cold = BMT.bulk_microphysics_derivatives(
802+
BMT.Microphysics1Moment(),
803+
mp, tps, ρ, T_cold, q_tot, q_lcl, q_icl, q_rai, q_sno,
804+
)
805+
@test derivs.∂tendency_∂q_sno < derivs_cold.∂tendency_∂q_sno
806+
end
807+
808+
@testset "BulkMicrophysicsDerivatives 1M - Finite difference validation" begin
809+
# Validate derivatives against finite differences for a representative state
810+
ρ = FT(1.2)
811+
T = T_freeze + FT(3) # warm, snow processes active
812+
q_lcl = FT(5e-4)
813+
q_icl = FT(0)
814+
q_rai = FT(5e-4)
815+
q_sno = FT(5e-4)
816+
q_tot = FT(0.015)
817+
818+
derivs = BMT.bulk_microphysics_derivatives(
819+
BMT.Microphysics1Moment(),
820+
mp, tps, ρ, T, q_tot, q_lcl, q_icl, q_rai, q_sno,
821+
)
822+
823+
# Finite difference for ∂(dq_sno_dt)/∂q_sno
824+
δ = FT(1e-8)
825+
tend_plus = BMT.bulk_microphysics_tendencies(
826+
BMT.Microphysics1Moment(),
827+
mp, tps, ρ, T, q_tot, q_lcl, q_icl, q_rai, q_sno + δ,
828+
)
829+
tend_minus = BMT.bulk_microphysics_tendencies(
830+
BMT.Microphysics1Moment(),
831+
mp, tps, ρ, T, q_tot, q_lcl, q_icl, q_rai, q_sno - δ,
832+
)
833+
fd_sno = (tend_plus.dq_sno_dt - tend_minus.dq_sno_dt) / (2δ)
834+
835+
# The analytic derivative should be within a factor of 3 of the finite difference
836+
# (the rate/q approximation is not exact, so we use a loose tolerance)
837+
@test derivs.∂tendency_∂q_sno < FT(0)
838+
@test fd_sno < FT(0)
839+
ratio = derivs.∂tendency_∂q_sno / fd_sno
840+
@test FT(0.1) < ratio < FT(10)
841+
end
713842
end
714843

715844
###

0 commit comments

Comments
 (0)