@@ -32,7 +32,9 @@ import ..Microphysics1M as CM1
3232import .. Microphysics2M as CM2
3333import .. MicrophysicsNonEq as CMNonEq
3434import .. P3Scheme as CMP3
35+ import .. HetIceNucleation as CM_HetIce
3536import ... ThermodynamicsInterface as TDI
37+ import .. Common as CO
3638
3739export MicrophysicsScheme,
3840 Microphysics0Moment,
@@ -801,9 +803,14 @@ to be non-Nothing, eliminating runtime type checks and dynamic dispatch.
801803"""
802804@inline function bulk_microphysics_tendencies (
803805 :: Microphysics2Moment , mp:: CMP.Microphysics2MParams{WR, ICE} , tps,
804- ρ, T, q_tot, q_lcl, n_lcl, q_rai, n_rai,
805- q_ice = zero (ρ), n_ice = zero (ρ), q_rim = zero (ρ), b_rim = zero (ρ), logλ = zero (ρ),
806+ ρ, T, q_tot,
807+ q_lcl, n_lcl, q_rai, n_rai,
808+ q_ice, n_ice, q_rim, b_rim, logλ,
806809) where {WR, ICE <: CMP.P3IceParams }
810+ FT = eltype (ρ)
811+ ϵₘ = UT. ϵ_numerics_2M_M (FT)
812+ ϵₙ = UT. ϵ_numerics_2M_N (FT)
813+ ϵB = UT. ϵ_numerics_P3_B (FT)
807814 # Clamp negative inputs to zero (robustness against numerical errors)
808815 ρ = UT. clamp_to_nonneg (ρ)
809816 q_tot = UT. clamp_to_nonneg (q_tot)
@@ -816,100 +823,133 @@ to be non-Nothing, eliminating runtime type checks and dynamic dispatch.
816823 q_rim = UT. clamp_to_nonneg (q_rim)
817824 b_rim = UT. clamp_to_nonneg (b_rim)
818825
819- # Unpack warm rain parameters (always present)
826+ # Convert to volumetric quantities for P3 functions
827+ L_lcl = q_lcl * ρ # [kg lcl / m³ air]
828+ L_rai = q_rai * ρ # [kg rai / m³ air]
829+ N_lcl = n_lcl * ρ # [1 / m³ air]
830+ N_rai = n_rai * ρ # [1 / m³ air]
831+ L_ice = q_ice * ρ # [kg ice / m³ air]
832+ N_ice = n_ice * ρ # [1 / m³ air]
833+ L_rim = q_rim * ρ # [kg rim / m³ air]
834+ B_rim = b_rim * ρ # [m³ rim / m³ air]
835+ # Compute rime fraction and density
836+ F_rim = ifelse (q_ice > ϵₘ, q_rim / q_ice, zero (L_rim))
837+ ρ_rim = ifelse (b_rim > ϵB, q_rim / b_rim, zero (L_rim)) # [kg rim / m³ rim]
838+ state = CMP3. P3State (mp. ice. scheme, L_ice, N_ice, F_rim, ρ_rim)
839+
840+ # Unpack warm rain parameters
820841 aps = mp. warm_rain. air_properties
842+ subdep = mp. warm_rain. subdep
821843
822844 # Initialize ice-related tendencies
823845 dq_ice_dt = zero (ρ)
824- # TODO : When ice number concentration becomes prognostic, add:
825- # dn_ice_dt = zero(ρ) # Ice number tendency (changes due to melting, aggregation)
846+ dn_ice_dt = zero (ρ)
826847 dq_rim_dt = zero (ρ)
827848 db_rim_dt = zero (ρ)
828849
829- # --- Core Warm Rain Processes (shared helper) ---
850+ # --- Core Warm Rain Processes ---
830851 warm = warm_rain_tendencies_2m (mp. warm_rain, tps, T, q_tot, q_lcl, q_rai, q_ice, ρ, n_lcl, n_rai)
831852 dq_lcl_dt = warm. dq_lcl_dt
832853 dn_lcl_dt = warm. dn_lcl_dt
833854 dq_rai_dt = warm. dq_rai_dt
834855 dn_rai_dt = warm. dn_rai_dt
835856
836- # Convert to number densities for remaining functions
837- N_lcl = ρ * n_lcl
838- N_rai = ρ * n_rai
839-
840857 # --- P3 Ice Processes ---
841858 p3 = mp. ice. scheme
842859 vel = mp. ice. terminal_velocity
843860 pdf_c = mp. ice. cloud_pdf
844861 pdf_r = mp. ice. rain_pdf
862+ ice_nucleation = mp. ice. ice_nucleation
863+
845864
846865 # Only compute ice processes if there is ice mass/number present
847- if (q_ice > zero (q_ice) || n_ice > zero (n_ice))
848- # Convert to volumetric quantities for P3 functions
849- L_ice = q_ice * ρ # [kg/m³]
850- N_ice = n_ice * ρ # [1/m³]
851- L_lcl = q_lcl * ρ # [kg/m³]
852- N_lcl = n_lcl * ρ # [1/m³]
853- L_rai = q_rai * ρ # [kg/m³]
854- N_rai = n_rai * ρ # [1/m³]
855-
856- # Compute rime fraction and density
857- F_rim = ifelse (q_ice > zero (q_ice), q_rim / q_ice, zero (q_rim))
858- ρ_rim = ifelse (b_rim > zero (b_rim), q_rim * ρ / (b_rim * ρ), ρ) # [kg/m³]
859-
860- # Liquid-ice collision sources (core P3 process)
861- # Only compute if there is ice present
862- if L_ice > zero (L_ice) && N_ice > zero (N_ice)
863- coll = CMP3. bulk_liquid_ice_collision_sources (
864- p3,
865- logλ,
866- L_ice,
867- N_ice,
868- F_rim,
869- ρ_rim,
870- pdf_c,
871- pdf_r,
872- L_lcl,
873- N_lcl,
874- L_rai,
875- N_rai,
876- aps,
877- tps,
878- vel,
879- ρ,
880- T,
881- )
882-
883- # Add collision tendencies
884- dq_lcl_dt += coll.∂ₜq_c
885- dq_rai_dt += coll.∂ₜq_r
886- dn_lcl_dt += coll.∂ₜN_c / ρ
887- dn_rai_dt += coll.∂ₜN_r / ρ
888- dq_ice_dt += coll.∂ₜL_ice / ρ
889- # TODO : When P3 collision sources return ∂ₜN_ice (aggregation, etc.), add:
890- # dn_ice_dt += coll.∂ₜN_ice / ρ
891- dq_rim_dt += coll.∂ₜL_rim / ρ
892- db_rim_dt += coll.∂ₜB_rim / ρ
893- end
866+ if q_ice > ϵₘ && n_ice > ϵₙ
867+
868+ # --- Liquid-ice collisions ---
869+ coll = CMP3. bulk_liquid_ice_collision_sources (
870+ state, logλ, pdf_c, pdf_r, L_lcl, N_lcl, L_rai, N_rai, aps, tps, vel, ρ, T,
871+ )
872+ dq_lcl_dt += coll.∂ₜq_c
873+ dq_rai_dt += coll.∂ₜq_r
874+ dn_lcl_dt += coll.∂ₜN_c / ρ
875+ dn_rai_dt += coll.∂ₜN_r / ρ
876+ dq_ice_dt += coll.∂ₜL_ice / ρ
877+ dq_rim_dt += coll.∂ₜL_rim / ρ
878+ db_rim_dt += coll.∂ₜB_rim / ρ
879+
880+ # --- Ice self-collection (aggregation) ---
881+ S_ice_agg = CMP3. ice_self_collection (state, logλ, aps, tps, vel, ρ, T)
882+ dn_ice_dt -= S_ice_agg. dNdt / ρ
894883
895884 # Ice melting (above freezing temperature)
896885 T_freeze = TDI. TD. Parameters. T_freeze (tps)
897- if T > T_freeze && L_ice > zero (L_ice)
898- state = CMP3. P3State (p3, L_ice, N_ice, F_rim, ρ_rim)
899- melt = CMP3. ice_melt (vel, aps, tps, T, ρ, state, logλ)
900-
901- # Melting converts ice to rain
902- dq_ice_dt -= melt. dLdt / ρ
903- dq_rai_dt += melt. dLdt / ρ
904- # TODO : When ice number concentration is tracked, add:
905- # dn_ice_dt -= melt.dNdt / ρ # Ice particles consumed by melting
906- dn_rai_dt += melt. dNdt / ρ # Melted ice becomes rain drops
907- end
886+ melt = ifelse (T > T_freeze,
887+ CMP3. ice_melt (vel, aps, tps, T, ρ, state, logλ),
888+ (; dNdt = zero (ρ), dLdt = zero (ρ))
889+ )
890+ # Melting converts ice to rain
891+ dq_rai_dt += melt. dLdt / ρ
892+ dn_rai_dt += melt. dNdt / ρ # Melted ice becomes rain drops
893+ dq_ice_dt -= melt. dLdt / ρ
894+ dn_ice_dt -= melt. dNdt / ρ # Ice particles consumed by melting
908895 end
909896
910- # TODO : When ice number concentration is tracked, add dn_ice_dt to return tuple:
911- # return (; dq_lcl_dt, dn_lcl_dt, dq_rai_dt, dn_rai_dt, dq_ice_dt, dn_ice_dt, dq_rim_dt, db_rim_dt)
912- return (; dq_lcl_dt, dn_lcl_dt, dq_rai_dt, dn_rai_dt, dq_ice_dt, dq_rim_dt, db_rim_dt)
897+ # --- --------------- ---
898+ # --- Ice Nucleation ---
899+ # --- --------------- ---
900+
901+ # Note: Intuitively, you choose one of two parameterization pathways,
902+ # either 1. process-based (e.g. using CM_HetIce.deposition_J, etc.) or
903+ # 2. empirical (e.g. using CM_HetIce.INP_concentration_mean)
904+ # 1. In the former, you try to differentiate between different aerosol types
905+ # and their properties, homogeneous freezing, heterogeneous deposition
906+ # freezing, heterogeneous immersion freezing, etc.
907+ # 2. The latter, which we adopt here, empirically relates some environmental
908+ # conditions (e.g. temperature) to the number of ice crystals nucleated.
909+ # We do not track the aerosol population, and do not consider different
910+ # nucleation pathways explicitly.
911+
912+ # --- Ice Nucleation (empirical) ---
913+ # Assume mass loss is mean condensate mass
914+ m_lcl = ifelse (n_lcl > ϵₙ, q_lcl / n_lcl, zero (q_lcl)) # mean liquid mass
915+
916+ # TODO : The parameterixation should return a rate, `∂N/∂t`, not number changes `ΔN`
917+ inpc = CM_HetIce. INP_concentration_mean (ice_nucleation, T) / ρ # [particles / kg air]
918+ τ_nuc = FT (1 ) # TODO : Make this a real tendency
919+ ∂ₜn_nuc = max (0 , (inpc - n_ice) / τ_nuc) # [particles / kg air]
920+ ∂ₜq_nuc = ∂ₜn_nuc * m_lcl # [kg ice / kg air]
921+
922+ dn_ice_dt += ∂ₜn_nuc
923+ dq_ice_dt += ∂ₜq_nuc
924+ dq_rim_dt += ∂ₜq_nuc
925+ db_rim_dt += ∂ₜq_nuc / p3. ρ_i # ρ_i = 916.7 kg m⁻³, the density of solid bulk ice
926+ dn_lcl_dt -= ∂ₜn_nuc
927+ dq_lcl_dt -= ∂ₜq_nuc
928+
929+ # --- Cloud Droplet Condensation Freezing ---
930+ # ref: `homogeneous_freezing` in `parcel/ParcelTendencies.jl`
931+ # get mean diameter of cloud droplets, then convert to volume
932+
933+ # --- Ice Sublimation / Deposition ---
934+ n_per_q_ice = ifelse (q_ice > ϵₘ, n_ice / q_ice, zero (n_ice))
935+ # Deposition/sublimation of cloud ice
936+ ∂ₜq_ice_dep = CMNonEq. conv_q_vap_to_q_lcl_icl_MM2015 (subdep, tps, q_tot, q_lcl, q_ice, q_rai, zero (q_ice), ρ, T)
937+ # No ice deposition above freezing (lack of INPs)
938+ ∂ₜq_ice_dep = ifelse (T > tps. T_freeze, min (∂ₜq_ice_dep, zero (T)), ∂ₜq_ice_dep)
939+ # During sublimation, the number of ice particles decreases in proportion to the mean ice mass
940+ # During deposition, the number of ice particles remain unchanged
941+ ∂ₜn_ice_dep = ifelse (∂ₜq_ice_dep < 0 , n_per_q_ice * ∂ₜq_ice_dep, zero (∂ₜq_ice_dep))
942+ dq_ice_dt += ∂ₜq_ice_dep
943+ dn_ice_dt += ∂ₜn_ice_dep
944+
945+ # --- Rain Heterogeneous Freezing ---
946+ # TODO : Implement heterogeneous freezing of rain
947+ # This process is currently missing in P3_processes.jl
948+ # S_rai_frz = ...
949+ # dq_rai_dt -= S_rai_frz
950+ # dq_ice_dt += S_rai_frz
951+
952+ return (; dq_lcl_dt, dn_lcl_dt, dq_rai_dt, dn_rai_dt, dq_ice_dt, dn_ice_dt, dq_rim_dt, db_rim_dt)
913953end
914954
915955end # module BulkMicrophysicsTendencies
0 commit comments