Skip to content

Re-apply: Widen UJacobianWrapper.p for nested ForwardDiff through Rosenbrock (#3381)#3587

Open
ChrisRackauckas-Claude wants to merge 4 commits into
SciML:masterfrom
ChrisRackauckas-Claude:fix-nested-forwarddiff-tag-in-wrapfun-v2
Open

Re-apply: Widen UJacobianWrapper.p for nested ForwardDiff through Rosenbrock (#3381)#3587
ChrisRackauckas-Claude wants to merge 4 commits into
SciML:masterfrom
ChrisRackauckas-Claude:fix-nested-forwarddiff-tag-in-wrapfun-v2

Conversation

@ChrisRackauckas-Claude
Copy link
Copy Markdown
Contributor

Reopens the fix from #3414, which was reverted by #3586. Rebased onto current master.

Why now

Per @ChrisRackauckas's comment on #3381 (2026-04-30), this is the intended fix for the tag-ordering issue (UJacobianWrapper.p carrying the outer NonlinearSolveTag Duals while the inner Rosenbrock FD needs to wrap them again). #3488 has since merged and fixes a different nested-FD path (JacReuseState dtgamma storage, #3486) — the two changes touch disjoint files and coexist cleanly.

Diff vs. master

Same as the original #3414 — 4 files, 157 insertions / 11 deletions. The revert in #3586 only touched these 4 files; reverting that revert produced no conflicts against #3488's changes (derivative_utils.jl, rosenbrock_caches.jl).

  • lib/DiffEqBase/ext/DiffEqBaseForwardDiffExt.jl
  • lib/OrdinaryDiffEqDifferentiation/src/derivative_wrappers.jl
  • lib/OrdinaryDiffEqDifferentiation/test/nested_forwarddiff_tests.jl (new)
  • lib/OrdinaryDiffEqDifferentiation/test/runtests.jl

Test plan

🤖 Generated with Claude Code

…ciML#3381)

Re-applies the changes from PR SciML#3414 on top of current master. The
original PR was reverted by SciML#3586; this restores it now that SciML#3488
(JacReuseState dtgamma fix) has landed and the two changes coexist
without conflict (they touch disjoint files).

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Move the nested-Dual `p` widening from per-`jacobian!`-call to once at
cache build time. The inner Dual eltype is fixed once `JacobianConfig`
is built, so the widened `uf` can be cached in the Rosenbrock cache
struct directly. `jacobian!` still calls `_widen_uf_p_for_jac`, but the
fast-path (`Tp === inner_T && return f`) returns the cached `uf`
unchanged with zero allocations.

Measured on the issue SciML#3381 nested-Dual MWE:
- Before: 240 bytes/jacobian! call (one Vector{nested_dual} convert +
  one UJacobianWrapper struct rebuild via `@set f.p`).
- After:  0 bytes/jacobian! call.

Non-nested case is unchanged (already 0 bytes/call via the
`Tp <: ForwardDiff.Dual || return f` fast-path).

Patches all four IIP cache builders in `rosenbrock_caches.jl`:
Rosenbrock23, Rosenbrock32, RodasTableauAlgorithms,
HybridExplicitImplicitRK. Other algorithms (BDF, SDIRK, FIRK, …) keep
the per-call widen for now; the same pattern can be lifted into their
caches as a follow-up.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Asserts the cache-time pre-widen invariant from the previous commit:
- `eltype(integ.cache.uf.p)` must equal the inner nested-Dual eltype.
- `_widen_uf_p_for_jac` on the cached uf must allocate zero bytes
  (fast-path returns `f` unchanged).

Catches regressions in either direction: a missing pre-widen line in a
cache builder fails the eltype check; a broken fast-path in the helper
fails the @allocated check.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
@ChrisRackauckas-Claude ChrisRackauckas-Claude force-pushed the fix-nested-forwarddiff-tag-in-wrapfun-v2 branch from 67d66cb to 89bb3f7 Compare May 21, 2026 01:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants