Don't embed initial-guess object types in collocation caches (fix 20x compile-time regression)#511
Conversation
`const g = 9.81` inside a `@testset` block is a local-scope `const`, which is a lowering error on all supported Julia versions. The error aborts inclusion of mirk_basic_tests.jl at that expression, so this testset and everything after it never ran (the sublibrary CI has been path-filtered around it since the test reorganization). Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Fixes the compile-time regression reported in SciML#500: a solve whose u0 is a previous solution (or any AbstractVectorOfArray) took ~20x longer than v5.21.0, with @time reporting ~100% compilation on every fresh session. Under RecursiveArrayTools v4 (SciMLBase v3 stack), `AbstractVectorOfArray <: AbstractArray`, so the `!(prob.u0 isa AbstractArray)` gate in `SciMLBase.__init` stopped remaking the problem with the extracted plain-vector u0. The full ODESolution type (~80k characters, including the previous solve's entire cache type) stayed embedded in `cache.prob`, and every closure, DI jacobian preparation, and NonlinearSolve specialization compiled against it. Gate on `AbstractVectorOfArray` explicitly in MIRK, FIRK (expand + nested), and MIRKN so initial-guess objects are always stripped to the extracted u0 vector, restoring the RAT v3-era behavior. Timings for the issue's MRE (Julia 1.12.6, second solve with solution-object u0, fresh session): - BoundaryValueDiffEqMIRK 1.17.0 released: 272.9 s (100% compilation) - with this patch: 18.0 s - BoundaryValueDiffEq 5.21.0 baseline: 20.2 s Adds regression tests asserting `cache.prob.u0 isa Vector{Float64}` when u0 is an ODESolution or VectorOfArray. Fixes SciML#500 Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Benchmark Results (Julia v1.11)Time benchmarks
Memory benchmarks
|
CI triage (first run, 27257879703 et al.)This PR is the first to exercise the sublibrary CI matrix since the grouped-tests reorganization (master pushes were path-filtered, 1. Self-hosted runner outage (~07:15–07:40 UTC): 2. Coverage step bug in the shared workflows (all sublibrary jobs): jobs that did run die at the 3. Real test results that did complete:
Local ground truth for this PR (Julia 1.12.6): full |
Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Note
This PR should be ignored until reviewed by @ChrisRackauckas.
Fixes #500.
Problem
Since the SciMLBase v3 / RecursiveArrayTools v4 ecosystem update (BoundaryValueDiffEq v5.23.0), solving a BVP whose
u0is a previous solution (the standard warm-start pattern viaremake(prob, u0 = sol)) became ~20x slower, with@timereporting ~100% compilation on every fresh session.Root cause
All collocation
__initmethods stored the problem in the cache viaUnder RAT v3 / SciMLBase v2,
ODESolutionwas not anAbstractArray, so the cache got a problem remade with the extracted plain-vectoru0. Under RAT v4,AbstractVectorOfArray <: AbstractArray(andODESolution <: AbstractVectorOfArray), so the gate flipped: the full solution type stayed embedded incache.prob. Measured with the issue's MRE,typeof(cache.prob.u0)went fromVector{Float64}(15 chars) to the entire previous solution type (~79,000 chars, which itself nests the previous solve's whole cache type). Every loss closure, DI jacobian preparation, and NonlinearSolve specialization then compiles against that type.Fix
Gate on
AbstractVectorOfArrayexplicitly in MIRK, FIRK (expand + nested), and MIRKN, so initial-guess objects (previous solutions,VectorOfArray/DiffEqArray, functions) are always stripped to the extractedu0vector — i.e. restore the exact RAT v3-era semantics. (Ascher never had the remake and is unchanged; it predates this regression.)Timings (issue MRE, Julia 1.12.6, second solve with solution-object u0, fresh session)
Identical numerical results in all configurations (
sol2.u[1] = [100.0, 3.6e-17, 0.000428...]).Tests
init(prob, alg, ...).prob.u0 isa Vector{Float64}whenu0is anODESolutionorVectorOfArray, plus successful solves. These are deterministic type checks, not timing tests.const g = 9.81inside the MIRK "Compatibility with StaticArrays"@testset— a local-scopeconst, which is a lowering error that aborted inclusion ofmirk_basic_tests.jlat that expression, silently skipping that testset and everything after it. Latent on master; surfaced because the new regression test (appended after it) never ran until it was fixed. (Sublibrary CI has been path-filtering around these tests.)Local verification (Julia 1.12.6)
Pkg.test("BoundaryValueDiffEqMIRK"): all green — Basic 200/200 (incl. previously-dark testsets after theconstfix), NLLS 72/72, Ensemble 10/10, AD 9/9, Singular 8/8, VectorOfVector 2/2, DynOpt 2/2, QA 11/11.Pkg.test("BoundaryValueDiffEqMIRKN"): all green (25/25 + QA 11/11).Pkg.test("BoundaryValueDiffEqFIRK"): new regression testset 4/4; suite shows 2 fail + 3 "Unexpected Pass" in pre-existing "Convergence on Linear" stage-4/5 checks for problems 9/10 — dependency-stack drift unrelated to this PR (these run a plainVector{Float64}u0 path this PR provably does not touch); documented in Misc test group red on master: BigFloat BVP solves stall — regression from LinearSolve v3.85 nonstructural-zeros reduction #509 (comment).Process notes
Investigation: reproduced the issue MRE against pinned v5.21.0 / v5.23.0 environments, measured
typeof(cache.prob.u0)string lengths to locate the type-nesting blowup, confirmedODESolution <: AbstractArrayflipped false→true across the RAT v3→v4 boundary while<: AbstractVectorOfArrayis true on both, then patched the four gate sites and re-verified the MRE end-to-end. Pre-existing master CI failures found along the way (Misc/Wrappers/format/Downgrade groups) were handled separately: PRs #507, #508, #510 and issues #509 + SciML/LinearSolve.jl#1027.🤖 Generated with Claude Code