Skip to content

Commit 465789a

Browse files
committed
fix: remove allocations in P3 scheme
1 parent a656b96 commit 465789a

File tree

4 files changed

+89
-225
lines changed

4 files changed

+89
-225
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: 6 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`, we return zero.
3838
3939
# Keyword arguments
4040
- `quad`: Quadrature scheme, default: `ChebyshevGauss(100)`
@@ -55,8 +55,9 @@ function integrate(f, a, b; quad = ChebyshevGauss(100))
5555
shift = (a + b) / 2
5656

5757
# Compute integral using Chebyshev-Gauss quadrature
58+
isnan(a) || isnan(b) && return FT(NaN) # return NaN if any bound is NaN
5859
result = zero(f(a))
59-
a == b && return result # return early if a == b
60+
a < b || return result # return early unless a < b
6061
(; n) = quad
6162
for i in 1:n
6263
# Node on [-1, 1] interval
@@ -164,17 +165,15 @@ Compute the integration bounds for the P3 size distribution,
164165
- `bnds`: The integration bounds (a `Tuple`), for use in [`QGK.quadgk`].
165166
"""
166167
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
168+
# Get reduced lower and upper bounds from quantiles
170169
k = get_μ(state, logλ) + moment_order
171170
D_min = DT.generalized_gamma_quantile(k, FT(1), exp(logλ), FT(p))
172171
D_max = DT.generalized_gamma_quantile(k, FT(1), exp(logλ), FT(1 - p))
173172

174173
# Only integrate up to the maximum diameter, `D_max`, including intermediate thresholds
175174
# 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
175+
thresholds = get_bounded_thresholds(state, D_min, D_max)
176+
return thresholds
178177
end
179178

180179
"""

src/P3_particle_properties.jl

Lines changed: 29 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -217,28 +217,29 @@ 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+
return clamp.((FT(D_min), D_th, D_gr, D_cr, FT(D_max)), FT(D_min), FT(D_max))
224+
end
225+
220226
"""
221227
get_segments(state::P3State)
222228
223-
Return the segments of the size distribution.
229+
Return the segments of the size distribution as a tuple of intervals.
224230
225231
# Arguments
226232
- `state`: [`P3State`](@ref) object
227233
228234
# Returns
229235
- `segments`: tuple of tuples, each containing the lower and upper bounds of a segment
230236
231-
For example, if the (valid) thresholds are `(D_th, D_gr, D_cr)`, then the segments are:
237+
For example, if the thresholds are `(D_th, D_gr, D_cr)`, then the segments are:
232238
- `(0, D_th)`, `(D_th, D_gr)`, `(D_gr, D_cr)`, `(D_cr, Inf)`
233239
"""
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)))
240+
function get_segments(state::P3State, D_min = 0, D_max = Inf)
241+
thresholds = get_bounded_thresholds(state, D_min, D_max)
242+
segments = tuple.(Base.front(thresholds), Base.tail(thresholds))
242243
return segments
243244
end
244245

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

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

330-
function get_∂mass_∂D_coeffs(args_D...)
331-
(a, b) = ice_mass_coeffs(args_D...)
331+
get_∂mass_∂D_coeffs((; params, F_rim, ρ_rim)::P3State, D) = get_∂mass_∂D_coeffs(params, F_rim, ρ_rim, D)
332+
function get_∂mass_∂D_coeffs(params::CMP.ParametersP3, F_rim, ρ_rim, D)
333+
(a, b) = ice_mass_coeffs(params, F_rim, ρ_rim, D)
332334
return a * b, b - 1
333335
end
334336

@@ -338,9 +340,9 @@ end
338340
339341
Return the derivative of the ice mass with respect to the particle diameter.
340342
"""
341-
function ∂ice_mass_∂D(args_D...)
342-
D = last(args_D)
343-
(a, b) = get_∂mass_∂D_coeffs(args_D...)
343+
∂ice_mass_∂D((; params, F_rim, ρ_rim)::P3State, D) = ∂ice_mass_∂D(params, F_rim, ρ_rim, D)
344+
function ∂ice_mass_∂D(params::CMP.ParametersP3, F_rim, ρ_rim, D)
345+
(a, b) = get_∂mass_∂D_coeffs(params, F_rim, ρ_rim, D)
344346
return a * D^b
345347
end
346348

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

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

405-
return ifelse(D == 0, 0, ϕ_ob)
407+
return ifelse(D == 0, FT(0), ϕ_ob)
406408
end
407409

408410
### ----------------- ###

0 commit comments

Comments
 (0)