Skip to content

Unwrap FunctionWrappersWrapper in DAE init closures#3066

Merged
ChrisRackauckas merged 1 commit into
SciML:masterfrom
ChrisRackauckas-Claude:fix-dae-init-functionwrapper
Feb 22, 2026
Merged

Unwrap FunctionWrappersWrapper in DAE init closures#3066
ChrisRackauckas merged 1 commit into
SciML:masterfrom
ChrisRackauckas-Claude:fix-dae-init-functionwrapper

Conversation

@ChrisRackauckas-Claude

Copy link
Copy Markdown
Contributor

Summary

  • Unwrap FunctionWrappersWrapper from f at the top of each ODEProblem _initialize_dae! method via SciMLBase.unwrapped_f(f)

Problem

DiffEqBase #1284 (v6.208.0) enabled AutoSpecialize for mass matrix ODEProblems by removing the f.mass_matrix isa UniformScaling guard from promote_f. This wraps f.f in a FunctionWrappersWrapper with 4 pre-compiled variants coordinated with the ODE solver's ForwardDiff (tag = OrdinaryDiffEqTag, chunk size = 1).

However, _initialize_dae! (both BrownFullBasicInit and ShampineCollocationInit) passes f into closures that NonlinearSolve differentiates through with its own ForwardDiff. NonlinearSolve's duals have a different tag and chunk size, so none of the 4 pre-compiled variants match, causing NoFunctionWrapperFoundError.

This breaks the OrdinaryDiffEqRosenbrock DAE AD tests (dae_rosenbrock_ad_tests.jl) on all Julia versions.

Fix

Call SciMLBase.unwrapped_f(f) after destructuring (; p, t, f) = integrator in the 4 ODEProblem methods of _initialize_dae!. This replaces the FunctionWrappersWrapper with the raw function for DAE initialization only. The solver's hot path in perform_step! still uses the wrapped version from integrator.f.

This is a no-op when AutoSpecialize is not active (i.e., when f.f is not a FunctionWrappersWrapper).

Test plan

  • OrdinaryDiffEqRosenbrock tests pass (specifically dae_rosenbrock_ad_tests.jl with AutoForwardDiff)
  • No regression in other subpackage tests

🤖 Generated with Claude Code

DiffEqBase SciML#1284 enabled AutoSpecialize (FunctionWrappers wrapping) for
mass matrix ODEProblems. The pre-compiled FunctionWrapper variants use
OrdinaryDiffEqTag with chunk size 1, coordinated with the ODE solver's
ForwardDiff via prepare_ADType. However, _initialize_dae! passes f into
NonlinearSolve closures where NonlinearSolve's internal ForwardDiff uses
its own tag and chunk size, causing NoFunctionWrapperFoundError.

Fix by unwrapping f at the top of each ODEProblem _initialize_dae! method.
This is a no-op when AutoSpecialize is not active. The solver's hot path
in perform_step! still uses the wrapped version from integrator.f.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ChrisRackauckas ChrisRackauckas merged commit 9d3072b into SciML:master Feb 22, 2026
256 of 275 checks passed
@ChrisRackauckas-Claude

Copy link
Copy Markdown
Contributor Author

Update: Added forwarddiff_chunksize trait implementation

The original PR fixed the DAE initialization issue (NonlinearSolve tag mismatch). This update adds the second piece of the fix: implementing the forwarddiff_chunksize trait to resolve the FunctionWrapper chunk size mismatch.

New commit: Implement forwarddiff_chunksize trait

Changes:

  • lib/OrdinaryDiffEqCore/src/alg_utils.jl: Implement SciMLBase.forwarddiff_chunksize for all algorithm types that use ForwardDiff (implicit, DAE, exponential, composite)
  • lib/OrdinaryDiffEqCore/Project.toml: Bump SciMLBase compat to 2.143, DiffEqBase compat to 6.209
  • Project.toml: Bump SciMLBase and DiffEqBase compat
  • lib/OrdinaryDiffEqRosenbrock/test/dae_rosenbrock_ad_tests.jl: Fix @test_broken → plain assignment (now inferable)

How it works

  1. SciMLBase (Add forwarddiff_chunksize algorithm trait SciMLBase.jl#1244): New forwarddiff_chunksize(alg) trait (defaults to 0)
  2. DiffEqBase (Thread ForwardDiff chunk size through FunctionWrapper compilation DiffEqBase.jl#1287): Threads chunk size through promote_fwrapfun_iip, compiles FunctionWrapper variants with chunk=CS for Jacobian and chunk=1 for time derivative
  3. OrdinaryDiffEq (this PR): Implements the trait, returning the algorithm's ForwardDiff chunk size

Test results

All tests pass locally:

  • DAE Rosenbrock AD Tests: 8/8 pass
  • Rosenbrock Convergence Tests: 853/853 pass

ChrisRackauckas-Claude pushed a commit to ChrisRackauckas-Claude/OrdinaryDiffEq.jl that referenced this pull request Feb 22, 2026
Instead of using bare `AutoForwardDiff()` in `default_nlsolve`, pass
`AutoForwardDiff{1}(ForwardDiff.Tag(OrdinaryDiffEqTag(), eltype(u)))` so
NonlinearSolve's internal ForwardDiff uses a tag matching the pre-compiled
FunctionWrapper variants. This addresses the type-instability introduced by
unwrapping in SciML#3066.

Also fixes 3 OOP `default_nlsolve` call sites that had swapped arguments
and missing `isAD` parameter, and removes `unwrapped_f` from OOP methods
where it is not needed.

Closes SciML#3067

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
@ChrisRackauckas-Claude

Copy link
Copy Markdown
Contributor Author

Pushed two additional commits:

  1. e0b3bbb - Implement forwarddiff_chunksize trait for all OrdinaryDiffEq algorithm types (was committed locally but not pushed previously)
  2. d4e7dc5 - Simplify to use existing _get_fwd_chunksize(AD) which already returns Val{CS}(), matching the updated trait contract from SciMLBase#1244. Consolidates three methods into one Union dispatch, consistent with the existing get_chunksize pattern.

Depends on SciMLBase#1244 and DiffEqBase#1287.

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