Skip to content

Commit e362cbc

Browse files
committed
perf(BMT): recover the primal tendency from the Rosenbrock Jacobian pass
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.
1 parent 1c96cf0 commit e362cbc

1 file changed

Lines changed: 33 additions & 5 deletions

File tree

src/BMT_rosenbrock.jl

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -438,10 +438,10 @@ fields as the `Instantaneous` entry (without the activation diagnostic).
438438
Tsub = T
439439
for _ in 1:nsub_eff
440440
g = Instantaneous2MP3Tendency(mp, tps, ρ, Tsub, q_tot, logλ)
441-
f = g(x)
442441
x_prev = x
443442
if all(isfinite, x)
444-
J = _apply_growth(mode.growth, FD.jacobian(g, x))
443+
f, J_raw = _tendency_and_jacobian(mode.jacobian, g, x)
444+
J = _apply_growth(mode.growth, J_raw)
445445
z = _species_mask(mode.jacobian, mode.growth)(x)
446446
d = if all(isfinite, J)
447447
_rosenbrock_update(x, f, J, z, h) - x
@@ -451,6 +451,7 @@ fields as the `Instantaneous` entry (without the activation diagnostic).
451451
d = _apply_limiter(mode.limiter, x, d, ρ, Tsub, q_tot, Lv_over_cp, Ls_over_cp, tps)
452452
x = max.(x .+ d, 0)
453453
else
454+
f = g(x)
454455
x = _euler_update(x, f, h)
455456
end
456457
Δ = x - x_prev
@@ -756,6 +757,33 @@ end
756757
"Exact ForwardDiff Jacobian provider for [`_rosenbrock_average_1m`](@ref)."
757758
@inline _ad_jacobian_1m(g, x) = FD.jacobian(g, x)
758759

760+
"""
761+
_tendency_and_jacobian(jacobian, g, x)
762+
763+
The raw substep tendency `f = g(x)` and the substep Jacobian (before the growth
764+
treatment) for a [`Jacobian`](@ref) option, returned as `(f, J)`.
765+
766+
For [`ExactJacobian`](@ref) the primal `f` and the `N×N` Jacobian are both
767+
obtained from `ForwardDiff`. The donor-based matrices ([`DonorJacobian`](@ref),
768+
[`CoupledDonorJacobian`](@ref)) produce no tendency by-product, so `f = g(x)` is
769+
evaluated separately.
770+
"""
771+
@inline function _tendency_and_jacobian(::ExactJacobian, g, x::SA.FieldVector{N, FT}) where {N, FT}
772+
Tag = typeof(FD.Tag(g, FT))
773+
dx = SA.SVector(
774+
ntuple(i -> FD.Dual{Tag}(x[i], ntuple(s -> ifelse(s == i, one(FT), zero(FT)), Val(N))...), Val(N)),
775+
)
776+
y = g(dx)
777+
f = typeof(x)(ntuple(i -> @inbounds(FD.value(y[i])), Val(N))...)
778+
J = SA.SMatrix{N, N, FT}(
779+
ntuple(k -> @inbounds(FD.partials(y[(k - 1) % N + 1], (k - 1) ÷ N + 1)), Val(N * N)),
780+
)
781+
return f, J
782+
end
783+
@inline _tendency_and_jacobian(::DonorJacobian, g, x) = (g(x), _jacobian_1m_linearized(g, x))
784+
@inline _tendency_and_jacobian(::CoupledDonorJacobian, g, x) =
785+
(g(x), _jacobian_1m_coupled(g, x))
786+
759787
"""
760788
_full_species_mask(x)
761789
@@ -784,18 +812,17 @@ and the increment limiter through [`_jacobian_provider`](@ref),
784812
h = Δt / FT(nsub_eff)
785813
Lv_over_cp = TDI.TD.Parameters.LH_v0(tps) / TDI.TD.Parameters.cp_d(tps)
786814
Ls_over_cp = TDI.TD.Parameters.LH_s0(tps) / TDI.TD.Parameters.cp_d(tps)
787-
jacobian = _jacobian_provider(mode.jacobian)
788815
mask = _species_mask(mode.jacobian, mode.growth)
789816

790817
x = MicroState1M{FT}(q_lcl, q_icl, q_rai, q_sno)
791818
x₀ = x
792819
Tsub = T
793820
for _ in 1:nsub_eff
794821
g = Raw1MTendency(mp, tps, ρ, Tsub, q_tot)
795-
f = g(x)
796822
x_prev = x
797823
if all(isfinite, x)
798-
J = _apply_growth(mode.growth, jacobian(g, x))
824+
f, J_raw = _tendency_and_jacobian(mode.jacobian, g, x)
825+
J = _apply_growth(mode.growth, J_raw)
799826
z = mask(x)
800827
d = if all(isfinite, J)
801828
_rosenbrock_update(x, f, J, z, h) - x
@@ -805,6 +832,7 @@ and the increment limiter through [`_jacobian_provider`](@ref),
805832
d = _apply_limiter(mode.limiter, x, d, ρ, Tsub, q_tot, Lv_over_cp, Ls_over_cp, tps)
806833
x = max.(x .+ d, 0)
807834
else
835+
f = g(x)
808836
x = _euler_update(x, f, h)
809837
end
810838
Δ = x - x_prev

0 commit comments

Comments
 (0)