Skip to content

Commit 3713bbb

Browse files
committed
Make the quadrature scheme the configuration choice
The P3 size-distribution quadrature was configured through a single quadrature_order knob, with build_quadrature silently mapping a magic set of orders {16, 32, 40, 64} to GaussLegendre and everything else to ChebyshevGauss. The scheme — with its parameters, notably the order — is now the explicit choice: pass a scheme instance, Microphysics2MParams(FT; with_ice = true, quadrature = Quadrature.GaussLegendre(40)) and build_quadrature only materializes it in the working float type (GaussLegendre nodes are rebuilt in FT; ChebyshevGauss is closed-form and passes through). The default ChebyshevGauss(100) preserves the previous default behavior.
1 parent 309724f commit 3713bbb

3 files changed

Lines changed: 56 additions & 43 deletions

File tree

src/Quadrature.jl

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ nodes/weights as `SVector{N, FT}`, so per-`integrate` access is a static lookup
192192
a GPU kernel.
193193
194194
Arbitrary orders `n ≥ 1` are supported (the order is the type parameter `N`).
195-
`40` is the ClimaAtmos production `quadrature_order`.
195+
`40` is the ClimaAtmos production order.
196196
197197
# GPU / type-stability
198198
@@ -254,26 +254,24 @@ end
254254
GaussLegendre(n::Int) = GaussLegendre(Float64, n)
255255

256256
"""
257-
build_quadrature(FT, quadrature_order)
258-
259-
Select and **construct** the quadrature rule for the P3 size-distribution
260-
integrals from a single `quadrature_order` knob, in element type `FT`. This is
261-
the host-side, one-shot builder; the returned object is `isbits` and stored on a
257+
build_quadrature(FT, scheme::QuadratureRule)
258+
259+
Materialize the quadrature `scheme` for the P3 size-distribution integrals in
260+
element type `FT`. The scheme — with its parameters, notably the order — is
261+
the caller's choice. [`ChebyshevGauss`](@ref) has closed-form nodes and passes
262+
through unchanged; [`GaussLegendre`](@ref) is rebuilt so its stored
263+
nodes/weights adopt `FT` (a Float64 rule would leak Float64 into Float32
264+
integrals). Host-side and one-shot; the result is `isbits` and stored on a
262265
parameter struct for reuse in the (GPU) hot loop.
263266
264-
Gauss-Legendre is preferred for the orders where it is meaningfully more
265-
accurate than Chebyshev-Gauss on the smooth P3 integrands (≈20× lower error on
266-
the dominant ice-rain collision integral at matched `n`; see [`GaussLegendre`](@ref)),
267-
namely `quadrature_order ∈ {16, 32, 40, 64}` (incl. the ClimaAtmos production
268-
order 40). Any other order falls back to [`ChebyshevGauss`](@ref), preserving the
269-
default behaviour for non-preferred orders.
267+
Scheme guidance: at matched order, Gauss-Legendre is substantially more
268+
accurate on the smooth P3 integrands (≈20× lower error on the dominant
269+
ice-rain collision integral; see [`GaussLegendre`](@ref)); Chebyshev-Gauss
270+
remains the conservative default (the cusp-limited `ice_self_collection`
271+
diagonal is quadrature-limited under both schemes).
270272
"""
271-
function build_quadrature(::Type{FT}, quadrature_order::Int) where {FT}
272-
return if quadrature_order in (16, 32, 40, 64)
273-
GaussLegendre(FT, quadrature_order)
274-
else
275-
ChebyshevGauss(quadrature_order)
276-
end
277-
end
273+
build_quadrature(::Type{FT}, scheme::ChebyshevGauss) where {FT} = scheme
274+
build_quadrature(::Type{FT}, scheme::GaussLegendre) where {FT} =
275+
GaussLegendre(FT, scheme.n)
278276

279277
end # module Quadrature

src/parameters/Microphysics2MParams.jl

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -71,24 +71,23 @@ which constructs the parameterization with components:
7171
immersion-cap rates. (A prognostic activation-memory model is deferred
7272
to a follow-up PR.)"
7373
inp_depletion_model::INPDM = NIceProxyDepletion()
74-
"Number of quadrature nodes used for size-distribution integrals
75-
(deposition / sublimation, melting, riming, ice-rain collection,
76-
sedimentation). Lower → faster, slightly less accurate. Default 100
77-
matches the original P3 paper sensitivity studies; n_elem=128 KiD runs
78-
show ~5× speed-up at qorder=40 with negligible bulk error."
79-
quadrature_order::Int = 100
80-
"Pre-constructed quadrature rule for the size-distribution integrals,
81-
built once (host-side) from `quadrature_order` via
82-
[`Quadrature.build_quadrature`](@ref) and reused in the (GPU) hot loop.
83-
It is `isbits` (`GaussLegendre`/`ChebyshevGauss`), so it ships to device
84-
kernels with no per-call construction. See [`Quadrature.GaussLegendre`](@ref)."
85-
quad::Q = QUAD.build_quadrature(Float64, quadrature_order)
74+
"Quadrature scheme used for the size-distribution integrals (deposition /
75+
sublimation, melting, riming, ice-rain collection, sedimentation). The
76+
scheme — with its parameters, notably the order — is the choice; pass
77+
e.g. `Quadrature.ChebyshevGauss(n)` or `Quadrature.GaussLegendre(FT, n)`
78+
(see [`Quadrature.build_quadrature`](@ref) for the element-type
79+
materialization the toml constructor applies). Lower order → faster,
80+
slightly less accurate; `ChebyshevGauss(100)` matches the original P3
81+
paper sensitivity studies, and n_elem=128 KiD runs show ~5× speed-up at
82+
`GaussLegendre(40)` with negligible bulk error. The object is `isbits`
83+
and reused in the (GPU) hot loop with no per-call construction."
84+
quad::Q = QUAD.ChebyshevGauss(100)
8685
end
8786
Base.show(io::IO, mime::MIME"text/plain", x::P3IceParams) =
8887
ShowMethods.verbose_show_type_and_fields(io, mime, x)
8988

9089
P3IceParams(toml_dict::CP.ParamDict;
91-
is_limited = true, quadrature_order::Int = 100,
90+
is_limited = true, quadrature = QUAD.ChebyshevGauss(100),
9291
inp_depletion_model = NIceProxyDepletion(τ_act = 300),
9392
) = P3IceParams(;
9493
scheme = ParametersP3(toml_dict),
@@ -98,11 +97,10 @@ P3IceParams(toml_dict::CP.ParamDict;
9897
ice_nucleation = Frostenberg2023(toml_dict),
9998
rain_freezing = RainFreezing(toml_dict),
10099
inp_depletion_model,
101-
quadrature_order,
102-
# Build the quadrature in the working float type, so its nodes/weights
100+
# Materialize the scheme in the working float type, so its nodes/weights
103101
# adopt the integrand's eltype (a Float64 rule would leak Float64 into the
104102
# Float32 collision integrals). Construction is host-side and one-shot.
105-
quad = QUAD.build_quadrature(CP.float_type(toml_dict), quadrature_order),
103+
quad = QUAD.build_quadrature(CP.float_type(toml_dict), quadrature),
106104
)
107105

108106
"""
@@ -147,13 +145,13 @@ Create a `Microphysics2MParams` object from a ClimaParams TOML dictionary.
147145
"""
148146
Microphysics2MParams(toml_dict::CP.ParamDict;
149147
with_ice = false, is_limited = true,
150-
quadrature_order::Int = 100,
148+
quadrature = QUAD.ChebyshevGauss(100),
151149
inp_depletion_model = NIceProxyDepletion(τ_act = 300),
152150
) = Microphysics2MParams(;
153151
# Warm rain parameters (always present)
154152
warm_rain = WarmRainParams2M(toml_dict; is_limited),
155153
# Optional ice phase parameters
156154
ice = with_ice ?
157-
P3IceParams(toml_dict; is_limited, quadrature_order, inp_depletion_model) :
155+
P3IceParams(toml_dict; is_limited, quadrature, inp_depletion_model) :
158156
nothing,
159157
)

test/bulk_tendencies_quadrature_tests.jl

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@ using Test
33
import ClimaParams as CP
44
import CloudMicrophysics as CM
55
import CloudMicrophysics.Parameters as CMP
6+
import CloudMicrophysics.Quadrature as QUAD
67
import CloudMicrophysics.BulkMicrophysicsTendencies as BMT
78
import CloudMicrophysics.ThermodynamicsInterface as TDI
89

910
"""
10-
Sweep test for the P3-ice quadrature order. `quadrature_order` now lives
11-
on `P3IceParams`, so the sweep is over `Microphysics2MParams(FT;
12-
with_ice = true, quadrature_order = n)`.
11+
Sweep test for the P3-ice quadrature resolution. The quadrature scheme
12+
(with its order) lives on `P3IceParams`, so the sweep is over
13+
`Microphysics2MParams(FT; with_ice = true, quadrature = ChebyshevGauss(n))`.
1314
1415
Issue 011 measured the worst-case relative error of one single integral
1516
(`bulk_liquid_ice_collision_sources`) across 5 states and 7 orders, and
@@ -240,9 +241,9 @@ end
240241

241242
function test_quadrature_order_sweep(FT)
242243
tps = TDI.TD.Parameters.ThermodynamicsParameters(FT)
243-
# `quadrature_order` now lives on `P3IceParams`, so building one
244+
# the quadrature scheme lives on `P3IceParams`, so building one
244245
# `Microphysics2MParams` per order keeps the sweep clean.
245-
make_mp(n) = CMP.Microphysics2MParams(FT; with_ice = true, quadrature_order = n)
246+
make_mp(n) = CMP.Microphysics2MParams(FT; with_ice = true, quadrature = QUAD.ChebyshevGauss(n))
246247

247248
orders_and_tol = [
248249
(100, FT(2e-3)),
@@ -295,7 +296,23 @@ function test_quadrature_order_sweep(FT)
295296
end
296297
end
297298

298-
@testset "BMT quadrature-order kwarg (Float64)" begin
299+
@testset "BMT quadrature-scheme kwarg (Float64)" begin
299300
test_quadrature_order_sweep(Float64)
300301
end
302+
303+
@testset "quadrature scheme choice and materialization" begin
304+
for FT in (Float32, Float64)
305+
# GaussLegendre: rebuilt in the working float type at the given order
306+
mp_gl = CMP.Microphysics2MParams(FT; with_ice = true, quadrature = QUAD.GaussLegendre(40))
307+
@test mp_gl.ice.quad isa QUAD.GaussLegendre{FT, 40}
308+
# ChebyshevGauss: closed-form nodes, passes through unchanged
309+
mp_cg = CMP.Microphysics2MParams(FT; with_ice = true, quadrature = QUAD.ChebyshevGauss(64))
310+
@test mp_cg.ice.quad isa QUAD.ChebyshevGauss
311+
@test mp_cg.ice.quad.n == 64
312+
end
313+
# default preserves the previous behavior
314+
mp = CMP.Microphysics2MParams(Float64; with_ice = true)
315+
@test mp.ice.quad isa QUAD.ChebyshevGauss
316+
@test mp.ice.quad.n == 100
317+
end
301318
nothing

0 commit comments

Comments
 (0)