Skip to content

Commit 1a32419

Browse files
authored
Merge pull request #577 from CliMA/he/feat/p3-collisions
feat: add P3 collision rates
2 parents 7ed9553 + 409bcaa commit 1a32419

File tree

8 files changed

+761
-8
lines changed

8 files changed

+761
-8
lines changed

Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ LogExpFunctions = "2ab3a3ac-af41-5b50-aa03-7779005ae688"
1212
QuadGK = "1fd47b50-473d-5c70-9696-f719f8f3bcdc"
1313
RootSolvers = "7181ea78-2dcb-4de3-ab41-2b8ab5a31e74"
1414
SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b"
15+
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
1516
Thermodynamics = "b60c26fb-14c3-4610-9d3e-2d17fe7ff00c"
1617

1718
[weakdeps]
@@ -33,4 +34,5 @@ QuadGK = "2.9"
3334
RootSolvers = "0.3, 0.4"
3435
SpecialFunctions = "1, 2"
3536
Thermodynamics = "0.12.14"
37+
StaticArrays = "1.9"
3638
julia = "1.6"

docs/make.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ pages = Any[
7171
"References" => "References.md",
7272
]
7373

74-
mathengine = MathJax(
74+
mathengine = MathJax3(
7575
Dict(
7676
:TeX => Dict(
7777
:equationNumbers => Dict(:autoNumber => "AMS"),

docs/src/API.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,10 +183,16 @@ P3Scheme.ice_melt
183183
```
184184

185185
#### Collisions with liquid droplets
186+
```@docs
187+
P3Scheme.bulk_liquid_ice_collision_sources
188+
```
186189
Supporting methods:
187190
```@docs
191+
P3Scheme.volumetric_collision_rate_integrand
188192
P3Scheme.compute_max_freeze_rate
189193
P3Scheme.compute_local_rime_density
194+
P3Scheme.get_liquid_integrals
195+
P3Scheme.∫liquid_ice_collisions
190196
```
191197

192198
### Supporting integral methods

docs/src/P3Scheme.md

Lines changed: 301 additions & 4 deletions
Large diffs are not rendered by default.

src/P3.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import SpecialFunctions as SF
1111
import QuadGK as QGK
1212
import RootSolvers as RS
1313
import LogExpFunctions
14+
import StaticArrays as SA
1415

1516
import ClimaParams as CP
1617

src/P3_processes.jl

Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,49 @@ function ice_melt(
9898
return (; dNdt, dLdt)
9999
end
100100

101+
function collision_cross_section_ice_liquid(state, Dᵢ, Dₗ)
102+
rᵢ_eff(Dᵢ) = (ice_area(state, Dᵢ) / π)
103+
return π * (rᵢ_eff(Dᵢ) + Dₗ / 2)^2 # collision cross section
104+
end
105+
106+
"""
107+
volumetric_collision_rate_integrand(state, velocity_params, ρₐ)
108+
109+
Returns a function that computes the volumetric collision rate integrand for ice-liquid collisions [m³/s].
110+
The returned function takes ice and liquid particle diameters as arguments.
111+
112+
# Arguments
113+
- `state`: [`P3State`](@ref)
114+
- `velocity_params`: velocity parameterization, e.g. [`CMP.Chen2022VelType`](@ref)
115+
- `ρₐ`: air density
116+
117+
# Returns
118+
A function `(D_ice, D_liq) -> E * K * |vᵢ - vₗ|` where:
119+
- `D_ice` and `D_liq` are the (maximum) diameters of the ice and liquid particles
120+
- `E` is the collision efficiency
121+
- `K` is the collision cross section
122+
- `vᵢ` and `vₗ` are the terminal velocities of ice and liquid particles
123+
124+
Note that `E`, `K`, `vᵢ` and `vₗ` are all, in general, functions of `D_ice` and `D_liq`.
125+
126+
This function is a component of integrals like
127+
128+
```math
129+
∫ ∫ E * K * |vᵢ - vₗ| * N'_i * N'_l dD_i dD_l
130+
```
131+
"""
132+
function volumetric_collision_rate_integrand(velocity_params, ρₐ, state)
133+
v_ice = ice_particle_terminal_velocity(velocity_params, ρₐ, state)
134+
v_liq = CO.particle_terminal_velocity(velocity_params.rain, ρₐ)
135+
function integrand(D_ice::FT, D_liq::FT) where {FT}
136+
E = FT(1) # TODO - Make collision efficiency a function of Dᵢ and Dₗ
137+
K = collision_cross_section_ice_liquid(state, D_ice, D_liq)
138+
return E * K * abs(v_ice(D_ice) - v_liq(D_liq))
139+
end
140+
141+
return integrand
142+
end
143+
101144
"""
102145
compute_max_freeze_rate(aps, tps, velocity_params, ρₐ, Tₐ, state)
103146
@@ -199,3 +242,259 @@ function compute_local_rime_density(velocity_params, ρₐ, T, state)
199242
end
200243
return ρ′_rim
201244
end
245+
246+
"""
247+
get_liquid_integrals(n, ∂ₜV, m_liq, ρ′_rim, liq_bounds; ∫kwargs...)
248+
249+
Returns a function `liquid_integrals(Dᵢ)` that computes the liquid particle integrals
250+
for a given ice particle diameter `Dᵢ`.
251+
252+
# Arguments
253+
- `n`: liquid particle size distribution function `n(D)`
254+
- `∂ₜV`: volumetric collision rate integrand function `∂ₜV(Dᵢ, D)`
255+
- `m_liq`: liquid particle mass function `m_liq(D)`
256+
- `ρ′_rim`: local rime density function `ρ′_rim(Dᵢ, D)`
257+
- `liq_bounds`: integration bounds for liquid particles
258+
259+
# Keyword arguments
260+
- `∫kwargs...`: Additional keyword arguments passed to `QuadGK.quadgk`
261+
262+
# Notes
263+
The function `liquid_integrals(Dᵢ)` returns a tuple `(∂ₜN_col, ∂ₜM_col, ∂ₜB_col)`
264+
of collision rates at `Dᵢ`, where:
265+
- `∂ₜN_col`: number collision rate [1/s]
266+
- `∂ₜM_col`: mass collision rate [kg/s]
267+
- `∂ₜB_col`: rime volume collision rate [m³/s]
268+
"""
269+
function get_liquid_integrals(n, ∂ₜV, m_liq, ρ′_rim, liq_bounds; ∫kwargs...)
270+
function liquid_integrals(Dᵢ)
271+
((∂ₜN_col, ∂ₜM_col, ∂ₜB_col), _) =
272+
QGK.quadgk(liq_bounds...; ∫kwargs...) do D
273+
return SA.SVector(
274+
# ∂ₜN_col = ∫ ∂ₜV ⋅ n ⋅ dD
275+
∂ₜV(Dᵢ, D) * n(D),
276+
# ∂ₜM_col = ∫ ∂ₜV ⋅ n ⋅ m_liq ⋅ dD
277+
∂ₜV(Dᵢ, D) * n(D) * m_liq(D),
278+
# ∂ₜB_col = ∫ ∂ₜV ⋅ n ⋅ m_liq / ρ′_rim ⋅ dD
279+
∂ₜV(Dᵢ, D) * n(D) * m_liq(D) / ρ′_rim(Dᵢ, D),
280+
)
281+
end
282+
return ∂ₜN_col, ∂ₜM_col, ∂ₜB_col
283+
end
284+
return liquid_integrals
285+
end
286+
287+
"""
288+
∫liquid_ice_collisions(
289+
n_i, ∂ₜM_max, cloud_integrals, rain_integrals, ice_bounds; ∫kwargs...
290+
)
291+
292+
Computes the bulk collision rate integrands between ice and liquid particles.
293+
294+
# Arguments
295+
- `n_i`: ice particle size distribution function n_i(D)
296+
- `∂ₜM_max`: maximum freezing rate function ∂ₜM_max(Dᵢ)
297+
- `cloud_integrals`: an instance of [`get_liquid_integrals`](@ref) for cloud particles
298+
- `rain_integrals`: an instance of [`get_liquid_integrals`](@ref) for rain particles
299+
- `ice_bounds`: integration bounds for ice particles, from [`integral_bounds`](@ref)
300+
301+
# Keyword arguments
302+
- `∫kwargs...`: Additional keyword arguments passed to `QuadGK.quadgk`
303+
304+
# Returns
305+
A tuple of 8 integrands, see [`∫liquid_ice_collisions`](@ref) for details.
306+
"""
307+
function ∫liquid_ice_collisions(n_i, ∂ₜM_max, cloud_integrals, rain_integrals, ice_bounds; ∫kwargs...)
308+
function liquid_ice_collisions_integrands(Dᵢ)
309+
# Inner integrals over liquid particle diameters
310+
∂ₜN_c_col, ∂ₜM_c_col, ∂ₜB_c_col = cloud_integrals(Dᵢ)
311+
∂ₜN_r_col, ∂ₜM_r_col, ∂ₜB_r_col = rain_integrals(Dᵢ)
312+
313+
# Partition the mass collisions between freezing and shedding
314+
∂ₜM_col = ∂ₜM_c_col + ∂ₜM_r_col # [kg / s]
315+
316+
∂ₜM_frz = min(∂ₜM_col, ∂ₜM_max(Dᵢ))
317+
f_frz = iszero(∂ₜM_col) ? zero(∂ₜM_frz) : ∂ₜM_frz / ∂ₜM_col
318+
𝟙_wet = ∂ₜM_col > ∂ₜM_frz # Used for wet densification
319+
320+
n = n_i(Dᵢ)
321+
# Integrating over `Dᵢ` gives another unit of `[m]`, so `[X / s / m]` --> `[X / s]`
322+
# ∂ₜX = ∫ ∂ₜX(Dᵢ) nᵢ(Dᵢ) dDᵢ
323+
return SA.SVector(
324+
n * ∂ₜM_c_col * f_frz, # QCFRZ
325+
n * ∂ₜM_c_col * (1 - f_frz), # QCSHD
326+
n * ∂ₜN_c_col, # NCCOL
327+
n * ∂ₜM_r_col * f_frz, # QRFRZ
328+
n * ∂ₜM_r_col * (1 - f_frz), # QRSHD
329+
n * ∂ₜN_r_col, # NRCOL
330+
n * ∂ₜM_col, # ∫M_col, total collision rate
331+
n * ∂ₜB_c_col * f_frz, # BCCOL, ∂ₜB_rim source
332+
n * ∂ₜB_r_col * f_frz, # BRCOL, ∂ₜB_rim source
333+
n * 𝟙_wet * ∂ₜM_col, # ∫𝟙_wet_M_col, wet growth indicator
334+
)
335+
end
336+
337+
(rates, _) = QGK.quadgk(liquid_ice_collisions_integrands, ice_bounds...; ∫kwargs...)
338+
return rates
339+
end
340+
341+
"""
342+
∫liquid_ice_collisions(
343+
state, logλ, psd_c, psd_r, L_c, N_c, L_r, N_r,
344+
aps, tps, vel, ρₐ, T, m_liq
345+
)
346+
347+
Compute key liquid-ice collision rates and quantities. Used by [`bulk_liquid_ice_collision_sources`](@ref).
348+
349+
# Arguments
350+
- `state`: [`P3State`](@ref)
351+
- `logλ`: the log of the slope parameter [log(1/m)]
352+
- `psd_c`: [`CMP.CloudParticlePDF_SB2006`](@ref)
353+
- `psd_r`: [`CMP.RainParticlePDF_SB2006`](@ref)
354+
- `L_c`: cloud liquid water content [kg/m³]
355+
- `N_c`: cloud liquid water number concentration [1/m³]
356+
- `L_r`: rain water content [kg/m³]
357+
- `N_r`: rain number concentration [1/m³]
358+
- `aps`: [`CMP.AirProperties`](@ref)
359+
- `tps`: `TDP.ThermodynamicsParameters`
360+
- `vel`: velocity parameterization, e.g. [`CMP.Chen2022VelType`](@ref)
361+
- `ρₐ`: air density [kg/m³]
362+
- `T`: temperature [K]
363+
- `m_liq`: liquid particle mass function `m_liq(D)`
364+
365+
# Returns
366+
A tuple `(QCFRZ, QCSHD, NCCOL, QRFRZ, QRSHD, NRCOL, ∫M_col, BCCOL, BRCOL, ∫𝟙_wet_M_col)`, where:
367+
1. `QCFRZ` - Cloud mass collision rate due to freezing [kg/s]
368+
2. `QCSHD` - Cloud mass collision rate due to shedding [kg/s]
369+
3. `NCCOL` - Cloud number collision rate [1/s]
370+
4. `QRFRZ` - Rain mass collision rate due to freezing [kg/s]
371+
5. `QRSHD` - Rain mass collision rate due to shedding [kg/s]
372+
4. `NRCOL` - Rain number collision rate [1/s]
373+
5. `∫M_col` - Total collision rate [kg/s]
374+
6. `BCCOL` - Cloud rime volume source [m³/m³/s]
375+
7. `BRCOL` - Rain rime volume source [m³/m³/s]
376+
8. `∫𝟙_wet_M_col` - Wet growth indicator [kg/s]
377+
"""
378+
function ∫liquid_ice_collisions(
379+
state, logλ,
380+
psd_c, psd_r, L_c, N_c, L_r, N_r,
381+
aps, tps, vel, ρₐ, T, m_liq,
382+
)
383+
FT = eltype(state)
384+
385+
# Particle size distributions
386+
n_c = DT.size_distribution(psd_c, L_c / ρₐ, ρₐ, N_c) # n_c(Dₗ)
387+
n_r = DT.size_distribution(psd_r, L_r / ρₐ, ρₐ, N_r) # n_r(Dₗ)
388+
n_i = DT.size_distribution(state, logλ) # n_i(Dᵢ)
389+
390+
# Initialize integration buffers by evaluating a representative integral
391+
ice_bounds = integral_bounds(state, logλ; p = 0.00001)
392+
mm = FT(1e-3)
393+
bounds_c = FT[0; 0.01mm; 0.1mm; 1mm; 10mm; 100mm; 1] # TODO: Replace by quantiles method
394+
bounds_r = FT[0; 0.01mm; 0.1mm; 1mm; 10mm; 100mm; 1] # TODO: Replace by quantiles method
395+
segbuf_c = QGK.quadgk_segbuf(n_c, bounds_c...)[3]
396+
segbuf_r = QGK.quadgk_segbuf(n_r, bounds_r...)[3]
397+
segbuf_ice = QGK.quadgk_segbuf(n_i, ice_bounds...)[3]
398+
399+
# Integrand components
400+
# NOTE: We assume collision efficiency, shape (spherical), and terminal velocity is the
401+
# same for cloud and precipitating liquid particles ⟹ same volumetric collision rate, ∂ₜV
402+
∂ₜV = volumetric_collision_rate_integrand(vel, ρₐ, state) # ∂ₜV(Dᵢ, Dₗ)
403+
ρ′_rim = compute_local_rime_density(vel, ρₐ, T, state) # ρ′_rim(Dᵢ, Dₗ)
404+
∂ₜM_max = compute_max_freeze_rate(aps, tps, vel, ρₐ, T, state) # ∂ₜM_max(Dᵢ)
405+
406+
cloud_integrals = get_liquid_integrals(n_c, ∂ₜV, m_liq, ρ′_rim, bounds_c; eval_segbuf = segbuf_c) # (∂ₜN_c_col, ∂ₜM_c_col, ∂ₜB_c_col)
407+
rain_integrals = get_liquid_integrals(n_r, ∂ₜV, m_liq, ρ′_rim, bounds_r; eval_segbuf = segbuf_r) # (∂ₜN_r_col, ∂ₜM_r_col, ∂ₜB_r_col)
408+
409+
return ∫liquid_ice_collisions(
410+
n_i, ∂ₜM_max, cloud_integrals, rain_integrals, ice_bounds; eval_segbuf = segbuf_ice,
411+
)
412+
end
413+
414+
"""
415+
bulk_liquid_ice_collision_sources(
416+
params, logλ, L_ice, F_rim, ρ_rim,
417+
psd_c, psd_r, L_c, N_c, L_r, N_r,
418+
aps, tps, vel, ρₐ, T,
419+
)
420+
421+
Computes the bulk rates for ice and liquid particle collisions.
422+
423+
# Arguments
424+
- `params`: the [`CMP.ParametersP3`](@ref)
425+
- `logλ`: the log of the slope parameter [log(1/m)]
426+
- `L_ice`: ice water content [kg/m³]
427+
- `F_rim`: riming fraction
428+
- `ρ_rim`: rime density [kg/m³]
429+
- `psd_c`: a [`CMP.CloudParticlePDF_SB2006`](@ref)
430+
- `psd_r`: a [`CMP.RainParticlePDF_SB2006`](@ref)
431+
- `L_c`: cloud liquid water content [kg/m³]
432+
- `N_c`: cloud liquid water number concentration [1/m³]
433+
- `L_r`: rain water content [kg/m³]
434+
- `N_r`: rain number concentration [1/m³]
435+
- `aps`: [`CMP.AirProperties`](@ref)
436+
- `tps`: thermodynamics parameters
437+
- `vel`: the velocity parameterization, e.g. [`CMP.Chen2022VelType`](@ref)
438+
- `ρₐ`: air density [kg/m³]
439+
- `T`: temperature [K]
440+
441+
# Returns
442+
A `NamedTuple` of `(; ∂ₜq_c, ∂ₜq_r, ∂ₜN_c, ∂ₜN_r, ∂ₜL_rim, ∂ₜL_ice, ∂ₜB_rim)`, where:
443+
1. `∂ₜq_c`: cloud liquid water content tendency [kg/kg/s]
444+
2. `∂ₜq_r`: rain water content tendency [kg/kg/s]
445+
3. `∂ₜN_c`: cloud number concentration tendency [1/m³/s]
446+
4. `∂ₜN_r`: rain number concentration tendency [1/m³/s]
447+
5. `∂ₜL_rim`: riming mass tendency [kg/m³/s]
448+
6. `∂ₜL_ice`: ice water content tendency [kg/m³/s]
449+
7. `∂ₜB_rim`: rime volume tendency [m³/m³/s]
450+
"""
451+
function bulk_liquid_ice_collision_sources(
452+
params, logλ, L_ice, F_rim, ρ_rim,
453+
psd_c, psd_r, L_c, N_c, L_r, N_r,
454+
aps, tps, vel, ρₐ, T,
455+
)
456+
FT = eltype(params)
457+
(; τ_wet) = params
458+
D_shd = FT(1e-3) # 1mm # TODO: Externalize this parameter
459+
460+
ρw = psd_c.ρw
461+
@assert ρw == psd_r.ρw "Cloud and rain should have the same liquid water density"
462+
m_liq(Dₗ) = ρw * CO.volume_sphere_D(Dₗ)
463+
464+
state = get_state(params; L_ice, F_rim, ρ_rim)
465+
466+
(QCFRZ, QCSHD, NCCOL, QRFRZ, QRSHD, NRCOL, ∫∂ₜM_col, BCCOL, BRCOL, ∫𝟙_wet_M_col) = ∫liquid_ice_collisions(
467+
state, logλ,
468+
psd_c, psd_r, L_c, N_c, L_r, N_r,
469+
aps, tps, vel, ρₐ, T, m_liq,
470+
)
471+
472+
# Bulk wet growth fraction
473+
f_wet = ∫𝟙_wet_M_col / ∫∂ₜM_col
474+
475+
# Shedding of rain
476+
# QRSHD = ∫∂ₜM_col - (QCFRZ + QRFRZ)
477+
NRSHD = QRSHD / m_liq(D_shd)
478+
# NCSHD = QCSHD / m_liq(D_shd)
479+
480+
# Densification of rime
481+
(; L_ice, F_rim, ρ_rim) = state
482+
B_rim = (L_ice * F_rim) / ρ_rim # from: ρ_rim = L_rim / B_rim
483+
QIWET = f_wet * L_ice * (1 - F_rim) / τ_wet # densification of rime mass
484+
BIWET = f_wet * (L_ice / ρ⭒ - B_rim) / τ_wet # densification of rime volume
485+
486+
# Bulk rates
487+
## Liquid phase
488+
∂ₜq_c = (-QCFRZ - QCSHD) / ρₐ
489+
∂ₜq_r = (-QRFRZ + QCSHD) / ρₐ
490+
∂ₜN_c = -NCCOL
491+
∂ₜN_r = -NRCOL + NRSHD
492+
## Ice phase
493+
∂ₜL_rim = QCFRZ + QRFRZ + QIWET
494+
∂ₜL_ice = QCFRZ + QRFRZ
495+
# ∂ₜN_ice = 0
496+
∂ₜB_rim = BCCOL + BRCOL + BIWET
497+
498+
return (; ∂ₜq_c, ∂ₜq_r, ∂ₜN_c, ∂ₜN_r, ∂ₜL_rim, ∂ₜL_ice, ∂ₜB_rim)
499+
500+
end

src/parameters/MicrophysicsP3.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,8 @@ $(DocStringExtensions.FIELDS)
267267
vent::VentilationFactor{FT}
268268
"Local rime density, e.g. [`LocalRimeDensity`](@ref)"
269269
ρ_rim_local::LocalRimeDensity{FT}
270+
"Wet growth time scale [`s`]"
271+
τ_wet::FT
270272
"Cloud ice density [`kg m⁻³`]"
271273
ρ_i::FT
272274
"Cloud liquid water density [`kg m⁻³`]"
@@ -306,6 +308,7 @@ ParametersP3{Float64}
306308
│ ├── b = 114.0 [-]
307309
│ ├── c = -5.5 [-]
308310
│ └── ρ_ice = 916.7 [-]
311+
├── τ_wet = 100.0 [s]
309312
├── ρ_i = 916.7 [kg m⁻³]
310313
├── ρ_l = 1000.0 [kg m⁻³]
311314
└── T_freeze = 273.15 [K]
@@ -338,6 +341,7 @@ function ParametersP3(toml_dict::CP.AbstractTOMLDict; slope_law = :powerlaw)
338341
:density_ice_water => :ρ_i,
339342
:density_liquid_water => :ρ_l,
340343
:temperature_water_freeze => :T_freeze,
344+
:P3_wet_growth_timescale => :τ_wet,
341345
)
342346
params = CP.get_parameter_values(toml_dict, name_map, "CloudMicrophysics")
343347
FT = CP.float_type(toml_dict)
@@ -404,6 +408,7 @@ function _get_parameter_unit(field::Symbol)
404408
:α_va => "kg μm^(-β_va)",
405409
=> "μm^(2-σ)",
406410
:a => "m^b",
411+
:τ_wet => "s",
407412
:ρ_i => "kg m⁻³",
408413
:ρ_l => "kg m⁻³",
409414
:T_freeze => "K",

0 commit comments

Comments
 (0)