Repository navigation
Route the solution's prob.p shadow back to p in the Enzyme solve_up rule - #4904
Open
SebastianM-C wants to merge 7 commits into
Open
SebastianM-C wants to merge 7 commits into
SebastianM-C wants to merge 7 commits into
Conversation
…_up` rule The returned solution's `prob.p` is the `p` argument (wrapped in `DespecializedParameters` under `AutoDespecialize`), so a loss that reads it, e.g. an observed variable evaluated with `sol[sym]`, leaves its parameter derivative in the shadow's `prob.p`. The ChainRules pullback never reads that field, so the derivative was dropped and such parameters got a zero gradient (SciML/SciMLSensitivity.jl#1714). The reverse rule now adds the tunable part of `dval.prob.p` into the shadow of `p` before calling the pullback, and zeroes what it read so it is consumed exactly once. Parameter types other than numeric arrays and aliasing SciMLStructures keep the previous behavior. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B6CVXLpT5vamupf2iC87KN
Adds the reproducer from SciML/SciMLSensitivity.jl#1714 to the rule's downstream tests: an observed variable `y ~ x + c` read from the solution of an MTK problem (`AutoDespecialize`, `MTKParameters`), with the automatic sensealg and Gauss/Interpolating/Quadrature adjoints (`EnzymeVJP`), for an observed-only and a mixed state + observed loss. Checks against ForwardDiff, the exact ∂L/∂c = 21, and that repeated calls agree. Adds ModelingToolkit to the downstream test environment. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FXAQW4otxDigUtMqrQywZW
Adds ModelingToolkitBase to the downstream test environment and narrows the ModelingToolkit compat to 11, which ModelingToolkitBase 1.60 implies. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FXAQW4otxDigUtMqrQywZW
The test passed `saveat = 0.1` before `solve_kwargs...`, whose `saveat = 0.25` overrode it, so the exact value is 9, not 21. Use the shared `solve_kwargs` and compare against the number of saved points. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FXAQW4otxDigUtMqrQywZW
Member
Author
|
CI on
Generated by Claude Code |
ChrisRackauckas
marked this pull request as ready for review
November 13, 2026 16:43
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FXAQW4otxDigUtMqrQywZW
Both sides appended testsets to enzyme_solve_up_rule.jl; keep both, with master's AutoSpecialize testset ahead of the MTK section. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FXAQW4otxDigUtMqrQywZW
…erge Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FXAQW4otxDigUtMqrQywZW
Member
Author
|
CI on
Generated by Claude Code |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes the Enzyme part of SciML/SciMLSensitivity.jl#1714: under Enzyme, a parameter that enters the loss only through the returned solution (e.g. an observed variable,
sol[sym]/getsym(prob, sym)(sol)) gets a zero gradient.Cause
solve_up's Enzyme rule builds the solution shadow withEnzyme.make_zero(res[1]), so it includes a zeroedprob.p. When the caller evaluates an observed variable, Enzyme accumulates∂y/∂pintodval.prob.p. The rule then handsdvalto the ChainRules pullback, which readsu. It never readsprob.p: SciMLSensitivity'ssolution_parameter_cotangenttreats a solution-shapedΔlike a ChainRules cotangent, whoseprob.pholds parameter values. So that part of the gradient was dropped. UnderAutoDespecialize(MTK's default) the shadow'spis also aDespecializedParameters, not theMTKParametersofp.Change
sol.prob.pis built from thepargument, so the reverse rule now routesdval.prob.pback topitself, before calling the pullback:DespecializedParameters(unwrap_parametersis the identity otherwise);MTKParameters);Const/ non-Duplicatedpand runtime-activity-aliased shadows, mirroring the existing guard.What this means
MTK users. An objective that reads observed variables now gets the gradient of every tunable parameter that appears in them. This holds for observed-only and mixed state + observed objectives, with the automatic sensealg and Gauss/Interpolating/Quadrature adjoints. Checked locally against ForwardDiff with MTK 11.45.5, including:
θthat containsInitial(x)(the way to vary an initial condition throughsetsym_oop);c2 = missing,initialization_eqs = [c2 ~ 2x]) that appears in the observed variable.remakealready runs initialization there, so Enzyme differentiates it directly.Non-tunable parameters (
tunable = false, integer-typed) get no gradient through this path, as everywhere else in the adjoint. ForwardDiff can't differentiate them throughsetsym_oopeither, since their buffer isFloat64-typed. Generated observed functions don't read theinitialsbuffer, so that part of the shadow stays zero.Non-MTK users. If
pis a numeric array (Vector,ComponentArray, …), any derivative of the loss with respect tosol.prob.pnow reachesp. For other parameter types (tuples, NamedTuples, plain structs, arrays of arrays), that part of the gradient is still dropped, as before.Less common cases not covered yet
cachesportion of a SciMLStructure, relevant underdiff_tunables = Val(false)(SCC problems). Planned as a follow-up.canonicalizereturns a copy), and a shadow whose type differs fromp's.sol.discretes.Δton the Enzyme shadow, dropped the same way.NonlinearSolveBase'ssolve_uprule, which has the same pattern.FullSpecializeerrors inremake(EnzymeNoTypeError), and a plain function underAutoDespecializewithEnzymeVJPhitsMethodError: -(::DespecializedParameters).Tests
test/downstream/enzyme_solve_up_rule.jlgets two testsets:p: a loss that readssol.prob.p, with Gauss/Interpolating/Quadrature adjoints (EnzymeVJP). Without the change, the Enzyme gradient is short by the direct term2p.y ~ x + c, read withgetsym(prob, y)(sol)). It coversAutoDespecializewithMTKParameters, the automatic sensealg and Gauss/Interpolating/Quadrature adjoints (EnzymeVJP), and an observed-only and a mixed state + observed loss. Each case is checked against ForwardDiff, for the observed-only loss against the exact∂L/∂c(the number of save points), and for agreement across repeated calls.ModelingToolkit (compat
11) and ModelingToolkitBase (compat1.60.1) are added to the downstream test environment for this.Locally, on Julia 1.12.7 (SciMLBase 3.58.0, ModelingToolkit 11.45.5, ModelingToolkitBase 1.77.5, SciMLSensitivity 7.119.13), the whole file:
∂L/∂c = 0instead of 9, and the plain-array cases are short by2p.In CI (DiffEqBase Downstream, Julia 1.13.2) the file passes, 32/32. A local Julia 1.13.2 run hit an Enzyme
IllegalTypeAnalysisExceptionin SymbolicIndexingInterface's observed-function broadcast, before the rule runs; that is addressed separately in SciML/SymbolicIndexingInterface.jl#197 and SciML/SciMLBase.jl#1652, and did not reproduce in CI.An earlier local MTK check script (issue reproducer, mixed losses, a
Duplicatedproblem, plain-arrayp, all four specializations, each with automatic, Gauss, Interpolating and Quadrature sensealgs): original rule 16 pass, 46 fail, 6 error; with this PR 62 pass, 0 fail, 6 error. The 6 errors are the two unrelated ones listed above. The SciMLSensitivity Enzyme tests (Core8 is not part of this repo's downstream CI) are still running locally with and without this change; I'll post the comparison.🤖 Generated with Claude Code
https://claude.ai/code/session_01B6CVXLpT5vamupf2iC87KN
https://claude.ai/code/session_01FXAQW4otxDigUtMqrQywZW
Checklist
contributor guidelines, in particular the SciML Style Guide and
COLPRAC.
Additional context
Add any other context about the problem here.