Skip to content

Commit f227cd9

Browse files
committed
Generalize the Rosenbrock substep kernel over state dimension
`_rosenbrock_update` hardcoded the 8-component 2M+P3 state: it built `one(SMatrix{8,8,FT})` and computed the channel mask internally from the named `MicroState2MP3` fields. Re-express it over any `StaticArrays.StaticVector{N}`, sizing the identity and the dense diagonal matrices (P, S, S⁻¹) from the static length `N`, and accept the channel mask `z` as an argument so the kernel carries no scheme-specific channel knowledge. The equilibration, `(I/h − P J P)` solve, and positivity clamp are unchanged. The 2M+P3 channel mask (`_rosenbrock_channel_mask`) stays scheme-specific; the `RosenbrockAverage` entry now computes it and threads it into the generic kernel. This makes a future fixed-dimension state (e.g. a 4-species 1M vector with its own mask) a drop-in without touching the solver. 2M+P3 behavior is preserved bit-for-bit: the operations are identical, only generic over dimension. The `RosenbrockAverage` regression suite passes for Float64 and Float32 with unchanged tolerances, the 0-byte/call allocation assertion still holds, and JET is report-free on the 2M entry for both float types.
1 parent 2a5dd29 commit f227cd9

1 file changed

Lines changed: 15 additions & 10 deletions

File tree

src/BMT_rosenbrock.jl

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -115,31 +115,35 @@ Forward-Euler substep, floored at zero.
115115
@inline _euler_update(x, f, h) = max.(x .+ h .* f, 0)
116116

117117
"""
118-
_rosenbrock_update(x, f, J, h)
118+
_rosenbrock_update(x, f, J, z, h)
119119
120120
One linearized-implicit (Rosenbrock-Euler) substep: solve
121121
122122
(I/h - P J P) Δx = f
123123
124124
and return `max.(x + Δx, 0)`, where `P = Diagonal(z)` is the channel
125-
projection of [`_rosenbrock_channel_mask`](@ref). The system is solved in
125+
projection built from the per-scheme channel mask `z` (e.g.
126+
[`_rosenbrock_channel_mask`](@ref) for 2M+P3). The system is solved in
126127
equilibrated variables: with `S = Diagonal(|x| + h |f| + ϵ)` the similarity
127128
transform `S⁻¹ A S` is O(1)-conditioned — the raw rows span ~9 orders of
128129
magnitude (number vs mass species), and an unscaled Float32 factorization
129130
bleeds roundoff from the large rows into empty species as phantom mass.
130131
Equilibration is exact in exact arithmetic and keeps roundoff relative to
131132
each species' own scale.
133+
134+
Dimension-generic over any `StaticArrays.StaticVector{N}` state: the
135+
identity and dense diagonal matrices are sized from the static length `N`,
136+
so any prognostic vector and its matching channel mask plug in unchanged.
132137
"""
133-
@inline function _rosenbrock_update(x::MicroState2MP3{FT}, f, J, h) where {FT}
134-
I₈ = one(SA.SMatrix{8, 8, FT})
135-
z = _rosenbrock_channel_mask(x)
138+
@inline function _rosenbrock_update(x::SA.StaticVector{N, FT}, f, J, z, h) where {N, FT}
139+
Iₙ = one(SA.SMatrix{N, N, FT})
136140
s = abs.(x) .+ h .* abs.(f) .+ eps(FT)
137141
# dense diagonal matrices: an `SDiagonal` wrapper here defeats the
138142
# optimizer's static-array stack allocation (heap spills per substep)
139-
P = I₈ .* z'
140-
S = I₈ .* s'
141-
S⁻¹ = I₈ .* inv.(s)'
142-
A = I₈ / h - S⁻¹ * (P * J * P) * S
143+
P = Iₙ .* z'
144+
S = Iₙ .* s'
145+
S⁻¹ = Iₙ .* inv.(s)'
146+
A = Iₙ / h - S⁻¹ * (P * J * P) * S
143147
Δx = S * (A \ (S⁻¹ * f))
144148
return max.(x .+ Δx, 0)
145149
end
@@ -197,7 +201,8 @@ fields as the `Instantaneous` entry (without the activation diagnostic).
197201
x_prev = x
198202
x = if all(isfinite, x)
199203
J = FD.jacobian(g, x)
200-
all(isfinite, J) ? _rosenbrock_update(x, f, J, h) : _euler_update(x, f, h)
204+
z = _rosenbrock_channel_mask(x)
205+
all(isfinite, J) ? _rosenbrock_update(x, f, J, z, h) : _euler_update(x, f, h)
201206
else
202207
_euler_update(x, f, h)
203208
end

0 commit comments

Comments
 (0)