You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Enzyme.gradient over NonlinearSolve.solve works for a NonlinearProblem whose f is a plain closure / anonymous function, but fails with EnzymeMutabilityException as soon as f is a RuntimeGeneratedFunction (RGF) — same call shape, only the function wrapper differs.
This is the second layer of the Enzyme + ModelingToolkit remake stack documented in SciML/ModelingToolkit.jl#4550 / #4551 and SciML/SciMLSensitivity.jl#1323 / #1359. Layer 1 (MTK supports_initialization) is fixed by ModelingToolkit#4551. Layer 2 is reachable independently with this RGF-only MWE — no MTK in the loop, no SciMLSensitivity.
EnzymeMutabilityException: Function argument passed to autodiff cannot be proven readonly.
The potentially writing call is store i8 %"...iprob.f1.i.sroa.0.0.extract.trunc", ptr addrspace(11) %14, ...
Verified on Julia 1.12.6, Enzyme 0.13.148, RuntimeGeneratedFunctions 0.5.19, NonlinearSolve 4.19.1, SciMLBase 3.14.0.
What I ruled out
Not an RGF body issue alone. Plain Enzyme.gradient(Reverse, p -> rgf(p[1]), [3.0]) works fine — RuntimeGeneratedFunction is differentiable in isolation. The failure mode is specifically RGF inside a NonlinearProblem under solve.
Not opaque_closures = true. Reproducing with ModelingToolkit using eval_expression = true (so f is a plain var"#2#3" anonymous Julia function wrapped in GeneratedFunctionWrapper, no RGF) fails identically. The trigger is the wrapped-function pattern combined with solve, not RGF specifically.
Enzyme.Const(loss) advances past this point but then trips EnzymeRuntimeActivityError with "Mismatched activity for Unknown object of type System" inside SciMLBase's remake path — a separate failure layer.
Reduction further
The failing IR variable is iprob.f1.i.sroa.0.0, which SROAs through iprob.f (NonlinearFunction) into a sub-field of the wrapped function. The "store i8" is a Union-tag byte. The mutation is reported inside NonlinearProblem's mutable-struct constructor at SciMLBase/src/problems/nonlinear_problems.jl:185 — but it surfaces only when the function passed in is a wrapped function (RGF or otherwise), not when it's a plain closure.
I have not been able to reduce this to a pure Enzyme + plain Julia case yet — every wrapped-function reduction I tried still routes through NonlinearSolve. So the suspected interaction is between Enzyme's IPA on NonlinearProblem (mutable struct) and the way NonlinearSolve.solve dispatches on wrapped-function fields.
What this unblocks if fixed
Enzyme.gradient(Reverse, loss, tunables) through remake(prob; p = repack(tunables)) + solve(...) on any MTK-generated problem. Currently @test_broken in SciML/SciMLSensitivity.jl's test/mtk.jl, test/parameter_initialization.jl, test/desauty_dae_mwe.jl. Once this lands, ModelingToolkit#4551 (Layer 1) plus this fix flip those three blocks from @test_broken to passing.
Environment
Julia 1.12.6
Enzyme 0.13.148
NonlinearSolve 4.19.1
RuntimeGeneratedFunctions 0.5.19
SciMLBase 3.14.0
FunctionWrappersWrappers 1.9.0
ModelingToolkit 11.26.5 (not required for the MWE, just for the user scenario)
Summary
Enzyme.gradientoverNonlinearSolve.solveworks for aNonlinearProblemwhosefis a plain closure / anonymous function, but fails withEnzymeMutabilityExceptionas soon asfis aRuntimeGeneratedFunction(RGF) — same call shape, only the function wrapper differs.This is the second layer of the Enzyme + ModelingToolkit
remakestack documented in SciML/ModelingToolkit.jl#4550 / #4551 and SciML/SciMLSensitivity.jl#1323 / #1359. Layer 1 (MTKsupports_initialization) is fixed by ModelingToolkit#4551. Layer 2 is reachable independently with this RGF-only MWE — no MTK in the loop, no SciMLSensitivity.MWE (no MTK)
Verified on Julia 1.12.6, Enzyme 0.13.148, RuntimeGeneratedFunctions 0.5.19, NonlinearSolve 4.19.1, SciMLBase 3.14.0.
What I ruled out
Not an RGF body issue alone. Plain
Enzyme.gradient(Reverse, p -> rgf(p[1]), [3.0])works fine —RuntimeGeneratedFunctionis differentiable in isolation. The failure mode is specifically RGF inside a NonlinearProblem under solve.Not
opaque_closures = true. Reproducing with ModelingToolkit usingeval_expression = true(sofis a plainvar"#2#3"anonymous Julia function wrapped inGeneratedFunctionWrapper, no RGF) fails identically. The trigger is the wrapped-function pattern combined withsolve, not RGF specifically.Not
FunctionWrappersWrappers.SingleCacheStorage. DeclaringEnzymeRules.inactive_type(::Type{SingleCacheStorage}) = true(filed as a defensive PR at Declare cache-storage types as Enzyme inactive SciML/FunctionWrappersWrappers.jl#51) does not change the outcome.set_runtime_activity(Reverse)does not help.Enzyme.Const(loss)advances past this point but then tripsEnzymeRuntimeActivityErrorwith "Mismatched activity forUnknown object of type System" inside SciMLBase'sremakepath — a separate failure layer.Reduction further
The failing IR variable is
iprob.f1.i.sroa.0.0, which SROAs throughiprob.f(NonlinearFunction) into a sub-field of the wrapped function. The "store i8" is a Union-tag byte. The mutation is reported insideNonlinearProblem's mutable-struct constructor atSciMLBase/src/problems/nonlinear_problems.jl:185— but it surfaces only when the function passed in is a wrapped function (RGF or otherwise), not when it's a plain closure.I have not been able to reduce this to a pure Enzyme + plain Julia case yet — every wrapped-function reduction I tried still routes through NonlinearSolve. So the suspected interaction is between Enzyme's IPA on
NonlinearProblem(mutable struct) and the wayNonlinearSolve.solvedispatches on wrapped-function fields.What this unblocks if fixed
Enzyme.gradient(Reverse, loss, tunables)throughremake(prob; p = repack(tunables)) + solve(...)on any MTK-generated problem. Currently@test_brokenin SciML/SciMLSensitivity.jl'stest/mtk.jl,test/parameter_initialization.jl,test/desauty_dae_mwe.jl. Once this lands, ModelingToolkit#4551 (Layer 1) plus this fix flip those three blocks from@test_brokento passing.Environment