Skip to content

Commit 183d055

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 the separate f = g(x) repeats a full (quadrature- dominated) tendency evaluation. Recover f from the same pass via DiffResults (jacobian! into an immutable JacobianResult). Applies to the 2M+P3 and the 1M ExactJacobian paths; the analytic Donor/CoupledDonor 1M Jacobians (no f by-product) keep their separate evaluation. Both paths now go through the shared `_tendency_and_jacobian(jacobian, g, x)` method, which reconstructs the same `FieldVector` species type as `x` (`MicroState1M` or `MicroState2MP3`), removing the duplicated DiffResults code in the 2M+P3 substep loop. ~27% faster per Rosenbrock substep; f and J bit-identical, no extra allocations, type-stable, composes with outer AD.
1 parent 49695f3 commit 183d055

3 files changed

Lines changed: 30 additions & 5 deletions

File tree

Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ authors = ["Climate Modeling Alliance"]
55

66
[deps]
77
ClimaParams = "5c42b081-d73a-476f-9059-fd94b934656c"
8+
DiffResults = "163ba53b-c6d8-5494-b064-1a9d43ac40c5"
89
DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
910
FastGaussQuadrature = "442a2c76-b920-505d-bb47-c5924d526838"
1011
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
@@ -27,6 +28,7 @@ EmulatorModelsExt = ["DataFrames", "MLJ"]
2728
[compat]
2829
ClimaParams = "1.0.18"
2930
DataFrames = "1.6"
31+
DiffResults = "1"
3032
DocStringExtensions = "0.8, 0.9"
3133
FastGaussQuadrature = "1"
3234
ForwardDiff = "0.10, 1"

src/BMT_rosenbrock.jl

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -429,10 +429,10 @@ fields as the `Instantaneous` entry (without the activation diagnostic).
429429
Tsub = T
430430
for _ in 1:nsub_eff
431431
g = Instantaneous2MP3Tendency(mp, tps, ρ, Tsub, q_tot, logλ)
432-
f = g(x)
433432
x_prev = x
434433
if all(isfinite, x)
435-
J = _apply_growth(mode.growth, FD.jacobian(g, x))
434+
f, J_raw = _tendency_and_jacobian(mode.jacobian, g, x)
435+
J = _apply_growth(mode.growth, J_raw)
436436
z = _species_mask(mode.jacobian, mode.growth)(x)
437437
d = if all(isfinite, J)
438438
_rosenbrock_update(x, f, J, z, h) - x
@@ -442,6 +442,7 @@ fields as the `Instantaneous` entry (without the activation diagnostic).
442442
d = _apply_limiter(mode.limiter, x, d, ρ, Tsub, q_tot, Lv_over_cp, Ls_over_cp, tps)
443443
x = max.(x .+ d, 0)
444444
else
445+
f = g(x)
445446
x = _euler_update(x, f, h)
446447
end
447448
Δ = x - x_prev
@@ -749,6 +750,27 @@ end
749750
"Exact ForwardDiff Jacobian provider for [`_rosenbrock_average_1m`](@ref)."
750751
@inline _ad_jacobian_1m(g, x) = FD.jacobian(g, x)
751752

753+
"""
754+
_tendency_and_jacobian(jacobian, g, x)
755+
756+
The raw substep tendency `f = g(x)` and the substep Jacobian (before the growth
757+
treatment) for a [`Jacobian`](@ref) option, returned as `(f, J)`.
758+
759+
For [`ExactJacobian`](@ref) the Jacobian is taken with `ForwardDiff` through
760+
`DiffResults`, which records the primal `.value` of the same pass as `f`;
761+
`JacobianResult` is immutable for a `StaticArray`, so the `jacobian!` return value
762+
is captured. The donor-based matrices ([`DonorJacobian`](@ref),
763+
[`CoupledDonorJacobian`](@ref)) produce no tendency by-product, so `f = g(x)` is
764+
evaluated separately.
765+
"""
766+
@inline function _tendency_and_jacobian(::ExactJacobian, g, x::SA.FieldVector{N, FT}) where {N, FT}
767+
r = FD.jacobian!(DR.JacobianResult(x), g, x)
768+
return typeof(x)(DR.value(r)), DR.jacobian(r)
769+
end
770+
@inline _tendency_and_jacobian(::DonorJacobian, g, x) = (g(x), _jacobian_1m_linearized(g, x))
771+
@inline _tendency_and_jacobian(::CoupledDonorJacobian, g, x) =
772+
(g(x), _jacobian_1m_relinearized(g, x))
773+
752774
"""
753775
_full_species_mask(x)
754776
@@ -777,18 +799,17 @@ and the increment limiter through [`_jacobian_provider`](@ref),
777799
h = Δt / FT(nsub_eff)
778800
Lv_over_cp = TDI.TD.Parameters.LH_v0(tps) / TDI.TD.Parameters.cp_d(tps)
779801
Ls_over_cp = TDI.TD.Parameters.LH_s0(tps) / TDI.TD.Parameters.cp_d(tps)
780-
jacobian = _jacobian_provider(mode.jacobian)
781802
mask = _species_mask(mode.jacobian, mode.growth)
782803

783804
x = MicroState1M{FT}(q_lcl, q_icl, q_rai, q_sno)
784805
x₀ = x
785806
Tsub = T
786807
for _ in 1:nsub_eff
787808
g = Raw1MTendency(mp, tps, ρ, Tsub, q_tot)
788-
f = g(x)
789809
x_prev = x
790810
if all(isfinite, x)
791-
J = _apply_growth(mode.growth, jacobian(g, x))
811+
f, J_raw = _tendency_and_jacobian(mode.jacobian, g, x)
812+
J = _apply_growth(mode.growth, J_raw)
792813
z = mask(x)
793814
d = if all(isfinite, J)
794815
_rosenbrock_update(x, f, J, z, h) - x
@@ -798,6 +819,7 @@ and the increment limiter through [`_jacobian_provider`](@ref),
798819
d = _apply_limiter(mode.limiter, x, d, ρ, Tsub, q_tot, Lv_over_cp, Ls_over_cp, tps)
799820
x = max.(x .+ d, 0)
800821
else
822+
f = g(x)
801823
x = _euler_update(x, f, h)
802824
end
803825
Δ = x - x_prev

src/BulkMicrophysicsTendencies.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import ..HetIceNucleation as CM_HetIce
3535
import ...ThermodynamicsInterface as TDI
3636
import ..Common as CO
3737
import ForwardDiff as FD
38+
import DiffResults as DR
3839
import StaticArrays as SA
3940

4041
export MicrophysicsScheme,

0 commit comments

Comments
 (0)