Fix Float32/static-array mass-matrix OOP Rosenbrock JacReuseState W typing#3732
Conversation
…yping Fixes SciML#3721. Two failure modes in the OOP Jacobian-reuse path for static-array mass-matrix problems: 1. JacReuseState's cached_W slot was seeded with build_J_W's W, whose eltype follows the state (e.g. Float32), but calc_W promotes the W eltype with typeof(dtgamma) (e.g. Float64 when dt promotes the time type). The fresh-Jacobian branch's `jac_reuse.cached_W = W` then throws, since StaticWOperator has no eltype-converting convert. 2. The cached-J reuse branch rebuilt W via default_factorize, producing a StaticArrays.LU instead of the StaticWOperator the slot holds, so any OOP static mass-matrix solve with a rejected step threw even in homogeneous Float64. Fix: add build_jac_reuse_W_seed, which builds the cached_W seed with the same concrete type calc_W produces at solve time, seed the OOP alg_caches with it and with zero(dt * gamma) for the dtgamma fields, and make the reuse branch mirror calc_W's StaticWOperator branch. OrdinaryDiffEqDifferentiation 3.2.0 -> 3.2.1, OrdinaryDiffEqRosenbrock 2.3.0 -> 2.3.1 (requires the new function, so the compat lower bound is raised accordingly). Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CI triage (none of the failures trace to this PR's changes)Jobs covering the changed libraries:
Failures unrelated to this PR (pre-existing on master):
Local verification (full output in my run logs): 🤖 Generated with Claude Code |
|
CI complete (4 checks still queued, all in untouched libraries/downstream). Final tally for the two modified libraries — every job that ran tests passed them, including both downgrade jobs:
A re-run of the failed jobs (needs admin) should clear everything except the known pre-existing failures triaged above. 🤖 Generated with Claude Code |
Per review: fix the W typing at the source instead of patching the JacReuseState seed afterwards. build_J_W now computes a dtgamma prototype (dt's full possibly-Dual time type promoted with the constvalue-stripped tableau eltype) and constructs every non-DAE W with the type calc_W produces at solve time: - OOP static: StaticWOperator(J - mass_matrix * inv(oneunit(dtgamma))) - OOP dense: lu_instance(J - mass_matrix * inv(oneunit(dtgamma))) - WOperator: gamma slot seeded with the dtgamma prototype instead of promote(t, dt)[2] / raw dt This removes the build_jac_reuse_W_seed helper; the OOP Rosenbrock alg_caches pass build_J_W's W straight through again. It also fixes the same latent seed/solve-time W type mismatch for Newton-based OOP caches that store build_J_W's W and later assign calc_W results. DAE branches are unchanged: their W is not built from J - M/(dt*gamma). Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Reworked per review (98bf3cb): the W typing is now fixed at the source in
The Re-verified locally:
🤖 Generated with Claude Code |
Note
This PR should be ignored until reviewed by @ChrisRackauckas.
Fixes #3721.
Problem
A Float32 static-array mass-matrix problem (e.g. DiffEqGPU's CPU-reference ROBER solve) solved with
Rosenbrock23()anddt = 0.1(a Float64, which promotes the integrator's time type to Float64) fails withTwo distinct failure modes in the OOP Jacobian-reuse path:
Seed/solve type mismatch (the reported error).
JacReuseState.cached_Wis seeded inalg_cachewithbuild_J_W's W, whose eltype follows the state (Float32). At solve timecalc_WbuildsStaticWOperator(J - mass_matrix * inv(dtgamma)), whose eltype is promoted bytypeof(dtgamma)(Float64). The fresh-Jacobian branch'sjac_reuse.cached_W = Wthen throws, becauseStaticWOperatorhas no eltype-convertingconvert. (The dense path silently survives the same mismatch only becauseLinearAlgebradefinesconvertbetweenLUeltypes.)Reuse branch built the wrong wrapper for static caches. The cached-J reuse branch in
calc_rosenbrock_differentiationrebuilt W viaDiffEqBase.default_factorize, producing aStaticArrays.LU— not theStaticWOperatorthecached_Wslot holds. Any OOP static-array mass-matrix solve with at least one rejected step (the mass-matrix decision returns(false, true)on rejection retries) hits this and throws, even in homogeneous Float64. Existing tests passed only because their runs happened to have zero rejections.Fix
build_jac_reuse_W_seed(W, J, u, mass_matrix, dtgamma)in OrdinaryDiffEqDifferentiation, which builds thecached_Wseed with the same concrete typecalc_Wproduces at solve time (mirroring itsStaticWOperator/WOperator/AbstractSciMLOperator/ factorized-dense branches), given adtgammaprototype carrying the solve-timedt * gammatype.JacReuseStatein the three OOPalg_caches (Rosenbrock23, Rosenbrock32, RodasTableauAlgorithms) with that seed and withzero(dt * tab.d)/zero(dt * tab.gamma)for the dtgamma fields (previouslyzero(dt)), so the field types match solve-timedtgammaexactly. Nested-Dual behavior (Second order differentiation ofsolvewith ForwardDiff errors due to incorrect types inJacReuseState#3486) is preserved: the product keepsdt's full Dual type.calc_rosenbrock_differentiationmirrorcalc_W's branches so it builds aStaticWOperatorfor static caches.Verification
Ran locally (all previously failing, now passing):
Rosenbrock23()+dt = 0.1→Success, endpoint matches Float64 reference to <1e-4, with 10 step rejections also exercising the static reuse branch.Success, matches reference.Rodas4/Rodas23W/ROS34PW2on the Float32 static problem (previously the same MethodError through the RodasTableau OOP cache) →Success.Pkg.testCore groups of OrdinaryDiffEqRosenbrock and OrdinaryDiffEqDifferentiation.Pre-existing issues found while testing, NOT addressed here (all reproduce identically on unmodified master):
CheckInitfor OOP static-array mass-matrix problems broadcasts in-place into anSVector(SciMLBaseinitialization.jl:216); the tests useBrownFullBasicInit()like the DiffEqGPU test does.Rodas5Pon Float32 ROBER hitsDtNaN(precision limitation; identical on the dense path).Versions: OrdinaryDiffEqDifferentiation 3.2.0 → 3.2.1 (new function), OrdinaryDiffEqRosenbrock 2.3.0 → 2.3.1 with compat
OrdinaryDiffEqDifferentiation = "3.2.1"(it now requires the new function — matters for downgrade CI).🤖 Generated with Claude Code