feat(Rosenbrock): unified RosenbrockAverage substepping framework + 2M+P3 exact-AD mode#726
Open
haakon-e wants to merge 8 commits into
Open
feat(Rosenbrock): unified RosenbrockAverage substepping framework + 2M+P3 exact-AD mode#726haakon-e wants to merge 8 commits into
haakon-e wants to merge 8 commits into
Conversation
Member
Author
|
This change is part of the following stack:
Change managed by git-spice. |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## he/ice-selfcol-triangle #726 +/- ##
===========================================================
- Coverage 92.81% 92.65% -0.16%
===========================================================
Files 56 57 +1
Lines 2952 3065 +113
===========================================================
+ Hits 2740 2840 +100
- Misses 212 225 +13
🚀 New features to boost your workflow:
|
haakon-e
force-pushed
the
he/rosenbrock-2m-p3
branch
from
June 12, 2026 02:17
2171cac to
ba40a9f
Compare
haakon-e
changed the base branch from
he/ad-compat-2m-p3
to
he/sentinel-return-types
June 12, 2026 04:12
haakon-e
force-pushed
the
he/rosenbrock-2m-p3
branch
from
June 12, 2026 04:12
1e73ee4 to
f250be2
Compare
haakon-e
changed the base branch from
he/sentinel-return-types
to
he/ad-compat-2m-p3
June 12, 2026 15:31
haakon-e
force-pushed
the
he/rosenbrock-2m-p3
branch
4 times, most recently
from
June 12, 2026 18:21
ddc9f3a to
897e48f
Compare
haakon-e
force-pushed
the
he/ad-compat-2m-p3
branch
2 times, most recently
from
June 14, 2026 02:02
7514748 to
415fe44
Compare
haakon-e
force-pushed
the
he/rosenbrock-2m-p3
branch
from
June 14, 2026 02:02
897e48f to
730166f
Compare
This was referenced Jun 14, 2026
haakon-e
force-pushed
the
he/ad-compat-2m-p3
branch
from
June 14, 2026 02:45
415fe44 to
b150213
Compare
haakon-e
force-pushed
the
he/rosenbrock-2m-p3
branch
2 times, most recently
from
June 16, 2026 20:00
1fa016b to
9bffa2c
Compare
haakon-e
force-pushed
the
he/rosenbrock-2m-p3
branch
from
June 17, 2026 21:20
9bffa2c to
5ba20ad
Compare
haakon-e
force-pushed
the
he/rosenbrock-2m-p3
branch
from
June 18, 2026 21:42
5ba20ad to
c161025
Compare
haakon-e
force-pushed
the
he/ad-compat-2m-p3
branch
from
June 18, 2026 22:41
643b5a8 to
aef2c68
Compare
haakon-e
force-pushed
the
he/rosenbrock-2m-p3
branch
from
June 18, 2026 22:41
c161025 to
326fb30
Compare
haakon-e
force-pushed
the
he/rosenbrock-2m-p3
branch
from
June 26, 2026 00:38
d9cb1e6 to
a5d4a45
Compare
haakon-e
force-pushed
the
he/ad-compat-2m-p3
branch
from
June 26, 2026 01:22
ee98c2d to
98e82b8
Compare
haakon-e
force-pushed
the
he/rosenbrock-2m-p3
branch
from
June 26, 2026 01:22
a5d4a45 to
631d52a
Compare
haakon-e
force-pushed
the
he/ad-compat-2m-p3
branch
from
June 26, 2026 01:31
98e82b8 to
6878dd5
Compare
haakon-e
force-pushed
the
he/rosenbrock-2m-p3
branch
from
June 26, 2026 01:31
631d52a to
62a00be
Compare
haakon-e
force-pushed
the
he/ad-compat-2m-p3
branch
from
July 1, 2026 05:34
6878dd5 to
044509d
Compare
haakon-e
force-pushed
the
he/rosenbrock-2m-p3
branch
from
July 1, 2026 05:34
62a00be to
0f9989e
Compare
haakon-e
force-pushed
the
he/ad-compat-2m-p3
branch
from
July 1, 2026 05:45
044509d to
11eb605
Compare
haakon-e
force-pushed
the
he/rosenbrock-2m-p3
branch
from
July 1, 2026 05:46
0f9989e to
b3cba94
Compare
haakon-e
force-pushed
the
he/ad-compat-2m-p3
branch
from
July 1, 2026 06:24
11eb605 to
ddd64f0
Compare
haakon-e
force-pushed
the
he/rosenbrock-2m-p3
branch
2 times, most recently
from
July 1, 2026 15:01
4cc8acb to
5336a31
Compare
haakon-e
force-pushed
the
he/rosenbrock-2m-p3
branch
from
July 8, 2026 04:34
5336a31 to
558fe72
Compare
haakon-e
force-pushed
the
he/rosenbrock-2m-p3
branch
2 times, most recently
from
July 10, 2026 23:15
4eff57c to
f91130e
Compare
haakon-e
force-pushed
the
he/ice-selfcol-triangle
branch
from
July 11, 2026 00:58
bbb3da8 to
8a2faf4
Compare
haakon-e
force-pushed
the
he/rosenbrock-2m-p3
branch
2 times, most recently
from
July 11, 2026 02:31
1082418 to
61d0407
Compare
…D substepping
Introduce the RosenbrockAverage{Jacobian, GrowthTreatment, TendencyLimiter}
tendency-mode framework and its two-moment + P3 exact-AD substep path.
- RosenbrockAverage struct, keyword constructor, and the rosenbrock_exact()
preset (ExactJacobian + ExplicitGrowthDiagonal + EndStateSaturationAdjustment).
- The Jacobian / GrowthTreatment / TendencyLimiter option families with the
concrete subtypes used by the exact preset.
- The 2M+P3 linearized-implicit (Rosenbrock-Euler) driver differentiating the
raw instantaneous tendency with ForwardDiff, the channel projection, the
equilibrated solve, and the ice-only end-state saturation adjustment.
- Documentation page (RosenbrockNumerics.md), API entries, and a 2M+P3
ExactJacobian framework test.
…fields Add a ClimaCore test that broadcasts bulk_microphysics_tendencies (RosenbrockAverage exact Jacobian, 2M+P3) over column and extruded-sphere spaces, exercising the highest-level substepping entry point. Gated to Julia >= 1.12, matching the inference-depth limit of the existing 2M+P3 kernel tests.
The Rosenbrock/2M+P3 docstrings cross-reference internal helper functions (e.g. the substep and per-process functions) that are not part of the exported API and therefore have no `@docs` entry. Set `warnonly = [:cross_references]` so these `@ref`s warn rather than fail the documentation build.
…elpers Add a dedicated error method for RosenbrockAverage with warm-rain-only Microphysics2MParams, so the failure names the missing P3 ice parameters instead of misreporting a Jacobian-choice problem. Document the internal Rosenbrock substep helpers in an API.md docs block and drop the site-wide warnonly cross-reference relaxation. Note on the Jacobian abstract type which options have methods.
Add a Dual method for `gamma_inc_moment` that takes the value from the
plain-`Real` method and assembles the partials from the closed-form integrand:
`∂M/∂D₁ = -D₁ᵖ e^{-αD₁}`, `∂M/∂D₂ = D₂ᵖ e^{-αD₂}`, `∂M/∂α = -M(D₁, D₂, p+1, α)`.
Differentiating the plain method traces ForwardDiff through the difference of two
near-equal regularized incomplete gammas; on narrow subintervals this loses
precision and can flip the sign of the derivative in Float32. The analytic rule
avoids the differencing and leaves the primal values unchanged.
The regularised-ratio blend weight (`sgs_weight_function`, used by `rime_density` and `rime_mass_fraction`) evaluated `(1 + tanh(2 * atanh(u))) / 2` with `u = 1 - 2 * (1 - a)^(-1 / log2(1 - a_half))`. In the sigmoid transition band, `u` rounds to exactly `±1` in single precision (the half-ULP gap at `1` exceeds `2 * (1 - a)^p`), so `atanh(u)` returns `±Inf`. The composed `tanh` recovers a finite value, but the ForwardDiff derivative is `Inf * 0 = NaN`. The existing extreme-value guards (`a > 42 * a_half`, `4 * a < eps`) do not cover this band for Float32, where `42 * eps(Float32) ≈ 5e-6` overlaps the physical scale of the rime volume `b_rim`. Replace the branch with the algebraic identity `tanh(2 * atanh(u)) = 2u / (1 + u^2)`, giving `(u + 1)^2 / (2 * (1 + u^2))`. This removes the `atanh`/`tanh` saturation, is value-identical to within one ULP across the band (byte-identical where the value is already `0`/`0.5`/`1`), and has finite derivatives throughout. It unblocks the exact-AD 2M+P3 Jacobian at rimed pure-ice states, where `ρ_rim = q_rim / b_rim` previously produced NaN partials.
…allocation/inference test
… test The exact-AD 2M+P3 Rosenbrock substep is type-stable and allocation-free on all CI Julia versions; the previous `VERSION >= v"1.12"` guard hid a test-harness artifact, not a real allocation. The `_framework_exact_call` helper returned a closure that captured the element type `FT`; capturing a type in a closure boxes it on Julia < 1.12, which made both `@inferred` report a non-concrete return and `@allocated` count the boxed call. Return a materialized argument tuple instead of an `FT`-capturing closure, and measure allocations with an interpolated `BenchmarkTools` benchmark (as in `bench_press`), which reports the call's own allocations rather than the surrounding closure. The `@inferred`, JET `@test_opt`, and zero-allocation assertions then pass on Julia 1.10, 1.11, and 1.12, so the guard is removed.
haakon-e
force-pushed
the
he/ice-selfcol-triangle
branch
from
July 11, 2026 03:28
dc1db96 to
355b541
Compare
haakon-e
force-pushed
the
he/rosenbrock-2m-p3
branch
from
July 11, 2026 03:28
61d0407 to
8ae90c4
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this adds
Introduces the
RosenbrockAverage{Jacobian, GrowthTreatment, TendencyLimiter}options framework for averaged-tendency microphysics substepping, with its first use: the 2-moment warm-rain + P3 ice (Microphysics2Moment) exact-AD mode.Each call divides
Δtintonsublinearized-implicit (Rosenbrock-Euler) substeps; per substep it solves(I/h − J) Δ = f(x), clamps the updated state to nonnegative values, and advances temperature from the latent heat of the realized increment.fis the instantaneous tendency andJis the ForwardDiff Jacobian off(x);logλandq_totare held fixed across the interval.The mode is configured by:
Jacobian, the substep matrix. Options:ExactJacobian, the exact ForwardDiff derivative. (DonorJacobianandCoupledDonorJacobian, the donor-based linearizations, are 1M-only and arrive in feat(Rosenbrock): 1M donor / coupled / exact modes on the unified framework #731.)GrowthTreatment, how the positive growth diagonal enters the operator. Options:ImplicitGrowth;ExplicitGrowthDiagonal, which zeros the positive growth diagonal so a growth mode is taken explicitly.TendencyLimiter, a limiter on the realized increment. Options:NoLimiter;EndStateSaturationAdjustment, which scales the increment so the latent-heated end state stays at or above ice saturation.The preset
rosenbrock_exact()=ExactJacobian+ExplicitGrowthDiagonal+EndStateSaturationAdjustmentis the 2M+P3 mode. On 2M+P3 onlyExactJacobianis supported.Coarse-step ice-growth instability
At cold, ice-supersaturated states carrying supercooled cloud liquid, the ice-growth tendency has an autocatalytic mode: rime mass grows by collecting cloud droplets, and the exact Jacobian carries this as a positive diagonal in the rime-mass (
q_rim) row. At a representative isolated cell —ρ = 1.0kg m⁻³,T = 263K,q_tot = 10⁻²,q_lcl = 2 × 10⁻³,n_lcl = 10⁸m⁻³,q_ice = 2 × 10⁻³,n_ice = 10⁴m⁻³, unrimed, ice supersaturationS_ice ≈ 1.8— this diagonal is+5 × 10⁻² s⁻¹(time scale ≈ 20 s, identical in Float32 and Float64), and it grows past10⁻¹ s⁻¹at colder, more liquid-rich states. The mode requires supercooled liquid: with the same ice state but no cloud liquid the rime-mass diagonal falls to order10⁻⁵ s⁻¹, and the pure-deposition diagonal is negative there (vapor depletion opposes further deposition).With
ImplicitGrowththe implicit operator loses definiteness once the substep is coarse relative to the growth time scale, and a single coarse step overshoots saturation.ExplicitGrowthDiagonalremoves that mode from the implicit operator andEndStateSaturationAdjustmentsupplies the nonlinear saturation bound; together they make the exact scheme robust across the resolved range of time steps. The crash itself is a property of the single-column convective configuration, not of an isolated cell: the growth diagonal above is an isolated-cell measurement, but the non-physical blow-up reproduces only in the coupled single-column run.docs/src/RosenbrockNumerics.mddocuments the mechanism, the resolution, the API and how to extend it, and the experiments that did not resolve it.Conditioning
Per substep, a diagonal equilibration brings the number and mass rows (which differ by about nine orders of magnitude) to a comparable scale before the solve, and
max(x + Δ, 0)is applied afterward; a non-finite Jacobian falls back to a forward-Euler substep. For theImplicitGrowthconfiguration, a mask can advance near-empty species by forward Euler rather than including them in the implicit solve (the tendency gradients are very large at near-zero mass, which degrades the conditioning and accuracy of the solve).Float32 robustness
The exact ForwardDiff Jacobian requires the tendency's derivatives to stay finite in single precision, where two sites did not:
sgs_weight_function(the rime-density blend): the sigmoid argumentucan round to±1within the transition band, soatanh(±1) = ±Infreturns a finite value with aNaNderivative at rimed pure-ice states. This is chiefly a single-precision hazard, but the same band-edge rounding also occurs in Float64. The branch now uses the identitytanh(2·atanh(u)) = 2u/(1 + u²), which differentiates finitely across the band and matches the original value to within one to two ULP.gamma_inc_moment: given an analytic ForwardDiff derivative (∂/∂α = −gamma_inc_moment(·, p+1, α),∂/∂D = D^p e^{−αD}), so the closed-form rain integrals differentiate through the exact integrand rather than through a difference of near-equal incomplete gammas, which loses precision on the narrow subintervals from the fall-speed-crossing split. Values are unchanged.Testing
Tests of the 2M+P3 exact mode: finite tendencies across
Δt × nsubin Float32 and Float64; a non-ExactJacobianRosenbrockAverageand warm-rain-only (with_ice = false) parameters both throw; therosenbrock_exact()bulk tendencies broadcast over ClimaCore fields; and an assertion that the exact 2M+P3 substep is type-stable (@inferred, JET@test_opt) and allocation-free. The exact-AD 2M+P3 substep is type-stable and allocation-free on the CI Julia versions; the allocation check uses an interpolatedBenchmarkToolsbenchmark (as inbench_press), which measures the call's own allocations rather than a surrounding closure, so the assertion runs unconditionally.Foundation for the 1M modes (#731) and the verbose diagnostics (#732).