Skip to content

Fix collect_vars! after late invalidation on Julia 1.10 - #4852

Draft
ChrisRackauckas-Claude wants to merge 1 commit into
SciML:masterfrom
ChrisRackauckas-Claude:agent/fix-collector-invalidation
Draft

Fix collect_vars! after late invalidation on Julia 1.10#4852
ChrisRackauckas-Claude wants to merge 1 commit into
SciML:masterfrom
ChrisRackauckas-Claude:agent/fix-collector-invalidation

Conversation

@ChrisRackauckas-Claude

Copy link
Copy Markdown
Member

Please ignore this PR until reviewed by @ChrisRackauckas.

Summary

  • route recursive four-argument collect_vars! calls from metadata collection through a non-inlined Ref{Function} dispatch barrier
  • preserve the public downstream extension point: methods added to four-argument collect_vars! after package load are still dispatched dynamically
  • add a regression that first compiles default collection, adds an unrelated method that triggers the Julia 1.10 invalidation, checks scalar and expression defaults, and then checks a late downstream collect_vars! method

Why

Loading DynamicQuantities and then unrelated packages can invalidate the mutually recursive collect_vars! / collect_var! inference cycle on Julia 1.10. After that invalidation, parameters used only in defaults can disappear. In downstream initialization this manifested as a missing Initial(x0) parameter and as y0 no longer being recognized as an unknown.

The first bad ModelingToolkit commit is 22000aa03d317353500c3c73ca01744d069530be (refactor: change collect_vars! for type-stability); its parent ca6eb0a92 is good. A package-load warmup is not durable because later method additions can invalidate the warmed specialization again.

The barrier is deliberately restricted to the recursive four-argument calls inside collect_var!. The explicit depth = 0 preserves their previous default-keyword semantics, while the abstract function cell prevents inference from rebuilding the problematic mutual cycle. It also avoids reaching into dependency internals or changing public API.

Performance

Fresh-process construction microbenchmarks, baseline -> patch:

case allocations time
plain variable 1240.01 B -> 1240.01 B 4228.6 ns -> 4214.2 ns
parameter default 3056.01 B -> 3088.01 B 11029.7 ns -> 11288.1 ns (+2.34%)
bounded metadata 1896.01 B -> 1992.01 B 10347.1 ns -> 10502.4 ns (+1.50%)

This is system-construction work, not the nonlinear solve! path. The warmed ImplicitDiscreteSolve homotopy solve remains 0 B per solve in the separate cache benchmark.

Local verification

  • Julia 1.10 targeted dq_units.jl: initialization 1/1; invalidation regression 6/6
  • Julia 1.12 targeted dq_units.jl: initialization 1/1; invalidation regression 6/6
  • Julia 1.10 real late-JSON reproduction: [json_p] is collected both before and after the late method addition; late custom dispatch collects [custom_p]
  • Julia 1.10 official GROUP=InterfaceI Pkg.test("ModelingToolkitBase"): 1498 passed, 5 existing broken, 0 failed/errored
  • Julia 1.10 official GROUP=Initialization: the two collector errors are gone; 583 passed, 9 unrelated DelayDiffEq DEOptions errors, 14 existing broken. The nine resolver errors are tracked by Retroactively cap DelayDiffEq at OrdinaryDiffEqCore 4.8.0 JuliaRegistries/General#162781.
  • Julia 1.12 official GROUP=QA: JET 54/54 passed. Aqua reports 45 ambiguities and 14 piracy methods; an exact clean-master d39174937 run reproduces the same Aqua 9 passed / 2 failed result, so that baseline failure is being handled separately rather than hidden here.
  • Runic 1.7.0 whole-repository --check .: exit 0
  • git diff --check: clean

No tests were skipped, disabled, silenced, or loosened.

Investigation/process notes

The work started from the downstream LinearSolve/ModelingToolkit initialization failures discovered while validating the ImplicitDiscreteSolve homotopy cache path. I reproduced the failure with frozen dependencies on Julia 1.10, separated the two collector errors from nine independent DelayDiffEq resolver errors, bisected the collector regression, minimized the invalidation trigger, compared direct calls, invoke, function wrappers, and a dynamic function cell, verified late downstream dispatch, benchmarked construction overhead, then ran the package groups and formatting checks above. The unrelated clean-master QA failures are being bisected in a separate worktree so this remains one focused fix.

Route recursive metadata collection through a dynamic dispatch barrier so late method additions cannot corrupt Julia 1.10 inference while downstream collect_vars! extensions remain visible.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>

Copy link
Copy Markdown
Member Author

Investigation scratchpad / execution record:

  1. Reproduce the downstream initialization failure on frozen Julia 1.10 dependencies and separate collector failures from resolver failures. Completed: 2 collector errors; 9 independent DelayDiffEq DEOptions errors.
  2. Bisect the collector regression. Completed: first bad 22000aa03d317353500c3c73ca01744d069530be, good parent ca6eb0a92.
  3. Minimize the invalidation and compare barriers without sacrificing the public four-argument collect_vars! extension point. Completed: the direct Ref{Function} barrier is the smallest durable option; invoke adds substantially more allocation and a function wrapper adds complexity without reducing allocation.
  4. Add a late-invalidation and late-extension regression. Completed: 6/6 on Julia 1.10 and 6/6 on Julia 1.12.
  5. Validate the actual downstream paths and benchmark construction cost. Completed: real late JSON and custom dispatch work; official Julia 1.10 InterfaceI passes 1498 tests; metadata construction overhead is reported in the PR body.
  6. Run QA and formatting. Completed: JET 54/54 and whole-repository Runic exit 0. Aqua failures reproduce unchanged on exact clean master and are under a separate mandatory bisect/fix investigation.
  7. Monitor CI and address only failures attributable to this focused diff. In progress.

Copy link
Copy Markdown
Member Author

Clean-master QA classification is now fully local and status-checked: exact origin/master d39174937, Julia 1.12.6, official GROUP=QA exited 1 with JET 54/54 passing and Aqua 9/11 (45 ambiguities, 14 piracy methods). This matches the feature branch exactly. The ambiguity portion is already scoped separately in #4849; its current-master validation is in progress. The piracy portion remains separate. Neither baseline category is being suppressed or folded into this collector fix.

Copy link
Copy Markdown
Member Author

CI classification: SciMLSensitivity.jl/Core8/1 failed before running tests during Pkg.develop resolution. The log resolves General only through SciMLBase 3.39.1, while SciMLSensitivity 7.118.0 requires SciMLBase 3.40+, producing an unsatisfiable requirement. This is independent of the collector diff and is tracked/fixed by SciML/SciMLSensitivity.jl#1575.

Copy link
Copy Markdown
Member Author

CI classification: SBMLToolkit.jl/All/1 also fails before tests during Pkg.test re-resolution. Its downstream project pins SBMLToolkitTestSuite = 0.0.5, while current OrdinaryDiffEq/Sundials compatibility under General's SciMLBase 3.39.1 forces TestSuite 1.0+; the resolver reports no versions left. This is independent of the collector diff. Per repository policy, a separate clean-master reproduce/bisect/fix-or-issue investigation is now running; I will link its focused result here.

Copy link
Copy Markdown
Member Author

Additional CI classification:

  • lib/ModelingToolkitBase [InterfaceII] / Julia pre fails in dde.jl with the same incompatible OrdinaryDiffEqCore.DEOptions constructor already reproduced in the official Initialization group and tracked by Retroactively cap DelayDiffEq at OrdinaryDiffEqCore 4.8.0 JuliaRegistries/General#162781.
  • lib/ModelingToolkitBase [Extended] / Julia lts reaches the distributed test, then worker 2 errors with ArgumentError: cannot deserialize a dropped RuntimeGeneratedFunction; serialize it before calling drop_expr. This is unrelated to the collector call path. A separate required clean-master Julia 1.10 reproduce/bisect/fix-or-issue investigation is now running; its result will be linked here.

Copy link
Copy Markdown
Member Author

Completed-cluster confirmation from the actual logs:

  • all three Extended jobs (Julia lts/current/pre) fail at the same distributed worker deserialization of a dropped RuntimeGeneratedFunction; the dedicated clean-master investigation covers the full cluster
  • InterfaceII lts/pre both fail at the already-tracked DelayDiffEq DEOptions constructor mismatch
  • QA current passes JET 54/54 and then reports exactly the clean-master Aqua baseline: 45 ambiguities and 14 piracy methods (Aqua 9/11)

None of these failures mention or exercise the new collector barrier/regression. #4849 handles the ambiguity class; #4670 retains the piracy ownership work.

Copy link
Copy Markdown
Member Author

Distributed-cluster investigation update: exact clean origin/master d39174937, Julia 1.10.11, fresh depot, official GROUP=Extended Pkg.test() reproduces the worker-2 dropped-RuntimeGeneratedFunction deserialization error after BigSystem 100/100, C 1/1, and multithreading 1/1 pass. The dependency boundary is RuntimeGeneratedFunctions 0.5.23: 0.5.22 has the prior working behavior; commit 6550973d9290c906b1f61d37d416695e95e63a7c removed dropped-RGF serialization and 483e2e938e1bfc5b5df6dc3aab0064c7005d08b6 released it. A separate public-API fix passes the RuntimeGeneratedFunctions package tests; exact MTK Extended validation is in progress.

Copy link
Copy Markdown
Member Author

Direct CI confirmation: sublibrary-ci / lib/ModelingToolkitBase [InterfaceI] / Julia lts completed successfully. Actual log summary: 1498 passed, 5 existing broken, 1503 total, followed by Testing ModelingToolkitBase tests passed. This matches the local Julia 1.10 official-group result and includes the new late-invalidation regression.

Copy link
Copy Markdown
Member Author

The direct ModelingToolkitBase [InterfaceI] matrix is now green on all three Julia lanes (lts/current/prerelease). Each actual job log reports 1498 passed, 5 existing broken, 1503 total, followed by Testing ModelingToolkitBase tests passed. This includes the new collector invalidation regression across the supported compiler range.

Copy link
Copy Markdown
Member Author

Downstream SBMLToolkit failure in job 90790793844 is confirmed independent of this PR and has a validated fix in SciML/SBMLToolkit.jl#223.

I reproduced the failure on the exact clean ModelingToolkit base d39174937 with SBMLToolkit 7dbc7bca71211d97ba2997b2c38a9f6d69c21827, Julia 1.12.6, an isolated depot, and the same fixed General registry snapshot (cf87b2e4a16d9b7ba710aaac144d607d0bf724df, tree 31befd7172041c2f95b752ca5d7147232c79da23). The hosted-shaped GROUP=All workflow exits 1 during Pkg resolution: SBMLToolkit pins SBMLToolkitTestSuite = "0.0.5", while current SciMLBase selects OrdinaryDiffEq 7 and Sundials 6, which require the 1.x test-suite line.

A first-parent compatibility bisect places the stale edge at SBMLToolkit commit 753fbc269868178401b9227a850203971412b220 (SciML/SBMLToolkit.jl#199), which first advertised OrdinaryDiffEq 7 / ModelingToolkit 11 while retaining the legacy test-suite constraint.

I then reran the exact workflow on clean ModelingToolkit d39174937 with SBMLToolkit PR #223 head 18eb314e9c15114f029c78f6f6355882ea7aedda. It selected SBMLToolkitTestSuite 1.1.2, OrdinaryDiffEq 7.2.0, Sundials 6.4.2, and SciMLBase 3.39.1, exited 0, and reported Testing SBMLToolkit tests passed (108/108 assertions across all seven Core test files). Runic --check also exits 0 on that head.

So this downstream check should clear once SciML/SBMLToolkit.jl#223 is merged/released; no collector change is implicated.

Copy link
Copy Markdown
Member Author

The Extended/LTS Distributed Test failure is unrelated to this PR's collector changes. I reproduced it from clean ModelingToolkit master d39174937fead779b29fa5baa50ba975adbde8c9 with the official GROUP=Extended command and traced it to RuntimeGeneratedFunctions v0.5.23.

Controls on the same clean master / Julia 1.10 setup:

  • v0.5.23: reproduces ArgumentError: cannot deserialize a dropped RuntimeGeneratedFunction
  • pinned v0.5.22: full Extended group passes
  • local fixed v0.5.23: full Extended group passes, including Distributed Test

The first bad RGF source commit is 6550973d9290c906b1f61d37d416695e95e63a7c (#136); #137 then changed its planned v0.6.0 release to v0.5.23 despite the behavior break. The focused fix and cross-process regression are in SciML/RuntimeGeneratedFunctions.jl#138. No ModelingToolkit workaround should be needed once that fix is released.

Copy link
Copy Markdown
Member Author

The newly terminal StructuralIdentifiability.jl/Core/1/ failure is already covered by focused draft StructuralIdentifiability #536; no duplicate PR is needed.

Exact #4852 downstream job reaches Core/y_saturation.jl and errors at output_saturation.jl:28 with UndefVarError: generators not defined, including Julia's ambiguity hint for the competing AbstractAlgebra/Nemo/RationalFunctionFields bindings. #536 independently reproduced and bisected that clean-master dependency boundary, replaces the undocumented ambiguous helper with the generator vector already supplied to the field constructor, and adds a rational-output regression. Its local GROUP=Core, GROUP=QA, and Runic checks passed; all 11 hosted checks are green. The PR remains draft for @ChrisRackauckas review.

Copy link
Copy Markdown
Member Author

Catalyst's stale expected-broken follow-up has now merged as Catalyst #1512, and I reran the complete downstream group locally against this collector branch.

Exact validation:

  • Catalyst master 79144ad28f49b594f84965c58be290be51041f6e
  • ModelingToolkitBase from this PR, 6f0a545a9d57ffec07fce3d2014e1818d19dcb3b
  • Julia 1.12.6
  • registered graph selected SciMLBase 3.39.1, OrdinaryDiffEq 7.2.0, and ModelingToolkitBase's local source path
  • GROUP=Modeling julia +1.12 --project=. -e 'using Pkg; Pkg.test()'

Terminal result: exit 0, Testing Catalyst tests passed. The decisive summary is:

Test Summary:     | Pass  Broken  Total     Time
Conservation Laws |  249      17    266  3m38.9s

The complete official group ran for 1829.82 seconds and also cleared the former prerequisites, including Symbolic Stoichiometry 30/30 and DSL Options 317 pass / 1 existing broken. This confirms the recorded Catalyst/Modeling/1 failure is stale relative to merged Catalyst master, not a collector regression. I am rerunning only that failed downstream job.

Copy link
Copy Markdown
Member Author

The MethodOfLines.jl/MOL_Interface1/1/ failure in job 90790793860 is independent of this PR's collector change and is now covered by focused draft PDEBase #102.

I reproduced it with exact clean ModelingToolkit base d39174937, MethodOfLines 5ec02a46, Julia 1.12.6, a fresh depot, and the hosted GROUP=MOL_Interface1 / Pkg.test(coverage=true) workflow. Registered PDEBase 0.1.31 exits 1 with the sole error at MOLtest1.jl:231 (Array u): MethodError: operation(::Symbolics.Arr{Num,1}) from PDEBase.get_ops.

This is a PDEBase behavioral regression, not a missing Symbolics operation(::Arr) method. PDEBase #98 rewrote indexed-array flattening; General 0.1.30 is the pre-regression tree (5b917e0), while 0.1.31 is the bad tree (97c9de5). The actual fix and regression test already merged as PDEBase #101, using the public owning-module SymbolicUtils.Rewriters API, but master still declared 0.1.31 and therefore could not be resolved downstream.

On the same downstream manifest, changing only PDEBase to current master with the 0.1.32 bump makes the official group exit 0 (MOL_Interface1 12m50.8s; MethodOfLines tests passed). PDEBase Core also passes 12 + 11 + 18 + 46 tests, QA passes 20/20, and Runic is clean. No ModelingToolkit workaround is needed; this check should clear once PDEBase #102 is merged and 0.1.32 is registered.

@ChrisRackauckas-Claude

Copy link
Copy Markdown
Member Author

Terminal cluster correction and exact count:

  • all 12 root/sublibrary Initialization and InterfaceII failures (lts/current/pre in each matrix), plus
  • SciMLBase.jl/SymbolicIndexingInterface/1/

fail with the same OrdinaryDiffEqCore.DEOptions constructor mismatch from the incompatible registered DelayDiffEq/Core pairing. I downloaded and checked each of the 13 completed job logs; this includes the current-Julia lanes, not just lts/pre. This cluster is independent of the collector diff and is covered by General #162781.

One correction to the earlier shorthand: the SciMLBase/SymbolicIndexingInterface job reaches tests and fails at DEOptions; it is not a resolver failure. The actual resolver-only failure is SciMLSensitivity.jl/Core8/1/, tracked by SciMLSensitivity #1575.

@ChrisRackauckas-Claude

Copy link
Copy Markdown
Member Author

MethodOfLines cluster is now fully validated:

  • PDEBase #102 fixes the release boundary for both MOL_Interface1 and MOL_Interface2 failures.
  • Exact local controls against clean ModelingToolkit d39174937 pass for both groups; MOL_Interface2 reports 5 pass, 8 existing broken, 13 total, followed by Testing MethodOfLines tests passed.
  • Allow arrays of variables #102 hosted CI is terminal with 23 successes. Every MethodOfLines downstream check is green.
  • Its sole hosted failure is PDEBase clean-master documentation compat drift, independently reproduced/bisected to 97c9de5 and fixed in focused draft PDEBase #103. Minor cleanup #103 has local Core 88/88 on Julia 1.12, Core 73/73 on Julia 1.10, QA 20/20, exact docs install, full Documenter build, and whole-repository Runic all passing.

Thus both #4852 MethodOfLines failures are independent of the collector diff and have direct local plus hosted coverage.

@ChrisRackauckas-Claude

Copy link
Copy Markdown
Member Author

The independent ModelingToolkitBase BVP downgrade failure is now covered by focused draft #4853.

Exact clean-master control (d39174937, Julia 1.10.11, the same julia-downgrade-compat action revision and alldeps workflow) exits 1 in the official GROUP=InterfaceII run with the hosted pair of Float64(::ForwardDiff.Dual) errors: Lotka–Volterra and BVP cost-function compilation. Its terminal BVP summary is 13 passed, 2 errored, and 5 existing broken.

The root cause is the pre-1.17.3 MIRK solution-evaluation buffer, fixed upstream by SciML/BoundaryValueDiffEq.jl#568 and released in BoundaryValueDiffEqMIRK 1.17.3. #4853 raises only the ModelingToolkitBase compatibility floor to that first good release.

With that one-line floor change, the exact downgrade workflow selects MIRK 1.17.3 and the full official InterfaceII group exits 0 (Optimal Control + Constraints Tests: 22 passed, 6 existing broken; Testing ModelingToolkitBase tests passed). The focused BVP file, whole-repository Runic, and exact clean-vs-feature QA comparison are also recorded in #4853. No collector-path change is implicated.

Copy link
Copy Markdown
Member Author

The ModelOrderReduction downstream failure is isolated and fixed in draft PR SciML/ModelOrderReduction.jl#169.

Root cause: PDEBase 0.1.31 correctly stopped creating an incidental symbolic_discretize binding when it moved from using SciMLBase to explicit imports. ModelOrderReduction was relying on MethodOfLines to expose that undeclared binding. The fix imports the exported/public/documented owner API directly from SciMLBase and adds SciMLBase as a direct test/docs dependency.

I reproduced the original UndefVarError on clean ModelingToolkit d39174937 + ModelOrderReduction 4c1fc0b5, then reran that exact downstream workflow with the patch: DataReduction 15/15, DEIM 4/4, utils 11/11, natural exit 0. ModelOrderReduction's isolated Core, QA, full docs build, and Runic also pass.

Copy link
Copy Markdown
Member Author

Consolidated terminal CI ledger

The hosted matrix is terminal at 64 success / 26 failure / 3 skipped. The 26 failures are fully accounted for; none points to the collector invalidation barrier in this PR.

  1. 13 initialization/interface/re-export failures — DelayDiffEq/Core release metadata. Fresh resolution selects an incompatible DelayDiffEq/Core pairing and fails at DEOptions. Retroactively cap DelayDiffEq at OrdinaryDiffEqCore 4.8.0 JuliaRegistries/General#162781 contains the focused compat correction, is open and non-draft, and its Julia 1.3–1.12 registry checks are green.
  2. 3 ModelingToolkitBase Extended failures — RuntimeGeneratedFunctions serialization boundary. Restore serialization of dropped runtime-generated functions RuntimeGeneratedFunctions.jl#138 is the focused draft fix; exact clean controls show RGF 0.5.22 pass, 0.5.23 fail, patched source pass.
  3. 2 QA failures — clean-master baselines. The focused Aqua correction is Fix ModelingToolkitBase method ambiguities #4849; the pre-existing JET baseline is tracked by QA: track pre-existing Aqua/JET/ExplicitImports findings (root + ModelingToolkitBase) #4670.
  4. 1 Catalyst/Modeling failure. Activate corrected conservation-law tests Catalyst.jl#1512 is merged; the exact downstream Modeling group passed locally against this collector branch.
  5. 2 MethodOfLines failures. PDEBase owner-import/docs fixes Allow arrays of variables #102/Minor cleanup #103 and MethodOfLines owner-import fix exprs_occur_in #613 are merged. PDEBase 0.1.32 and MethodOfLines 0.11.19 are both registered via New version: PDEBase v0.1.32 JuliaRegistries/General#162815 and #162817. At the scheduled 07:53 EDT audit, General's trees exactly matched the source commits and a fresh Julia 1.10 environment actually loaded registered MethodOfLines 0.11.19 + PDEBase 0.1.32 successfully.
  6. 1 ModelOrderReduction failure. Import symbolic_discretize from SciMLBase ModelOrderReduction.jl#169 is the focused draft public-owner import fix. Its sole hosted LTS red is the now-stale MethodOfLines 0.11.18/PDEBase 0.1.32 boundary; the registered replacement boundary above loads locally. The current token lacks admin permission to rerun only that job, so no empty commit was used to churn CI.
  7. 1 SBMLToolkit failure. Restore current SBML test-suite compatibility SBMLToolkit.jl#223 is the focused draft fix; the exact downstream suite passed 108/108 locally against clean ModelingToolkit plus that branch.
  8. 1 SciMLSensitivity failure. Restore SciMLBase 3.40 compat for NonlinearSolveBase 2.38 JuliaRegistries/General#162820 corrects the factually wrong NonlinearSolveBase 2.38 cap and is green across Julia 1.3–1.12. Resolution then correctly stops at the valid OrdinaryDiffEqCore 4.11 cap: that registered release privately accesses SciMLBase.islinear. Restore SciMLBase 3.39 compatibility for owner imports OrdinaryDiffEq.jl#4066 is the source/future-release path for fixed ODECore 4.12; the 4.11 cap must not be removed.
  9. 1 StructuralIdentifiability failure. Avoid ambiguous generators in output saturation StructuralIdentifiability.jl#536 is the focused draft fix and its hosted matrix is green.
  10. 1 ModelingToolkitBase downgrade failure. Require Dual-safe BoundaryValueDiffEqMIRK #4853 is the focused MIRK floor correction. Exact clean bad/fixed controls and the full local InterfaceII group pass with the correction. Its hosted downgrade job reached the formerly failing target at 22 pass / 6 broken, then was later terminated by a self-hosted runner shutdown while entering QA precompile; the endpoint annotation is cancellation, not a new assertion failure. The independent covered NeuralPDE failure exposed on that matrix is tracked with an exact clean-master reproducer at NNPDE1 third-order ODE test is coverage-sensitive NeuralPDE.jl#1095.

So the answer to “are all independent follow-ups merged?” is no. The Catalyst/PDEBase/MethodOfLines source and registration pieces are merged; the focused RGF, MTK QA/downgrade, MOR, SBML, StructuralIdentifiability, OrdinaryDiffEq, General compat, and related release steps remain open as listed above.

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.

2 participants