Skip to content

Commit 07f1c02

Browse files
committed
fix: remove allocations in P3 scheme
1 parent 5d5af66 commit 07f1c02

File tree

6 files changed

+116
-226
lines changed

6 files changed

+116
-226
lines changed

src/IceNucleation.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ function P3_deposition_N_i(ip::CMP.MorrisonMilbrandt2014, T::FT) where {FT}
150150
max(FT(0), FT(ip.c₁ * exp(ip.c₂ * (T₀ - T_thres)))),
151151
max(FT(0), FT(ip.c₁ * exp(ip.c₂ * (T₀ - T)))),
152152
)
153-
return Nᵢ * 1e3 # converts L^-1 to m^-3
153+
return Nᵢ * 1000 # converts L^-1 to m^-3
154154
end
155155

156156
"""
@@ -176,9 +176,9 @@ function P3_het_N_i(
176176
Δt::FT,
177177
) where {FT}
178178

179-
a = ip.het_a # (celcius)^-1
180-
B = ip.het_B # cm^-3 s^-1 for rain water
181-
V_l_converted = V_l * 1e6 # converted from m^3 to cm^3
179+
a = ip.het_a # (celcius)^-1
180+
B = ip.het_B # cm^-3 s^-1 for rain water
181+
V_l_converted = V_l * 1_000_000 # converted from m^3 to cm^3
182182
Tₛ = ip.T₀ - T
183183

184184
return N_l * (1 - exp(-B * V_l_converted * Δt * exp(a * Tₛ)))

src/P3_integral_properties.jl

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
3535
# Arguments
3636
- `f`: Function to integrate
37-
- `a`, `b`: Integration bounds
37+
- `a`, `b`: Integration bounds. Note: if `a ≥ b`, or `a` or `b` is `NaN`, `zero(f(a))` is returned.
3838
3939
# Keyword arguments
4040
- `quad`: Quadrature scheme, default: `ChebyshevGauss(100)`
@@ -56,7 +56,7 @@ function integrate(f, a, b; quad = ChebyshevGauss(100))
5656

5757
# Compute integral using Chebyshev-Gauss quadrature
5858
result = zero(f(a))
59-
a == b && return result # return early if a == b
59+
a < b || return result # return early unless a < b
6060
(; n) = quad
6161
for i in 1:n
6262
# Node on [-1, 1] interval
@@ -164,17 +164,15 @@ Compute the integration bounds for the P3 size distribution,
164164
- `bnds`: The integration bounds (a `Tuple`), for use in numerical integration (c.f. [`integrate`](@ref)).
165165
"""
166166
function integral_bounds(state::P3State{FT}, logλ; p, moment_order = 0) where {FT}
167-
# Get mass thresholds
168-
(; D_th, D_gr, D_cr) = get_thresholds_ρ_g(state)
169-
# Get bounds from quantiles
167+
# Get reduced lower and upper bounds from quantiles
170168
k = get_μ(state, logλ) + moment_order
171169
D_min = DT.generalized_gamma_quantile(k, FT(1), exp(logλ), FT(p))
172170
D_max = DT.generalized_gamma_quantile(k, FT(1), exp(logλ), FT(1 - p))
173171

174172
# Only integrate up to the maximum diameter, `D_max`, including intermediate thresholds
175173
# If `F_rim` is very close to 1, `D_cr` may be greater than `D_max`, in which case it is disregarded.
176-
bnds = (D_min, filter(<(D_max), (D_th, D_gr, D_cr))..., D_max)
177-
return bnds
174+
thresholds = get_bounded_thresholds(state, D_min, D_max)
175+
return thresholds
178176
end
179177

180178
"""

src/P3_particle_properties.jl

Lines changed: 31 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -217,28 +217,31 @@ function get_thresholds_ρ_g(params::CMP.ParametersP3, F_rim, ρ_rim)
217217
return (; D_th, D_gr, D_cr, ρ_g)
218218
end
219219

220+
function get_bounded_thresholds(state::P3State, D_min = 0, D_max = Inf)
221+
FT = eltype(state)
222+
(; D_th, D_gr, D_cr) = get_thresholds_ρ_g(state)
223+
thresholds = clamp.((FT(D_min), D_th, D_gr, D_cr, FT(D_max)), FT(D_min), FT(D_max))
224+
bounded_thresholds = replace(thresholds, NaN => FT(D_max))
225+
return bounded_thresholds
226+
end
227+
220228
"""
221229
get_segments(state::P3State)
222230
223-
Return the segments of the size distribution.
231+
Return the segments of the size distribution as a tuple of intervals.
224232
225233
# Arguments
226234
- `state`: [`P3State`](@ref) object
227235
228236
# Returns
229237
- `segments`: tuple of tuples, each containing the lower and upper bounds of a segment
230238
231-
For example, if the (valid) thresholds are `(D_th, D_gr, D_cr)`, then the segments are:
239+
For example, if the thresholds are `(D_th, D_gr, D_cr)`, then the segments are:
232240
- `(0, D_th)`, `(D_th, D_gr)`, `(D_gr, D_cr)`, `(D_cr, Inf)`
233241
"""
234-
function get_segments(state::P3State)
235-
FT = eltype(state)
236-
(; D_th, D_gr, D_cr) = get_thresholds_ρ_g(state)
237-
# For certain high rimed values, D_gr < D_th (cf test/p3_tests.jl):
238-
# so here we filter away invalid thresholds
239-
# (this also works correctly for the unrimed case, where D_gr = D_cr = NaN)
240-
valid_D = filter((D_th), (D_th, D_gr, D_cr))
241-
segments = tuple.((FT(0), valid_D...), (valid_D..., FT(Inf)))
242+
function get_segments(state::P3State, D_min = 0, D_max = Inf)
243+
thresholds = get_bounded_thresholds(state, D_min, D_max)
244+
segments = tuple.(Base.front(thresholds), Base.tail(thresholds))
242245
return segments
243246
end
244247

@@ -300,9 +303,9 @@ Return the mass of a particle based on where it falls in the particle-size-based
300303
- `params, F_rim, ρ_rim`: The [`CMP.ParametersP3`](@ref), rime mass fraction, and rime density,
301304
- `D`: maximum particle dimension [m]
302305
"""
303-
function ice_mass(args_D...)
304-
D = last(args_D)
305-
(a, b) = ice_mass_coeffs(args_D...)
306+
ice_mass((; params, F_rim, ρ_rim)::P3State, D) = ice_mass(params, F_rim, ρ_rim, D)
307+
function ice_mass(params::CMP.ParametersP3, F_rim, ρ_rim, D)
308+
(a, b) = ice_mass_coeffs(params, F_rim, ρ_rim, D)
306309
return a * D^b
307310
end
308311

@@ -322,13 +325,14 @@ Return the density of a particle at diameter D
322325
by the volume of a sphere with the same D [MorrisonMilbrandt2015](@cite).
323326
Needed for aspect ratio calculation, so we assume zero liquid fraction.
324327
"""
325-
function ice_density(args_D...)
326-
D = last(args_D)
327-
return ice_mass(args_D...) / CO.volume_sphere_D(D)
328+
ice_density((; params, F_rim, ρ_rim)::P3State, D) = ice_density(params, F_rim, ρ_rim, D)
329+
function ice_density(params::CMP.ParametersP3, F_rim, ρ_rim, D)
330+
return ice_mass(params, F_rim, ρ_rim, D) / CO.volume_sphere_D(D)
328331
end
329332

330-
function get_∂mass_∂D_coeffs(args_D...)
331-
(a, b) = ice_mass_coeffs(args_D...)
333+
get_∂mass_∂D_coeffs((; params, F_rim, ρ_rim)::P3State, D) = get_∂mass_∂D_coeffs(params, F_rim, ρ_rim, D)
334+
function get_∂mass_∂D_coeffs(params::CMP.ParametersP3, F_rim, ρ_rim, D)
335+
(a, b) = ice_mass_coeffs(params, F_rim, ρ_rim, D)
332336
return a * b, b - 1
333337
end
334338

@@ -338,9 +342,9 @@ end
338342
339343
Return the derivative of the ice mass with respect to the particle diameter.
340344
"""
341-
function ∂ice_mass_∂D(args_D...)
342-
D = last(args_D)
343-
(a, b) = get_∂mass_∂D_coeffs(args_D...)
345+
∂ice_mass_∂D((; params, F_rim, ρ_rim)::P3State, D) = ∂ice_mass_∂D(params, F_rim, ρ_rim, D)
346+
function ∂ice_mass_∂D(params::CMP.ParametersP3, F_rim, ρ_rim, D)
347+
(a, b) = get_∂mass_∂D_coeffs(params, F_rim, ρ_rim, D)
344348
return a * D^b
345349
end
346350

@@ -391,18 +395,18 @@ Returns the aspect ratio (ϕ) for an ice particle
391395
divided by the volume of a spherical particle with the same D_max [MorrisonMilbrandt2015](@cite).
392396
Assuming zero liquid fraction and oblate shape.
393397
"""
394-
function ϕᵢ(args_D...)
395-
D = last(args_D)
398+
ϕᵢ((; params, F_rim, ρ_rim)::P3State, D) = ϕᵢ(params, F_rim, ρ_rim, D)
399+
function ϕᵢ(params::CMP.ParametersP3, F_rim, ρ_rim, D)
396400
FT = eltype(D)
397-
mᵢ = ice_mass(args_D...)
398-
aᵢ = ice_area(args_D...)
399-
ρᵢ = ice_density(args_D...)
401+
mᵢ = ice_mass(params, F_rim, ρ_rim, D)
402+
aᵢ = ice_area(params, F_rim, ρ_rim, D)
403+
ρᵢ = ice_density(params, F_rim, ρ_rim, D)
400404

401405
# TODO - prolate or oblate?
402406
ϕ_ob = min(1, 3 * sqrt(FT(π)) * mᵢ / (4 * ρᵢ * aᵢ^FT(1.5))) # κ = 1/3
403407
#ϕ_pr = max(1, 16 * ρᵢ^2 * aᵢ^3 / (9 * FT(π) * mᵢ^2)) # κ = -1/6
404408

405-
return ifelse(D == 0, 0, ϕ_ob)
409+
return ifelse(D == 0, FT(0), ϕ_ob)
406410
end
407411

408412
### ----------------- ###

test/gpu_tests.jl

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1246,7 +1246,15 @@ function test_gpu(FT)
12461246
kernel!(output, ip_P3, T, N_l, V_l, Δt; ndrange)
12471247

12481248
# test if P3_het_N_i is callable and returns reasonable values
1249-
TT.@test Array(output)[1] FT(0.0002736160475969029)
1249+
ref_val = if FT == Float64
1250+
FT(0.0002736160475969029)
1251+
else
1252+
# loss of precision due to
1253+
# `exp(-B * V_l_converted * Δt * exp(a * Tₛ))` -> 0.9999999 (Float32)
1254+
# instead of 0.9999998631919762 (Float64).
1255+
FT(0.00023841858f0)
1256+
end
1257+
TT.@test Array(output)[1] ref_val
12501258

12511259
dims = (1, 1)
12521260
(; output, ndrange) = setup_output(dims, FT)

test/p3_tests.jl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -781,6 +781,14 @@ function test_p3_bulk_liquid_ice_collisions(FT)
781781
@test BCCOL 3.7184509f-9
782782
@test BRCOL 4.2099646f-7
783783
@test ∫𝟙_wet_M_col 1.58113f-5
784+
785+
### Test the bulk source function
786+
rates = P3.bulk_liquid_ice_collision_sources(
787+
params, logλ, Lᵢ, Nᵢ, F_rim, ρ_rim,
788+
psd_c, psd_r, L_c, N_c, L_r, N_r,
789+
aps, tps, vel_params, ρₐ, T,
790+
)
791+
@test eltype(rates) == FT # check type stability
784792
end
785793
end
786794

0 commit comments

Comments
 (0)