Repository navigation
Unify array-equation support behind a capability trait; fold in #5101 ODE support and MultiObjectiveOptimizationFunction - #5148
Merged
ChrisRackauckas merged 18 commits intoSep 14, 2026
Conversation
Import unwrap explicitly in the new NL tests and raise the DiffEqBase floor so nested AutoDespecialize accepts already-unwrapped parameters.
Stacked on SciML#5049: array *equations* are accepted by the implicit-DAE and nonlinear paths, but the opt-in is a hardcoded `!implicit_dae && !(constructor <: NonlinearFunction)` test in `__process_SciMLProblem` — a codegen flag and a constructor-type check doing capability duty. This treats accepting array equations as a named, per-constructor capability. - `accepts_array_equations(constructor)` gates `__process_SciMLProblem`. `DAEFunction` and `NonlinearFunction` opt in, matching SciML#5049's behavior exactly (`HomotopyProblem` inherits nonlinear support through the same constructor). Every other problem type still needs `mtkcompile`, and opting a new constructor in is a one-line method. - `has_array_equations(eqs)` gets a docstring; it is the shared, shape-based detection predicate behind `check_array_equations` and the Jacobian guards. - `DAEFunction` throws the same `ArgumentError` for `jac = true` / `sparse = true` on array residuals that `NonlinearFunction` gets: today `jac = true` dies inside `executediff` and `sparse = true` silently builds a wrong-shaped `jac_prototype` (one row per equation instead of one per residual row). Related: SciML#4983, SciML#5049, SciML#5131, SciML#5136. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> Agent-Harness: Devin CLI 3000.10.21 Agent-Model: SWE-2 Max Agent-Session: local Devin CLI session, no shareable URL (workspace /home/crackauc/sandbox/tmp_20260913_101544_55869)
ChrisRackauckas-Claude
force-pushed
the
unify-array-equation-capability
branch
from
September 13, 2026 21:20
7fa965b to
ca570b3
Compare
…ile` `complete(sys)` followed by `ODEProblem` rejected array equations such as `D(u[2:n-1]) ~ lap(u)`, forcing the finite-difference use case through the scalarizing `mtkcompile`, whose output and compile time grow with the grid. `DAEProblem` already accepts them, so give the explicit ODE path the same treatment: the right-hand side of an array equation is written to a contiguous block of `du` through `array_residual_maker`, the mass matrix gets one row per element, and the residual form `D(x) .- f ~ 0` emitted by MethodOfLines is rewritten to `D(x) ~ f` first. The generated code is independent of the array length. `full_equations` expands array equations into scalar rows so that the symbolic jacobian, sparsity and initialization paths keep one equation per row, and `ODEFunction` only computes the W sparsity pattern when `sparse` or `sparsity` asks for it. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Aayush Sabharwal <aayush.sabharwal@gmail.com>
SciML#2597 test Co-authored-by: Cursor <cursoragent@cursor.com>
…lements Co-authored-by: Cursor <cursoragent@cursor.com>
…rownFullBasicInit The downgrade CI environment (DiffEqBase 7.15) trips a despecialization wrapper assertion inside BrownFullBasicInit for DAEFunctions, which aborted the InterfaceII group before the new ODEProblem tests could run. The tests only need a solve to compare against the analytic solution, so hand the solver a consistent du0 and skip DAE initialization. Co-authored-by: Cursor <cursoragent@cursor.com>
`is_array_equation` (from the ODE array-equation support) and `has_array_equations` were two spellings of the same shape check; keep the per-equation predicate in `utils.jl` and make `has_array_equations` delegate to it. `ODEFunction` opts into `accepts_array_equations`, matching what its `generate_rhs` branch already assembles through `array_residual_maker`. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> Agent-Harness: Devin CLI 3000.10.21 Agent-Model: SWE-2 Max Agent-Session: local Devin CLI session, no shareable URL (workspace /home/crackauc/sandbox/tmp_20260913_101544_55869)
Post-SciML#5131 `flat_unknowns` handles the array unknown unconditionally, so an array equation over `u(t)[1:4]` declared as `[u]` constructs and evaluates rather than throwing "array unknowns". Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> Agent-Harness: Devin CLI 3000.10.21 Agent-Model: SWE-2 Max Agent-Session: local Devin CLI session, no shareable URL (workspace /home/crackauc/sandbox/tmp_20260913_101544_55869)
`ODEProblem` accepts array equations now, so it no longer belongs in the "still require scalarized equations" testset (`SDEProblem` rejection remains covered in the same file), and a `D(u) ~ -u` system over the array unknown `u` constructs because `flat_unknowns` handles it. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> Agent-Harness: Devin CLI 3000.10.21 Agent-Model: SWE-2 Max Agent-Session: local Devin CLI session, no shareable URL (workspace /home/crackauc/sandbox/tmp_20260913_101544_55869)
ChrisRackauckas-Claude
force-pushed
the
unify-array-equation-capability
branch
from
September 13, 2026 22:02
9403b2a to
d834c1a
Compare
The scalar path only accepts `D(x) ~ f` from `complete`; `D(x) - f ~ 0` is a DAE and needs `mtkcompile` or `DAEProblem`. Treat `D(u[2:4]) .- f ~ 0` the same way instead of pattern-matching it back into explicit form in every consumer. Also drop the duplicate InterfaceII include of `array_equation_dae.jl` (InterfaceI already runs it) and scope the docs note about `jac`/`sparse` to array equations. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Agent-Harness: Claude Code Agent-Model: claude-fable-5-1 Agent-Session: https://claude.ai/code/session_01VorhgJYhDasyVLpGNrviga Claude-Session: https://claude.ai/code/session_01VorhgJYhDasyVLpGNrviga
`OptimizationProblem` accepts `f::MultiObjectiveOptimizationFunction` in SciMLBase, but MTK could never produce one: `generate_cost` always folds `get_costs(sys)` through `consolidate` into a scalar. - `costs(sys)` returns the unconsolidated objective vector: the system's own costs plus the consolidated cost of each subsystem, namespaced. - `MultiObjectiveOptimizationFunction(sys)` generates a vector-valued objective over `costs(sys)`. `jac` builds the objective jacobian; `hess` is a vector with one generated hessian function per objective, the shape `OptimizationBase.instantiate_function` iterates. - The constraint fields are assembled by `generate_constraint_fields`, shared with `OptimizationFunction` instead of duplicated. - `OptimizationProblem(sys, op; multiobjective = true)` routes through it. `weights` cannot combine with it. Also drop the `jac`/`sparse` guards on `NonlinearFunction` and `DAEFunction` for array equations: since `full_equations` scalarizes array equations, the symbolic jacobian and sparsity pattern already come out with one row per residual, so both now work and are tested against ForwardDiff and the analytic stencil. The array-residual DAE tests go back to `BrownFullBasicInit`, which runs on the DiffEqBase 7.18.1 floor. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Agent-Harness: Claude Code Agent-Model: claude-fable-5-1 Agent-Session: https://claude.ai/code/session_01VorhgJYhDasyVLpGNrviga Claude-Session: https://claude.ai/code/session_01VorhgJYhDasyVLpGNrviga
`costs` and the `generate_multiobjective_*` / `calculate_multiobjective_*` functions are exported from ModelingToolkitBase and therefore reexported by the ModelingToolkit facade; list them in the root QA allow-list. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Agent-Harness: Claude Code Agent-Model: claude-fable-5-1 Agent-Session: https://claude.ai/code/session_01VorhgJYhDasyVLpGNrviga Claude-Session: https://claude.ai/code/session_01VorhgJYhDasyVLpGNrviga
This was referenced Sep 14, 2026
Member
|
I finally like it, and there's a lot riding on it downstream so I'm going for it, but @AayushSabharwal please take a look and if any clean ups are required we can modify post. |
ChrisRackauckas
added a commit
that referenced
this pull request
Sep 14, 2026
- Document the PDE solution interface shared by discretizers (#5149) - Point the docs `ModelingToolkit` source at the repository root (#5152) - Unify array-equation support behind a capability trait; fold in #5101 ODE support and MultiObjectiveOptimizationFunction (#5148) - Raise SymbolicIndexingInterface compat lower bound to 0.3.47 (#5153) - Compare `complete` and `mtkcompile` array-unknown problems through symbolic indices (#5154) Agent-Harness: Claude Code Agent-Model: claude-opus-5[1m] Claude-Session: https://claude.ai/code/session_014FEzNTLFutCmTEAZ3zBg5R Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Sep 20, 2026
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.
Summary
Stacked on the merged #5131 (array unknowns via
flat_unknowns); folds in the closed #5049 (array equations on the nonlinear path) and the open #5101 (array differential equations on the ODE path), and, as the last commit, the multi-objective optimization work fromChrisRackauckas-Claude:multiobjective-optimization. If this lands, #5101 and that branch are subsumed."Accepts array equations" is a named, per-constructor capability:
accepts_array_equations(constructor)gates__process_SciMLProblem, andODEFunction,DAEFunctionandNonlinearFunctionopt in. All three lower an array equation to one scalar row per element through the sharedarray_residual_maker;full_equationsscalarizes array equations, so everything downstream of it (symbolic Jacobians, sparsity,tgrad, the initialization system) sees one row per residual without further changes.Review of the earlier revision and what changed
The earlier revision of this PR (through d834c1a) was reviewed in full. Everything in it was correct as far as tests and probes could tell; the following was simplified or extended on top:
explicit_array_derivative_formpattern-matchedD(u[2:4]) .- f ~ 0back intoD(u[2:4]) ~ fingenerate_rhs,calculate_massmatrix,full_equationsandbuild_explicit_observed_function. The scalar path accepts no such thing:D(x) - f ~ 0is a DAE and must go throughmtkcompileorDAEProblem. Array equations now behave the same way (the ODE path rejects them with the same "nondifferentiated variables" error, andDAEProblemkeeps accepting them, asarray_equation_dae.jltests). That removes ~50 lines and five call sites.jac = true/sparse = truework for array residuals onNonlinearFunctionandDAEFunctioninstead of throwing. Sincefull_equationsscalarizes,calculate_jacobianandjacobian_sparsityalready produce one row per residual; the guards only hid that. Tested against a ForwardDiff Jacobian (nonlinear, dense and sparse, including a solve) and against the analytic stencil with theγterm (DAE, dense). Consequence: WIP: build implicit DAE Jacobians and sparsity from scalarized array residuals #5136 is not needed for the Jacobian itself.sparse = trueon a DAE in residual form still errors insidecalculate_massmatrix("Only semi-explicit constant mass matrices"); that is pre-existing and equally true for scalar residual-form DAEs, so it is out of scope here.array_equation_dae.jlwas included twice (InterfaceI on master, and again in InterfaceII by this PR). The duplicate is removed.BrownFullBasicInitagain rather than a hand-computed consistentdu0underNoInit(), so solver-driven DAE initialization is exercised through the array residual. BuildODEProblemfrom array differential equations withcompleteonly #5101 had switched toNoInitbecauseBrownFullBasicInittripped aParameterDespecializationWrapperassertion on the downgrade job's DiffEqBase 7.15; theDiffEqBase = "7.18.1"floor this PR already carries is what fixes that, verified by running the test against a pinned DiffEqBase 7.18.1 (see below).mtkcompilebeforejac/sparse" notes are removed since they no longer apply; the nonlinear tutorial note describes what array equations look like instead.Multi-objective optimization (last commit)
OptimizationProblemacceptsf::MultiObjectiveOptimizationFunctionin SciMLBase but MTK never produced one (generate_costalways consolidates to a scalar).costs(sys): the unconsolidated objective vector (own costs, then the consolidated cost of each subsystem, namespaced). Exported and documented next tocost.MultiObjectiveOptimizationFunction(sys): vector-valued objective overcosts(sys);jacbuilds the objective Jacobian;hessis a vector of generated functions, one per objective, which is the shapeOptimizationBase.instantiate_functioniterates ([... for h in f.hess]). The original branch generated one function returning a vector of matrices, which that consumer cannot use.cons,cons_j,cons_h, their prototypes,cons_expr) are assembled by onegenerate_constraint_fieldsshared withOptimizationFunctioninstead of a duplicated 25-line block.OptimizationProblem(sys, op; multiobjective = true)routes through it;weightsis rejected in combination with it.costs, thegenerate_multiobjective_*/calculate_multiobjective_*functions, the SciMLBase constructor) have docstrings and@docsentries.Not in scope
generate_rhsresidual codegen is unchanged from #5049; OOPDualpromotion forDAEProblem{false}; arrayconstraintsingenerate_cons; sparse Jacobians for residual-form DAEs (see above).Verification
Julia 1.12.7, ModelingToolkitBase test environment on this machine.
All commands run from
lib/ModelingToolkitBaseunless noted;GROUP=… julia --project -e 'using Pkg; Pkg.test()'for the groups.GROUP=InterfaceIInterfaceI | 1732 pass | 5 broken | 1737 | 43m52s—Testing ModelingToolkitBase tests passed. The 5 broken are pre-existing@test_brokens; this PR adds none.GROUP=InterfaceIItests passed:Array Equation ODEProblem Test | 51 | 51,Array-equation Nonlinear | 172 | 172,NonlinearSystem Test | 113 | 113,Code Generation Test | 65 | 65,JumpSystem Test | 4207 | 4207, … (2 pre-existing broken inOptimal Control + Constraints).test/array_equation_dae.jl(InterfaceI member, rerun alone after the last test edit)array_equation_dae.jl | 32 | 32 | 1m02stest/optimization/multiobjective.jl(new, Optimization group)optimization/multiobjective.jl | 35 | 35 | 8m38stest/optimization/optimizationsystem.jl(sharesgenerate_constraint_fields)126 pass | 1 broken (pre-existing) | 127 | 3m22sGROUP=QA54 | 54; Aqua: 20 of 21 pass. The one failure isJET.report_package(...; mode = :typo)with 234 reports, alllocal variable ##And#…/##Call#… may be undefinedfromMoshi.@matchexpansions, none in code this PR touches. That check has never been green on master since it was enabled (#4832); a separate agent reproduced 234 on clean master with the same JET/Moshi/SymbolicUtils versions, reduced it to a Moshi+JET-only MWE, and confirmed 0 reports with the upstream fix Roger-luo/Moshi.jl#93. Tracked in #5063; no new issue opened.BrownFullBasicIniton the array-residual DAE at the compat floorDiffEqBasepinned to 7.18.1 (and separately 7.21.1):retcode Success, max error3.0e-3against the analytic heat solution, same as the test asserts.GROUP=QA(repo root)QA | 40 | 40 | 4m35s,Testing ModelingToolkit tests passedafter adding the six new reexported names totest/qa/qa.jl's allow-list (the first CI run failed only that check).--check --diff) andtyposover every changed filejulia --project=docs docs/make.jl, repo root)Doctest,ExpandTemplates(all@example/@docsblocks),CrossReferences,CheckDocument(checkdocs = :exports) andRenderDocument; only pre-existingsize_threshold_warnwarnings. The externallinkcheckstep was disabled for the local rerun after GitHub answered429to theNEWS.mdlink on the first run.Not verified: Julia LTS /
pre; the downgrade CI job itself (only the 7.18.1 pin above);Optimizationgroup'sdynamic_optimization.jl/test_infiniteopt.jl(the Pyomo/CasADi pixi install fails on this machine's network, unrelated); downstream packages; GPU.sparse = trueon residual-form DAEs is a pre-existingcalculate_massmatrixlimitation and is intentionally not covered.Judgment calls to push back on: (1) dropping the residual-form ODE rewrite from #5101 (array
D(u) .- f ~ 0now needsDAEProblem/mtkcompile, exactly like scalar); (2)hesson the multi-objective function is a vector of per-objective functions rather than one function returning a vector of matrices, chosen to matchOptimizationBase; (3)jac/sparseguards removed instead of extended.CI on ee92d65 (first push) and e4f7d29 (second push)
Everything is green except the following, all of which reproduce on master's own CI and are not touched by this PR:
sublibrary-ci / lib/ModelingToolkitBase [QA] / Julia 1and the roottests / QAJET step: the 234 Moshi@matchreports, tracked in ModelingToolkitBase QA lane red since #4832: JET typo mode reports 263 errors, 234 of them from Moshi@matchcodegen #5063 (the roottests / QAjob additionally flagged the six new reexports; fixed by the last commit).tests / InterfaceIandtests / InterfaceIIon Julia 1 and lts,downgrade-mtkbase (InterfaceI|InterfaceII),Downgrade Tests - InterfaceI: every failure is inside the "Array unknowns on acompleted system" / "DAEProblem" testsets that Support array unknowns on acompleted system for every problem type #5131 added toodesystem.jl,sdesystem.jlandnonlinearsystem.jl(rawu0/f/gorder comparisons acrosscompletevsmtkcompile, andsymbolic_container(::IndexCache)missing under the downgrade dependency set). Master's run for the Support array unknowns on acompleted system for every problem type #5131 merge commit https://github.com/SciML/ModelingToolkit.jl/actions/runs/34779646543 fails the same jobs; the previous master commit was green. Fixed separately: Raise SymbolicIndexingInterface compat lower bound to 0.3.47 #5153 raises theSymbolicIndexingInterfacefloor to 0.3.47 (the version whosegetsymno longer recurses intoparameter_index(::IndexCache, ::Int)), and Compare complete/mtkcompile array-unknown problems through symbolic indices #5154 makes those tests compare through symbolic indexing instead of assumingmtkcompilekeeps the array unknown's element order (with ModelingToolkitTearing loaded it reverses it). This PR's ownarray_equation_dae.jlpasses on the downgrade job, i.e.BrownFullBasicIniton the array residual works at the DiffEqBase 7.18.1 floor in CI as well.sublibrary-ci / lib/ModelingToolkitBase [Initialization] / Julia ltson the second push only:initializationsystem.jl"Initialization problem type is shared across models, use_scc = true",ForwardDiff.gradient(loss, [1.5])[1]evaluates to9.898834314793689e154. The same job with identical dependency versions passed on the first push, and master's run for df57469 (https://github.com/SciML/ModelingToolkit.jl/actions/runs/34766850677) fails with the identical value, so it is a pre-existing intermittent failure, tracked in Flaky: downgrade Initializationuse_scc = truegradient test returns 1e153 (Dual solve goes non-finite at t=0) #5125. Root cause is upstream: theremaked initialization problem has Dualu0but a non-Dual-arrayp, so NonlinearSolve's Dual hooks do not fire anddefault_nlsolve's Broyden iterates in Dual arithmetic, letting the partials grow to ~1e155 while only the value residual is checked for convergence.SciMLSensitivity.jl/Core8/1/:Enzyme through initin SciMLSensitivity's owndesauty_dae_mwe.jlraisesEnzymeNoTypeError. Fails on every masterDownstream.ymlrun since Concretize initialization problems once at construction #5062 (first bad job https://github.com/SciML/ModelingToolkit.jl/actions/runs/34695771592/job/103558875060, identical dependency set as the last good one) and on SciMLSensitivity's own CI against released MTK 11.43.1; tracked in Enzyme SCC initialization regresses at concretize_initializeprob on Julia 1.13 #5140.build:makedocsstops at[:linkcheck]becausehttps://docs.sciml.ai/PDEBase/stable/interface/(a link Document the PDE solution interface shared by discretizers #5149 added on master) answers 403; master's own docs job for that commit fails the same way. Everything before linkcheck (doctests, all@example/@docsblocks, cross-references,checkdocs) passes on CI, and the full render passes locally with linkcheck off (see Verification).downgrade-mtkbase (Optimization) / test: cancelled after the 6-hour limit on both pushes, exactly as on master's run for 444ccfb; the group is known to be slow (seetest_groups.toml) and the downgrade dependency set makes it slower still.Links
ODEProblemfrom array differential equations withcompleteonly #5101 and feat: accept array equations on the NonlinearProblem path #5049 (closed); stacked on Support array unknowns on acompleted system for every problem type #5131 (merged)ModelingToolkitsource at the repository root #5152completed system for every problem type #5131 found via this PR's CI, fixed separately: Raise SymbolicIndexingInterface compat lower bound to 0.3.47 #5153 and Compare complete/mtkcompile array-unknown problems through symbolic indices #5154@matchcodegen #5063🤖 Generated with Claude Code (model: claude-fable-5-1); the earlier revision of this PR was generated with Devin CLI (model: SWE-2 Max).
https://claude.ai/code/session_01VorhgJYhDasyVLpGNrviga