Skip to content

Declare AbstractSystem as Enzyme inactive_type#4553

Merged
ChrisRackauckas merged 1 commit into
SciML:masterfrom
ChrisRackauckas-Claude:cc/system-inactive-type
May 23, 2026
Merged

Declare AbstractSystem as Enzyme inactive_type#4553
ChrisRackauckas merged 1 commit into
SciML:masterfrom
ChrisRackauckas-Claude:cc/system-inactive-type

Conversation

@ChrisRackauckas-Claude
Copy link
Copy Markdown

Please ignore until reviewed by @ChrisRackauckas.

Summary

AbstractSystem is symbolic metadata — never carries numerical derivative data. The existing inactive_noinl rule on getproperty(::AbstractSystem, ::Symbol) keeps Enzyme from trying to differentiate sys.x property accesses, but does not keep Enzyme's runtime-activity dispatch from trying to track the System itself.

When a user follows the documented Enzyme idioms — Const(loss) for a closure capturing a Const-correct mutable problem, plus set_runtime_activity(Reverse) for activity inference — and the closure transitively carries a System (which is the case for every MTK-generated NonlinearFunction / ODEFunction via the ObservedFunctionCache{System,...} field), Enzyme's runtime-activity wrapping trips:

MethodError: no method matching MixedDuplicated(::System, ::System)
  Closest candidates: MixedDuplicated(::T1, ::Base.RefValue{T1}) ...

MixedDuplicated only accepts a RefValue-boxed shadow, so Enzyme's machinery is asking for a shadow it can't produce. The right answer is to tell Enzyme to never wrap System in the first place — that's what inactive_type(::Type{<:AbstractSystem}) = true does.

Effect

This is necessary but not sufficient to flip the three @test_broken Enzyme.gradient blocks in SciML/SciMLSensitivity.jl's test/mtk.jl, test/parameter_initialization.jl, test/desauty_dae_mwe.jl. With this PR applied locally on Julia 1.12.6 (Enzyme 0.13.148), Enzyme.gradient(set_runtime_activity(Reverse), Const(loss), tunables) advances past the MixedDuplicated(::System, ::System) error and onto the next upstream layer (TypeError: in new, expected DataType, got Type{Core.SimpleVector}). The remaining chain is tracked in SciML/SciMLSensitivity.jl#1359.

Refs

Test plan

  • Locally on Julia 1.12.6: Enzyme.gradient(set_runtime_activity(Reverse), Const(loss), tunables) on parameter_initialization's "Adjoint through Prob (Enzyme)" closure advances past the MixedDuplicated(::System, ::System) error after this declaration is added
  • CI green

The existing `inactive_noinl` rule on `getproperty(::AbstractSystem, ::Symbol)`
is not enough to keep Enzyme's runtime-activity dispatch from trying to track
a `System` as a differentiable value. Without `inactive_type`,
`Enzyme.gradient(set_runtime_activity(Reverse), Const(loss), p)` over a
closure that transitively captures an MTK-generated problem trips a
`MethodError: no method matching MixedDuplicated(::System, ::System)` in
`create_activity_wrapper`.

The `System` type holds symbolic metadata only — never numeric derivative
data — so declaring the type inactive is semantically correct and unblocks
one more layer of the Enzyme-through-MTK-`remake` stack documented in
SciML/SciMLSensitivity.jl#1359.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
@wsmoses
Copy link
Copy Markdown

wsmoses commented May 21, 2026

@oscardssmith this seems sus to me

@ChrisRackauckas
Copy link
Copy Markdown
Member

Why would this be sus? Symbolic spaces are discrete and not differentiable.

@wsmoses
Copy link
Copy Markdown

wsmoses commented May 22, 2026

can you guarantee that all types which subclass AbstractSystem cannot contain a vector{float64} anywhere (including via a closed over function)?

@ChrisRackauckas
Copy link
Copy Markdown
Member

Even better, by design any float in there has to be treated as a constant by definition.

@ChrisRackauckas ChrisRackauckas marked this pull request as ready for review May 23, 2026 09:10
@ChrisRackauckas ChrisRackauckas merged commit 1ddc132 into SciML:master May 23, 2026
47 of 79 checks passed
ChrisRackauckas added a commit to SciML/SciMLSensitivity.jl that referenced this pull request May 23, 2026
…tivity

The three `@test_broken Enzyme.gradient` blocks (`mtk.jl` line 168,
`parameter_initialization.jl` "Adjoint through Prob (Enzyme)",
`desauty_dae_mwe.jl` "Enzyme through init") previously called
`Enzyme.gradient(Enzyme.Reverse, loss, tunables)` — i.e. relied on
`Enzyme.gradient` to infer annotations on a closure that captures a
mutable `ODEProblem`/`NonlinearProblem`. That triggers
`EnzymeMutabilityException` on a captured-mutable, which per Billy Moses
(EnzymeAD/Enzyme.jl#3117 close) is correct Enzyme behavior, not a bug:
`Enzyme.gradient` can't annotate captures, so the user must do it.

Apply the documented user-side pattern in all three blocks:

  Enzyme.gradient(
      Enzyme.set_runtime_activity(Enzyme.Reverse),
      Enzyme.Const(loss),
      tunables,
  )

Plus, for `desauty_dae_mwe.jl`, pin `solve(iprob2, NewtonRaphson())` so
Enzyme's type analysis does not trip on the polyalgorithm Union the
default NonlinearSolve dispatch would otherwise emit. (`mtk.jl` already
uses `Rodas5P()`; `parameter_initialization.jl` uses an `ODEProblem`
solve with `GaussAdjoint` sensealg, no NonlinearSolve polyalg in the AD
hot path.)

These tests **stay `@test_broken`** — the activity layer is correct
after this change, but the chain still hits a `MixedDuplicated` /
`Core.SimpleVector` MethodError further down in Enzyme's
runtime-activity wrapping for MTK-`System` / `NonlinearSolution` types.
Tracked in #1359, with one upstream piece
already filed at SciML/ModelingToolkit.jl#4553 (declare `AbstractSystem`
as `inactive_type`). When the remaining upstream lifts, flipping
`@test_broken` → `@test` is the only change needed in these blocks.

Refs #1323, #1359; EnzymeAD/Enzyme.jl#3117.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
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.

3 participants