feat(BMT): coupled Tier-3 donor linearization for the 2M+P3 ManualJacobian#744
Draft
haakon-e wants to merge 36 commits into
Draft
feat(BMT): coupled Tier-3 donor linearization for the 2M+P3 ManualJacobian#744haakon-e wants to merge 36 commits into
haakon-e wants to merge 36 commits into
Conversation
Member
Author
|
This change is part of the following stack:
Change managed by git-spice. |
This was referenced Jun 23, 2026
haakon-e
force-pushed
the
he/rosenbrock-manual-2mp3
branch
from
June 23, 2026 00:32
6ab368f to
414fb48
Compare
haakon-e
force-pushed
the
he/rosenbrock-manual-tier3
branch
from
June 23, 2026 00:32
0f05968 to
c8ebd41
Compare
haakon-e
force-pushed
the
he/rosenbrock-manual-2mp3
branch
from
June 23, 2026 21:32
414fb48 to
3f36245
Compare
haakon-e
force-pushed
the
he/rosenbrock-manual-tier3
branch
from
June 23, 2026 21:32
c8ebd41 to
70688b7
Compare
haakon-e
force-pushed
the
he/rosenbrock-manual-2mp3
branch
from
June 23, 2026 22:26
3f36245 to
1a5a00d
Compare
haakon-e
force-pushed
the
he/rosenbrock-manual-tier3
branch
from
June 23, 2026 22:26
70688b7 to
f7f0804
Compare
haakon-e
force-pushed
the
he/rosenbrock-manual-2mp3
branch
from
June 24, 2026 21:56
1a5a00d to
53e48a3
Compare
haakon-e
force-pushed
the
he/rosenbrock-manual-tier3
branch
from
June 24, 2026 21:56
f7f0804 to
5dc3a29
Compare
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## he/rosenbrock-manual-2mp3 #744 +/- ##
============================================================
Coverage ? 93.30%
============================================================
Files ? 57
Lines ? 3463
Branches ? 0
============================================================
Hits ? 3231
Misses ? 232
Partials ? 0
🚀 New features to boost your workflow:
|
haakon-e
force-pushed
the
he/rosenbrock-manual-2mp3
branch
from
June 25, 2026 18:01
53e48a3 to
a2f9619
Compare
haakon-e
force-pushed
the
he/rosenbrock-manual-tier3
branch
from
June 25, 2026 18:01
5dc3a29 to
3913282
Compare
haakon-e
force-pushed
the
he/rosenbrock-manual-2mp3
branch
from
June 26, 2026 00:38
a2f9619 to
22b1886
Compare
haakon-e
force-pushed
the
he/rosenbrock-manual-tier3
branch
from
June 26, 2026 00:38
3913282 to
c8c9b3d
Compare
haakon-e
force-pushed
the
he/rosenbrock-manual-2mp3
branch
from
June 26, 2026 01:22
22b1886 to
ac7f724
Compare
haakon-e
force-pushed
the
he/rosenbrock-manual-tier3
branch
from
June 26, 2026 01:22
c8c9b3d to
1d56e11
Compare
haakon-e
force-pushed
the
he/rosenbrock-manual-2mp3
branch
from
June 26, 2026 01:31
ac7f724 to
8bdc7f1
Compare
haakon-e
force-pushed
the
he/rosenbrock-manual-tier3
branch
from
June 26, 2026 01:31
1d56e11 to
cdf374d
Compare
haakon-e
force-pushed
the
he/rosenbrock-manual-2mp3
branch
from
July 1, 2026 05:35
8bdc7f1 to
cf9bec0
Compare
haakon-e
force-pushed
the
he/rosenbrock-manual-tier3
branch
from
July 1, 2026 05:35
cdf374d to
cfa1630
Compare
haakon-e
force-pushed
the
he/rosenbrock-manual-2mp3
branch
from
July 11, 2026 00:58
a966e54 to
2228883
Compare
haakon-e
force-pushed
the
he/rosenbrock-manual-tier3
branch
from
July 11, 2026 00:58
0ef8741 to
db4e2f2
Compare
haakon-e
force-pushed
the
he/rosenbrock-manual-2mp3
branch
from
July 11, 2026 02:14
2228883 to
58a62eb
Compare
haakon-e
force-pushed
the
he/rosenbrock-manual-tier3
branch
from
July 11, 2026 02:14
db4e2f2 to
df8a7ef
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.
Extend the unified RosenbrockAverage framework to the one-moment model: add the DonorCellJacobian and CoupledDonorJacobian providers and channel masks, the rosenbrock_donor() and rosenbrock_coupled() presets, and the Microphysics1Moment substep driver. LinearizedAverage now forwards to rosenbrock_donor(), the donor-cell configuration of the framework. The one-moment kernels are made ForwardDiff-able so rosenbrock_exact() runs on the 1M model. Documentation and Float64/Float32 tests cover the donor/coupled/exact presets, the keyword constructor, donor-equals- LinearizedAverage, and zero-allocation hot calls.
The 2M+P3 RosenbrockAverage entry point dispatches only on ExactJacobian (or ManualJacobian); the default RosenbrockAverage() selects DonorJacobian, which the guard rejects with an ArgumentError. Pass rosenbrock_exact() so the kernel exercises the intended path.
Extract the shared saturation bisection from the two EndStateSaturationAdjustment methods. Rename _jacobian_1m_relinearized to _jacobian_1m_coupled to match CoupledDonorJacobian and rosenbrock_coupled(). Parametrize the 1M convergence tests over the limiter-free presets and use named presets instead of the bare default constructor; drop a rationale comment covered by the AD-compatibility guide.
Cover rosenbrock_donor, rosenbrock_coupled, and rosenbrock_exact through the 1M bulk_microphysics_tendencies entry on the GPU backend.
Guard the Verbose substep Jacobian on state finiteness, matching the non-verbose loops' Euler fallback. Add an informative error for Verbose with a non-Exact Jacobian on the 2M+P3 scheme. Test that a limiter-free exact-Jacobian mode gives identical verbose and non-verbose nets.
EndStateSaturationAdjustment now keeps the latent-heated end state at or above saturation over its more-supersaturated phase, max(S_ice, S_liq), instead of over ice alone. Below freezing ice carries the larger supersaturation, so the criterion reduces to the previous ice cap and the cold deposition-instability cure is unchanged; above freezing it caps on liquid and removes the warm under-condensation an ice-only cap leaves behind. Add a documentation section deriving the criterion with a 0-D vapor-exchange-only illustration of the Wegener-Bergeron-Findeisen transfer, and a framework test for the warm (liquid) and cold (ice) caps.
Mirror the warm subtest in the cold block, so a regression that disables the ice-side limiter is caught. Drop markdown emphasis from the saturation-adjustment documentation.
The mixed-phase saturation section labelled the coarse-step instability a "deposition instability", but the positive Jacobian diagonal sits in the rime-mass row (riming self-gain); the pure-deposition diagonal is negative there. Rename the reference to "ice-growth instability" to match the section heading and the mechanism.
ForwardDiff's Jacobian pass already evaluates the primal tendency f as the dual .value and discards it, so a separate f = g(x) repeats a full (quadrature- dominated) tendency evaluation. Recover f from the same pass: seed all N partials of the state in a single call to g with static Duals, and read the values and partials directly into the same FieldVector species type as x (MicroState1M or MicroState2MP3). Applies to the 2M+P3 and the 1M ExactJacobian paths; the analytic Donor/CoupledDonor 1M Jacobians (no f by-product) keep their separate evaluation. The DiffResults dependency is dropped: DiffResults.JacobianResult's mutable result buffer heap-allocates on GPU, so the seeding is done directly with StaticArrays instead. Both paths go through the shared _tendency_and_jacobian(jacobian, g, x) method, removing the duplicated code in the 2M+P3 substep loop. Measured on MicroState2MP3 (N=8, quadrature-dominated) and MicroState1M (N=4): the single full-width call is ~31% faster than the previous two-call f = g(x); J = FD.jacobian(g, x) for 2M+P3, and ~39% faster for 1M, with zero allocations in both cases. f and J are bitwise identical to the two-call reference across representative states. The tendency and Jacobian are unchanged, the path stays type-stable, and it composes with an outer AD pass.
Add a manually-linearized Jacobian (`ManualJacobian` + `rosenbrock_manual()`) for the two-moment + P3 microphysics Rosenbrock substep, as an alternative to the ForwardDiff `ExactJacobian` (which is ~2.7x the tendency and dominates the substep cost). Tiered after the one-moment donor Jacobians: - Tier 1 (closed form): cloud condensation/evaporation and ice deposition/sublimation (including the ice-number sublimation channel), the number adjustments, and the F23 nucleation number channel -- the stiff autocatalytic supersaturation block, captured exactly (matches ForwardDiff to ~3e-12 in Float64). - Tier 2 (donor-diagonal): the warm-rain transfers and freezing, linearized in their donor species via `D = rate / max(q_min, q_donor)`. - Tier 3 (dropped from the Jacobian): the quadrature ice collision / aggregation / melt couplings, kept explicit in the tendency. This avoids any `gamma_inc` shape derivative. The default `ExactJacobian` path is unchanged (purely additive). 0 allocations, type-stable, all existing rosenbrock tests pass.
…ries Add a ManualJacobian smoke testset mirroring the rosenbrock_exact one and validate the closed-form Tier-1 derivatives against ForwardDiff of the primal functions they linearize, across their branch regimes. Unify the ExactJacobian and ManualJacobian 2M+P3 substep entries into one method. Share the ice number-adjustment bounds between the primal tendency and the manual Jacobian; rename q_cap/cap_is_ice to q_limit/limit_is_ice; name rosenbrock_manual() in the 2M+P3 mode error message.
…obian Make the ManualJacobian usable in mixed-phase by donor-linearizing the mixed-phase quadrature transfers into the substep Jacobian. For a transfer of primal rate S from donor d to receiver r, add -D on the donor diagonal and +D on the (r, d) off-diagonal with D = S/max(floor, x_d), reusing the per-process rates of _per_process_2mp3 (no gamma_inc shape derivative). The dominant coupling is the ice->rain melt source (mass donor q_ice, number donor n_ice, rim mass/volume on their own donors); without it the rain melt source integrates explicitly and diverges with the step. The liquid-ice collision cloud/rain sinks self-limit on their own donors. Mixed-phase error vs the fine explicit reference drops from 88-2420 (non-convergent) to 0.0157-1.54 and converges under nsub, matching ExactJacobian to the same order of magnitude; ice-only is unchanged (melt/collision inactive below freezing). 0-alloc, type-stable (F32+F64); existing rosenbrock tests pass.
…Jacobian Add the missing rain-mass donor term of the liquid-ice collision to _jacobian_2mp3_manual, completing the set the surrounding comment and the ManualJacobian docstring describe. In a riming-active state the exact (rai, rai) diagonal is -1.7 per second while the previous manual entry was -3.5e-4; with the term the manual entry is -1.4 and substep refinement is monotone. Add a manual-mode convergence regression test in that state, reuse the Tier-2 donor factors, and state the ice-side receivers' positivity-clamp bound.
haakon-e
force-pushed
the
he/rosenbrock-manual-2mp3
branch
from
July 11, 2026 02:31
58a62eb to
958a343
Compare
haakon-e
force-pushed
the
he/rosenbrock-manual-tier3
branch
from
July 11, 2026 02:31
df8a7ef to
6f3f04c
Compare
haakon-e
force-pushed
the
he/rosenbrock-manual-2mp3
branch
from
July 11, 2026 03:28
958a343 to
ea43e21
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.
Make the ManualJacobian usable in mixed-phase by donor-linearizing the mixed-phase quadrature transfers into the substep Jacobian. For a transfer of primal rate S from donor d to receiver r, add -D on the donor diagonal and +D on the (r, d) off-diagonal with D = S/max(floor, x_d), reusing the per-process rates of _per_process_2mp3 (no gamma_inc shape derivative).
The dominant coupling is the ice->rain melt source (mass donor q_ice, number donor n_ice, rim mass/volume on their own donors); without it the rain melt source integrates explicitly and diverges with the step. The liquid-ice collision cloud/rain sinks self-limit on their own donors.
Purpose
To-do
Content