@@ -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_M (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,13 +823,26 @@ 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
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+
819839 # Unpack warm rain parameters (always present)
820840 aps = mp. warm_rain. air_properties
841+ subdep = mp. warm_rain. subdep
821842
822843 # Initialize ice-related tendencies
823844 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)
845+ dn_ice_dt = zero (ρ) # Ice number tendency (changes due to melting, aggregation)
826846 dq_rim_dt = zero (ρ)
827847 db_rim_dt = zero (ρ)
828848
@@ -833,51 +853,24 @@ to be non-Nothing, eliminating runtime type checks and dynamic dispatch.
833853 dq_rai_dt = warm. dq_rai_dt
834854 dn_rai_dt = warm. dn_rai_dt
835855
836- # Convert to number densities for remaining functions
837- N_lcl = ρ * n_lcl
838- N_rai = ρ * n_rai
839-
840856 # --- P3 Ice Processes ---
841857 p3 = mp. ice. scheme
842858 vel = mp. ice. terminal_velocity
843859 pdf_c = mp. ice. cloud_pdf
844860 pdf_r = mp. ice. rain_pdf
861+ heterogeneous = mp. ice. heterogeneous
862+ deposition_condfreeze = mp. ice. deposition_condfreeze
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³]
866+ if q_ice > ϵₘ && n_ice > ϵₙ
867+ state = CMP3. P3State (p3, L_ice, N_ice, F_rim, ρ_rim)
859868
860869 # Liquid-ice collision sources (core P3 process)
861870 # Only compute if there is ice present
862871 if L_ice > zero (L_ice) && N_ice > zero (N_ice)
863872 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,
873+ state, logλ, pdf_c, pdf_r, L_lcl, N_lcl, L_rai, N_rai, aps, tps, vel, ρ, T,
881874 )
882875
883876 # Add collision tendencies
@@ -895,7 +888,6 @@ to be non-Nothing, eliminating runtime type checks and dynamic dispatch.
895888 # Ice melting (above freezing temperature)
896889 T_freeze = TDI. TD. Parameters. T_freeze (tps)
897890 if T > T_freeze && L_ice > zero (L_ice)
898- state = CMP3. P3State (p3, L_ice, N_ice, F_rim, ρ_rim)
899891 melt = CMP3. ice_melt (vel, aps, tps, T, ρ, state, logλ)
900892
901893 # Melting converts ice to rain
@@ -907,9 +899,67 @@ to be non-Nothing, eliminating runtime type checks and dynamic dispatch.
907899 end
908900 end
909901
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)
902+ # --- --------------- ---
903+ # --- Ice Nucleation ---
904+ # --- --------------- ---
905+
906+ # Note: Intuitively, you choose one of two parameterization pathways,
907+ # either 1. process-based (e.g. using CM_HetIce.deposition_J, etc.) or
908+ # 2. empirical (e.g. using CM_HetIce.INP_concentration_mean)
909+ # 1. In the former, you try to differentiate between different aerosol types
910+ # and their properties, homogeneous freezing, heterogeneous deposition
911+ # freezing, heterogeneous immersion freezing, etc.
912+ # 2. The latter, which we adopt here, empirically relates some environmental
913+ # conditions (e.g. temperature) to the number of ice crystals nucleated.
914+ # We do not track the aerosol population, and do not consider different
915+ # nucleation pathways explicitly.
916+
917+ # --- Ice Nucleation (empirical) ---
918+ # Assume mass loss is mean condensate mass
919+ m_lcl = ifelse (n_lcl > ϵₙ, q_lcl / n_lcl, zero (q_lcl)) # mean liquid mass
920+
921+ # TODO : The parameterixation should return a rate, `∂N/∂t`, not number changes `ΔN`
922+ inpc = CM_HetIce. INP_concentration_mean (heterogeneous, T) / ρ # [particles / kg air]
923+ n_het = max (0 , inpc - n_ice) # [particles / kg air]
924+ q_het = n_het * m_lcl # [kg ice / kg air]
925+
926+ dn_ice_dt += n_het
927+ dq_ice_dt += q_het
928+ dq_rim_dt += q_het
929+ db_rim_dt += q_het / p3. ρ_i # ρ_i = 916.7 kg m⁻³, the density of solid bulk ice
930+ dn_lcl_dt -= n_het
931+ dq_lcl_dt -= q_het
932+
933+ # --- Cloud Droplet Condensation Freezing ---
934+ # ref: `homogeneous_freezing` in `parcel/ParcelTendencies.jl`
935+ # get mean diameter of cloud droplets, then convert to volume
936+
937+ # --- Ice Sublimation / Deposition ---
938+ # Deposition/sublimation of cloud ice
939+ ∂ₜ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)
940+ # No ice deposition above freezing (lack of INPs)
941+ ∂ₜq_ice_dep = ifelse (T > tps. T_freeze, min (∂ₜq_ice_dep, zero (T)), ∂ₜq_ice_dep)
942+ # During sublimation, the number of ice particles decreases in proportion to the mean ice mass
943+ # During deposition, the number of ice particles remain unchanged
944+ ∂ₜn_ice_dep = ifelse (∂ₜq_ice_dep < 0 , (n_ice / q_ice) * ∂ₜq_ice_dep, zero (∂ₜq_ice_dep))
945+ dq_ice_dt += ∂ₜq_ice_dep
946+ dn_ice_dt += ∂ₜn_ice_dep
947+
948+ # --- Ice Self-collection (Aggregation) ---
949+ if q_ice > ϵₘ && n_ice > ϵₙ
950+ state = CMP3. P3State (p3, L_ice, N_ice, F_rim, ρ_rim)
951+ S_ice_agg = CMP3. ice_self_collection (state, logλ, aps, tps, vel, ρ, T)
952+ dn_ice_dt -= S_ice_agg. dNdt / ρ
953+ end
954+
955+ # --- Rain Heterogeneous Freezing ---
956+ # TODO : Implement heterogeneous freezing of rain
957+ # This process is currently missing in P3_processes.jl
958+ # S_rai_frz = ...
959+ # dq_rai_dt -= S_rai_frz
960+ # dq_ice_dt += S_rai_frz
961+
962+ 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)
913963end
914964
915965end # module BulkMicrophysicsTendencies
0 commit comments