Skip to content

Migrate to SciMLBase v3 / RecursiveArrayTools v4 / DiffEqBase v7#293

Merged
ChrisRackauckas merged 3 commits into
SciML:masterfrom
ChrisRackauckas-Claude:feature/scimlbase-v3-rat-v4-migration
Apr 24, 2026
Merged

Migrate to SciMLBase v3 / RecursiveArrayTools v4 / DiffEqBase v7#293
ChrisRackauckas merged 3 commits into
SciML:masterfrom
ChrisRackauckas-Claude:feature/scimlbase-v3-rat-v4-migration

Conversation

@ChrisRackauckas-Claude

Copy link
Copy Markdown
Contributor

Summary

This PR supersedes and extends #292 (Dependabot), which only widened the
DiffEqBase compat bound in Project.toml without the accompanying code
migration. Running with DiffEqBase v7 actually pulls in SciMLBase v3 and
RecursiveArrayTools v4, both of which have code-level breaking changes that
affect this package.

Changes grouped by breaking-change category, per the OrdinaryDiffEq v7
NEWS.md migration guide:

RecursiveArrayTools v4 — AbstractVectorOfArray <: AbstractArray

This is the largest source of actual breakage for a loss-function package.

  • length(sol) semantics changed. Under RAT v4, length(sol) is the
    total scalar element count (prod(size(sol))), not the number of
    timesteps. Replaced the four timestep-counting loops in
    L2Loss(::AbstractSciMLSolution) and the final-state iteration in
    L2Loss(::AbstractNoTimeSolution) to compute length(sol.u) once and
    iterate 1:nsteps, matching the previous behavior on both RAT v3 and v4.
  • Iterating an AbstractEnsembleSolution yields scalar elements, not
    trajectories.
    Fixed four call sites in LogLikeLoss and two call sites
    in L2Loss that did for s in sol / [s[i, j] for s in sol], switching
    them to sol.u. This is the forward-compatible form that works under both
    v3 and v4.

2-argument sol[j, i] (component j at time i) is unchanged — it resolves
to sol.u[i][j] under both v3 and v4, so the inner loop bodies in
L2Loss and LogLikeLoss do not need to change.

SciMLBase v3 — OptimizationSolution.minimizer removed

sol.minimizer / sol.minimum on an AbstractOptimizationSolution are
removed in SciMLBase v3; replacements are sol.u / sol.objective. Updated
two @test result.minimizer ≈ … checks in
test/tests_on_odes/regularization_test.jl to use result.u. The other
result.minimizer call sites in the test suite come from
Optim.optimize(...) (returning Optim.OptimizationResults, which keeps
.minimizer) and are unaffected.

Audited-but-unaffected categories

For completeness, the following categories from the v7 migration guide were
checked and do not affect this package:

  • sol.destats / has_destats — zero hits in src/ or test/.
  • DEAlgorithm / DEProblem / DESolution abstract-type renames — zero
    hits. (SciMLBase.AbstractDEProblem is a different, still-exported name
    and is unchanged.)
  • u_modified! / integrator.u_modifiedderivative_discontinuity!
    zero hits.
  • symbol_to_ReturnCode, tuples(), intervals(), IntegratorTuples,
    QuadratureProblem, IntegralProblem nout/batch, prob.lb/prob.ub
    — zero hits.
  • concrete_solve, fastpow, RECOMPILE_BY_DEFAULT, DEStats — zero hits.
  • Ensemble prob_func(prob, i, repeat)prob_func(prob, ctx) — the only
    ensemble handling in the package (STANDARD_PROB_GENERATOR for
    EnsembleProblem in src/DiffEqParamEstim.jl) just forwards the user's
    prob_func and never calls it, so no signature update is required here.
  • Algorithm struct {CS, AD, FDT, ST, CJ} type parameters, chunk_size /
    diff_type / standardtag / precs kwargs, controller kwargs
    (gamma/beta1/beta2/qmin/qmax/...), integrator.EEst — none are
    referenced in src/ or test/ (DiffEqParamEstim never constructs
    solvers directly; it takes alg as an argument and forwards it).
  • Bool → typed-object kwargs (autodiff, verbose, alias, lazy) —
    the only user-visible site is verbose = false/true in test files, which
    is forwarded to the ODE solve inside build_loss_objective. Left
    unchanged — the underlying solve still accepts Bool for verbose in
    the v6-compatible path that CI actually resolves against (see below).
  • construct* tableau functions, Euler/KenCarp/etc. solver exports — no
    non-default solvers are referenced in the runtests.jl-included test
    files. (lorenz_test.jl and lorenz_true_test.jl use Euler() but are
    not included in runtests.jl.)

Project.toml

Ecosystem note — resolution state as of this PR

At the time of this PR, OrdinaryDiffEq on the General registry is still
v6.111.0, which pins DiffEqBase = 6 / SciMLBase = 2 / RAT = 3.
So a fresh CI resolve with the widened compat in this PR still selects:

  • DiffEqBase = 6.x
  • SciMLBase = 2.x
  • RecursiveArrayTools = 3.x

CI therefore exercises the RAT v3 path of the code. The code in this PR
is written to be source-compatible with both RAT v3 and v4 — the migration
shortcuts from the NEWS.md guide are all forward-compatible
(sol.u[i] / for s in sol.u / length(sol.u) work identically on v3),
so once OrdinaryDiffEq v7 is released and the resolver begins picking
DiffEqBase = 7, this package will just work without further changes.

Test plan

  • Audit every category from OrdinaryDiffEq.jl NEWS.md v7 migration
    guide against src/ and test/.
  • using DiffEqParamEstim loads under both the v6 (CI) and v7 (local
    dev) resolutions.
  • Manual verification on RAT v4: VectorOfArray([[1,2],[3,4],[5,6]])
    has length(.) = 6 (scalars), length(.u) = 3 (timesteps), and
    voa[j, i] = component j at time i — confirming my reasoning
    that the 2-arg indexing is unchanged.
  • Full Pkg.test() local run — started under the v7 resolution. Will
    update this PR with the pass/fail result once the precompilation of
    the v7-DEV OrdinaryDiffEq + SciMLSensitivity + ModelingToolkit stack
    finishes locally.

Relation to #292

Closes/supersedes #292. The Project.toml change here includes the
DiffEqBase = "6, 7" widening from #292 plus the companion source changes.

🤖 Generated with Claude Code

ChrisRackauckas and others added 2 commits April 24, 2026 01:36
RecursiveArrayTools v4 makes `AbstractVectorOfArray <: AbstractArray`, which
changes the semantics of several operations on `ODESolution`, `DAESolution`,
and `EnsembleSolution`:

- `length(sol)` is now `prod(size(sol))` (total scalar count) rather than the
  number of timesteps. Replaced with `length(sol.u)` in the timestep-counting
  loops in `L2Loss` and in the ensemble retcode check in `LogLikeLoss`.
- Iterating `sol` on an `AbstractEnsembleSolution` now yields scalar elements,
  not trajectories. Replaced `for s in sol` with `for s in sol.u` in both
  `LogLikeLoss(::AbstractEnsembleSolution)` and the `AbstractSciMLSolution`
  fallback branch.
- For the steady-state `AbstractNoTimeSolution` path, moved to iterating
  `sol.u` (the final-state vector) explicitly rather than via `length(sol)` /
  `sol[i]`, which are now scalar AbstractArray operations on v4.

2-argument indexing `sol[j, i]` is unchanged: on both RAT v3 and v4 it
resolves to `sol.u[i][j]` (component j at timestep i), so the interior loops
do not need to change.

Widen `Project.toml` compat:
- `DiffEqBase = "6, 7"` (was "6")
- `SciMLBase  = "1.69, 2, 3"` (was "1.69, 2, 3.1")
- Bump package version to 2.5.0.

Test-suite fix: `result.minimizer` on a SciMLBase `OptimizationSolution` was
removed in v3; replaced with `result.u` in `regularization_test.jl`. Other
`result.minimizer` references in the test suite come from
`Optim.optimize(...)` (not `solve(optprob, ...)`), which still returns an
`Optim.OptimizationResults` with `.minimizer` and is unaffected.

Supersedes and extends Dependabot PR SciML#292, which only widened the
`DiffEqBase` compat without the accompanying code migration.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Two `L2Loss` branches (the `AbstractNoTimeSolution` and `AbstractSciMLSolution`
methods) still iterated `for s in sol` in the `AbstractEnsembleSolution`
retcode-check. Under RAT v4 that iterates scalar elements, not trajectories.
Switched both to `for s in sol.u`, matching the fix already applied in
`LogLikeLoss`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
@ChrisRackauckas-Claude

Copy link
Copy Markdown
Contributor Author

Local smoke-test of the migrated code (under a resolved v6 test environment — which is what CI will see, since OrdinaryDiffEq is still on v6.111 in the registry):

Single sol: length(sol.u)=11 length(sol)=11
L2Loss(sol)=0.0 (PASS)
Ensemble: length(esol.u)=3
L2Loss(ensemble)=0.0 (PASS)
LogLikeLoss(sol)=-40.54854818061761 (PASS)
All smoke tests PASS

Full Pkg.test() is running — will comment again with the result.

Comment thread src/cost_functions.jl Outdated
Comment thread src/cost_functions.jl Outdated
Comment thread src/cost_functions.jl Outdated
Comment thread src/cost_functions.jl Outdated
Comment thread src/cost_functions.jl Outdated
Comment thread src/cost_functions.jl Outdated
Co-authored-by: Christopher Rackauckas <accounts@chrisrackauckas.com>
@ChrisRackauckas ChrisRackauckas merged commit fd2ae90 into SciML:master Apr 24, 2026
1 of 7 checks passed
@ChrisRackauckas-Claude

Copy link
Copy Markdown
Contributor Author

Local verification status

Direct-API smoke tests PASS (reproduced above): L2Loss(single_sol), L2Loss(ensemble), and LogLikeLoss(single_sol) all produce the expected results with the migrated code, on a resolved test env (DiffEqBase 6.218, SciMLBase 2.155, RAT 3.54, OrdinaryDiffEq 6.111).

Pkg.test() local run: Precompilation of the test environment completed successfully (62 deps in 1148s), but the run then got stuck in a ParameterizedFunctions / DiffEqBase → DiffEqBaseEnzymeExt "stale cache" recompile-and-retry loop — this is an unrelated Julia 1.12 precompile-invalidation issue independent of this PR (the precompile job re-runs OrdinaryDiffEqSDIRK/FIRK/ModelingToolkitBase repeatedly without progress after the initial success). Will rely on CI for a clean-room test result.

Why this PR's source is correct even if I couldn't finish the local full run

All four code changes are forward-only refactors from an already-working v6/RAT-v3 form to a form that works identically under both v3 and v4:

  • length(sol)length(sol.u) — same value on v3, correct on v4.
  • for s in sol (ensemble) → for s in sol.u — same iteration on v3, correct on v4.
  • result.minimizerresult.u.minimizer was always a deprecated alias that returns .u.
  • sol[j, i] (2-arg) unchanged — already the correct form on both v3 and v4.

Nothing in the PR removes v3-era compatibility or introduces v4-only syntax.

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