MethodError: no method matching MixedDuplicated(::T, ::T) — runtime_generic_augfwd / create_activity_wrapper passes plain shadow where Base.RefValue{T} is required
## Description
`Enzyme.gradient(set_runtime_activity(Reverse), Const(loss), tunables)` throws a `MethodError: no method matching MixedDuplicated(::T, ::T)` when the loss returns (and is evaluated through) a value whose type has `FunctionWrappersWrappers.FunctionWrappersWrapper` deep in its type parameters — e.g. an `ODESolution` whose `ODEFunction` carries a `FunctionWrappersWrapper` (the default for `@mtkbuild`/MTK-built systems).
The root site is `Enzyme.Compiler.create_activity_wrapper` (`src/rules/jitrules.jl:14`):
```julia
elseif aref == MixedState
if Width == 1
return quote
Base.@_inline_meta
MixedDuplicated(primarg, shadowarg) # <-- here
end
```
This emits `MixedDuplicated(primarg, shadowarg)` where `shadowarg` is the same shape as `primarg`. But `EnzymeCore.MixedDuplicated`'s only constructor requires `dx::Base.RefValue{T}`:
```julia
# EnzymeCore.jl
struct MixedDuplicated{T} <: Annotation{T}
...
@inline MixedDuplicated(x::T1, dx::Base.RefValue{T1}, check::Bool=true) where {T1} = new{T1}(x, dx)
end
```
So the same `(primarg, shadowarg)` pair that works for the `Duplicated` branch a few lines below blows up in the `MixedState` branch. The `runtime_generic_augfwd` path needs to box `shadowarg` into a `Ref{T}` (or take a separate path) before calling `MixedDuplicated`.
This is the underlying wall that keeps surfacing through SciML's MTK-Enzyme stack — every workaround we apply downstream eventually trips into the same constructor.
## MWE (SciML stack)
I tried for ~30 min to reduce this to a `FunctionWrappersWrappers`-only MWE without success — the trigger seems to require both (a) a value with FWW in deep type params *and* (b) Enzyme's MixedActivity classification, which fires off the SciML solve dispatch chain. Pure `FunctionWrappersWrapper` + a simple struct does not promote anything to `MixedDuplicated`, so the bad call site is never hit. Filing with the smallest SciML-stack reproducer I have:
```julia
using ModelingToolkit, OrdinaryDiffEq, Enzyme
using ModelingToolkit: t_nounits as t, D_nounits as D
using SciMLSensitivity
import SciMLStructures as SS
using SymbolicIndexingInterface
@parameters a b
@variables x(t) y(t)
eqs = [
D(x) ~ a * x + y,
0 ~ x^2 - b * y, # algebraic constraint -> ODESolution carries FWW deep in type params
]
@mtkbuild sys = ODESystem(eqs, t)
prob = ODEProblem(sys, [x => 1.0], (0.0, 1.0), [a => -0.5, b => 2.0],
guesses = [y => 1.0])
tunables, repack, _ = SS.canonicalize(SS.Tunable(), parameter_values(prob))
# Forward solve is fine
sol = solve(prob, Rodas5P())
loss = let prob = prob, repack = repack
p -> begin
new_prob = remake(prob; p = repack(p))
sol = solve(new_prob, Rodas5P(); abstol = 1e-8, reltol = 1e-6)
sum(sol)
end
end
grad = Enzyme.gradient(set_runtime_activity(Enzyme.Reverse),
Enzyme.Const(loss), tunables)
```
## Error
```
MethodError: no method matching MixedDuplicated(::ODESolution{Float64, 2, Vector{Vector{Float64}}, Nothing, Nothing, Vector{Float64}, Vector{Vector{Vector{Float64}}}, Nothing,
ODEProblem{Vector{Float64}, Tuple{Float64, Float64}, true,
MTKParameters{Vector{Float64}, Vector{Float64}, Tuple{}, Tuple{}, Tuple{}, Tuple{}},
ODEFunction{true, SciMLBase.AutoSpecialize,
FunctionWrappersWrappers.FunctionWrappersWrapper{
Tuple{
FunctionWrappers.FunctionWrapper{Nothing, Tuple{Vector{Float64}, Vector{Float64}, MTKParameters{...}, Float64}},
FunctionWrappers.FunctionWrapper{Nothing, Tuple{Vector{ForwardDiff.Dual{...}}, Vector{ForwardDiff.Dual{...}}, MTKParameters{...}, Float64}},
FunctionWrappers.FunctionWrapper{Nothing, Tuple{Vector{ForwardDiff.Dual{...}}, Vector{Float64}, MTKParameters{...}, ForwardDiff.Dual{...}}},
FunctionWrappers.FunctionWrapper{Nothing, Tuple{Vector{ForwardDiff.Dual{...}}, Vector{ForwardDiff.Dual{...}}, MTKParameters{...}, ForwardDiff.Dual{...}}}
},
FunctionWrappersWrappers.AllowNonIsBits,
FunctionWrappersWrappers.SingleCacheStorage
},
...
},
...
},
Rodas5P{...}, OrdinaryDiffEqCore.InterpolationData{...}, ...
}, ::ODESolution{Float64, 2, ...}) # <-- shadow arg passed as same type, NOT Base.RefValue{...}
Closest candidates are:
MixedDuplicated(::T1, ::Base.RefValue{T1}) where T1
MixedDuplicated(::T1, ::Base.RefValue{T1}, ::Bool) where T1
```
Both `primarg` and `shadowarg` are passed as `::ODESolution{...}`, but the constructor needs `(::T, ::Base.RefValue{T})`.
## Expected behavior
Either:
- `create_activity_wrapper` boxes `shadowarg` into a `Ref` before constructing `MixedDuplicated` (the simple fix), or
- a correct gradient gets returned (matches `FiniteDifferences`).
## Versions
- Julia 1.12.4
- Enzyme.jl v0.13.150
- EnzymeCore v0.8.x
- FunctionWrappersWrappers.jl v1.9.1
- ModelingToolkit / SciMLBase / OrdinaryDiffEq / SciMLSensitivity (current master)
## Related
- This is the same dispatch site as #2994 (`Fix1` throws `MethodError on Duplicated(::Base.Fix1, ::Base.RefValue{...})`), but symmetric: there the *runtime* call was emitting a `Ref` where a plain value was expected (`Duplicated` branch); here the *generated wrapper* emits a plain value where a `Ref` is expected (`MixedDuplicated` branch). They look like two sides of the same Ref-vs-plain-shadow confusion in `runtime_generic_augfwd`.
- Possibly related: #3021 (`MixedReturnException` in `runtime_generic_augfwd` with `NonlinearSolvePolyAlgorithm`), #2712 (higher-order: handle mixed duplicated on llvm side), #2677 (`test_reverse` with `MixedDuplicated`).
## Downstream context (SciML)
- Originally reported in SciML/SciMLSensitivity.jl#1359 (MTK DAE + GaussAdjoint/EnzymeVJP).
- Workaround tracking: SciML/SciMLSensitivity.jl#1455, SciML/SciMLSensitivity.jl#1456 (re-routing tests through `Enzyme.autodiff` with `Duplicated` instead of `set_runtime_activity` + `Const`).
- `NonlinearSolve` had the same wall and worked around it by adding `store_original::Val{false}` to the polyalg so the returned `NonlinearSolution`'s `original` field no longer carries the `@generated`-heavy union of sub-algorithm caches (SciML/NonlinearSolve.jl#938). The equivalent surgery for `ODESolution` (strip / box the type-parameter-heavy components of the interpolant) is what the SciML-side fix would look like, but the underlying Enzyme constructor mismatch is what we'd really like to resolve.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
6 条评论