Make the pointwise 2M+P3 tendencies ForwardDiff-differentiable#725
Conversation
4759ee8 to
374c47c
Compare
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #725 +/- ##
==========================================
+ Coverage 92.68% 92.96% +0.28%
==========================================
Files 56 56
Lines 2816 2859 +43
==========================================
+ Hits 2610 2658 +48
+ Misses 206 201 -5
🚀 New features to boost your workflow:
|
374c47c to
bdd359d
Compare
|
This change is part of the following stack:
Change managed by git-spice. |
7514748 to
415fe44
Compare
415fe44 to
b150213
Compare
643b5a8 to
aef2c68
Compare
31e584b to
ea7a9b0
Compare
aef2c68 to
4696205
Compare
55f2d59 to
09d92b0
Compare
4696205 to
687d8e7
Compare
687d8e7 to
db7adcf
Compare
26145ad to
2c7d843
Compare
6436c12 to
710d1ce
Compare
98e82b8 to
6878dd5
Compare
`get_state_from_prognostic` is not defined anywhere; the path was unexercised by tests.
Implicit substepping needs Jacobians of the pointwise tendencies w.r.t. the 8 prognostic species (`logλ` held fixed). - Type flexibility for mixed Dual/plain inputs: P3State, Γ_incl, the gamma_inc_inv wrapper, ice_mass_coeffs, logistic_function_integral, Chen2022_exponential_pdf, LclRaiRates, the size-distribution closures. - gamma_inc_inv: add an implicit-function-theorem Dual rule (none upstream), exact in the quantile slots; the shape slot differences the smaller of (P, Q), the larger saturating in the tails. - SB2006 autoconversion: ϕ_au ∝ τ^0.7 has a vertical tangent at τ = 0, so ∂/∂q_rai is NaN at zero rain with cloud present. Condition the enhancement below ϵ_numerics_2M_M; value at q_rai = 0 unchanged. - sgs_weight_function: Check the lower tail where atanh → -Inf makes rime-ratio partials bad in Float32. - Early returns: typing from one argument (e.g. `FT = eltype(q_tot)`) makes the return union-typed and heap-boxed for mixed-type callers. Add Utilities.promote_typeof and apply it where relevant. Tests: per-row Jacobian-vs-FD consistency tests; AD coverage for the regularised-ratio band, the tail shape slots, and mixed arguments; sentinel_type_tests assert concrete returns per signature. Deferred: the logλ shape solve (needs ∂/∂a of forward gamma_inc) and the 1M early return cases.
The analytical solution for ρ_d has a denominator that is 0/0 as F_rim → 0. In Float32 the cancellation drives ρ_d, and therefore ρ_g, negative, which later raises a DomainError in get_D_gr. Rewrite get_ρ_d with the relative exponential functions exprel1 and exprel2 so the cancellation is removed; ρ_g then stays positive for every physical input, with no clipping. Add the derivation and an error-cancellation figure to the P3 documentation, and a Float32 stability test.
044509d to
11eb605
Compare
The differentiated 2M+P3 tendency must compile for CUDA. Several constructs that are fine on the CPU break the GPU compile by heap-allocating or boxing: - get_ρ_d / exponential moments: avoid the host factorial table and Rational exponents (use a `fac` helper and float coefficients). - P3State construction: convert the derived fields with explicit `FT(...)` calls instead of broadcasting the (parametric, possibly `Dual`) element type over a tuple, which the GPU back end heap-allocates. - liquid_freezing_rate: type the fallback zero via `promote_typeof` so the `ifelse` branches share an element type under ForwardDiff. - gamma_inc_inv: take the inverse-CDF shape derivative with a one-sided finite difference at a fixed step, removing one `gamma_inc` evaluation.
11eb605 to
ddd64f0
Compare
| """ | ||
| function liquid_freezing_rate(opt::CMP.RainFreezing, pdf, tps, q, ρ, N, T) | ||
| FT = eltype(q) | ||
| FT = float(UT.promote_typeof(q, ρ, N, T)) |
There was a problem hiding this comment.
How expensive it is per element in the list here? Does it save us any to just have q as before?
There was a problem hiding this comment.
I'm not quite sure what you mean by expensive? I think the "proper" fix instead of this is to merge #746, as we discussed in the office, where FT is derived from the "return type" of the relevant calculation.
Though that PR is currently implemented as cond ? f() : otherwise(FT), so if we're supposed to rewrite everything with ifelse as you mentioned elsewhere, I'd have to update the PR first.
There was a problem hiding this comment.
I meant float(UT.promote_typeof(q, ρ, N, T)) vs float(UT.promote_typeof(q) Since I expect it's q is the one with the potentially tricky type.
And yeah, I wasn't sure how orthogonal our discussion is compared to the GPU performance PR
| # Avoid NaN when both q and x_min are 0 | ||
| N_max = iszero(x_min) ? oftype(q, Inf) : ρ * q / x_min | ||
| FT = UT.promote_typeof(q, ρ, N) | ||
| N_max = iszero(x_min) ? FT(Inf) : FT(ρ * q / x_min) |
There was a problem hiding this comment.
Are we supposed to use ifelse now?
There was a problem hiding this comment.
I suspect the compiler will internally rewrite these to ifelse, but can do it explicitly here for extra safety:)
| """ | ||
| fac(n) | ||
|
|
||
| Integer factorial `n!`, valid for `0 ≤ n ≤ 20`. |
There was a problem hiding this comment.
Should we throw an error for n>20 ?
There was a problem hiding this comment.
sure, I can do that. In practice, we'll never use this for numbers even close to that anyways.
| # fallback values typed by the promotion of the node and the captured state | ||
| # (mixed plain/Dual under differentiation) | ||
| FT = UT.promote_typeof(Dᵢ, ΔT, Δρᵥ_sat, denom) | ||
| Tₐ ≥ T_frz && return zero(FT) # No collisional freezing above the freezing temperature |
There was a problem hiding this comment.
Are we supposed to do ifelse now?
There was a problem hiding this comment.
thanks for the catch! I'll update these.
|
welp. Sorry @trontrytel I'll put your feedback into the next PR. I forgot I had auto merge on! |
No worries! I knowingly hit approve |
Implicit (Rosenbrock-type) microphysics substepping needs Jacobians of the pointwise 2M+P3 tendencies w.r.t. the 8 prognostic species, so
ForwardDiff.jacobianmust work throughbulk_microphysics_tendencies(logλis held fixed). This PR does some groundwork to enable this.P3StateandΓ_inclto accept a mix ofDualand floating point arguments.Dualmethod forSF.gamma_inc_inv(What aboutgamma_inc_inv's derivative ? JuliaMath/SpecialFunctions.jl#452) locally. Ideally this should be added toSpecialFunctions, and I might do so later, but adding here to move forward locally for now. The implementation uses a mix of analytical and central differences of the forward map.ice_mass_coeffsreturns one tuple type (needed for efficient AD).Utilities.promote_typeof), so a caller mixingDualand float arguments does not get a union-typed, heap-boxed return.sgs_weight_function(lower tail rounds 1 - a to 1, so atanh(-1) = -Inf) and SB2006 autoconversion (vertical tangent ofτ^0.7at zero rain), which previously could produce NaNs.get_distribution_logλ_from_prognosticcalled an undefined function.I might try to differentiate through the
logλshape solve later; which needs ∂/∂a of the forwardgamma_inc(JuliaMath/SpecialFunctions.jl#317).Tests cover Jacobians at four regimes for both float types against per-row central finite differences, the
gamma_inc_invrule into theq = eps()tail, the regularised-ratio band, and that the touched functions stay concretely typed under single-Dualand all-Dualargument mixes.