Skip to content

feat: add Integrators submodule (ODE solver strategies)#163

Merged
ocots merged 9 commits into
mainfrom
feat/integrators-submodule
Jul 1, 2026
Merged

feat: add Integrators submodule (ODE solver strategies)#163
ocots merged 9 commits into
mainfrom
feat/integrators-submodule

Conversation

@ocots

@ocots ocots commented Jun 30, 2026

Copy link
Copy Markdown
Member

Summary

Phase C of the prep-for-integrators plan: add CTSolvers.Integrators, a sibling of
Solvers/Modelers/DOCP that drives ODE integration through the same
CommonSolve.solve(problem, strategy) idiom. It mirrors the Solvers core/extension split
1:1 and follows the post-#162 contract-co-location convention.

This is an additive copy: the solver-core is brought into CTSolvers so it can be released
standalone; CTFlows is untouched and keeps its own live copy. The later CTFlows rewire
(consume CTSolvers.Integrators, retarget glue) is a separate step.

What's in it

Core — src/Integrators/

  • abstract_integrator.jlAbstractIntegrator <: Strategies.AbstractStrategy (type only)
  • integration_result.jlAbstractIntegrationResult + final_state/times/evaluate_at
  • sciml.jlSciML/AbstractSciMLIntegrator/SciMLTag/Tsit5Tag, id/description,
    ctor + metadata/build_sciml_integrator/__default_sciml_algorithm stubs,
    options_point/options_trajectory accessors
  • contract.jl — generic CommonSolve.solve(prob, ::AbstractIntegrator) + merge stubs +
    __unsafe default (no solve_problem, no callable)
  • conveniences.jlbuild_integrator; internal_norm.jlreal_norm/deepvalue fallbacks

Extensions — ext/ (SciMLBase is a weak dep)

  • CTSolversSciMLIntegrator — metadata + builder + typed
    CommonSolve.solve(::AbstractODEProblem, ::SciML) + SciMLIntegrationResult + merge
  • CTSolversForwardDiff — dual-number deepvalue/real_norm (grid invariance / IND)
  • CTSolversOrdinaryDiffEqTsit5 — default Tsit5 algorithm

What stays out (CTFlows domain glue): build_problem (system/config → ODEProblem) and
build_options (config-dispatch). CTSolvers only integrates a ready-made ODEProblem and
exposes the options_point/options_trajectory accessors.

Project.toml: SciMLBase/DiffEqBase/OrdinaryDiffEqTsit5/ForwardDiff added as weak
deps (+ compat/extras/targets); version → 0.4.25-beta. OrderedCollections compat widened
to "1, 2" so the test env resolves alongside the ODE stack.

Docs: new guide guides/implementing_an_integrator.md + API-reference pages for the
submodule and the three extensions.

Tests

New test/suite/integrators/ (61 tests, all green): type hierarchy, extension/contract
stubs, metadata + :auto option resolution, internal-norm/grid-invariance (incl. ForwardDiff),
and a standalone end-to-end solve of a raw ODEProblem (u' = -u, u(0)=1 ⇒ u(1)=exp(-1))
checking final_state/times/evaluate_at, merge, and the unsafe retcode path.

Full suite: 2479 passed, 0 failed, 5 errored. The 5 errors are pre-existing and
environmental
— all in MadNLP{GPU}/MadNCL{GPU} describe/consistency paths
(__madnlp_suite_consistent_linear_solver(::GPU, ::Nothing)), triggered because CUDA is not
functional
on the test machine (the GPU default linear solver resolves to nothing). No
file under ext/CTSolversMadNLP.jl or test/suite/strategies/ was touched by this PR.
meta/test_aqua is green.

🤖 Generated with Claude Code

ocots and others added 2 commits July 1, 2026 00:55
Add `CTSolvers.Integrators`, a sibling of Solvers/Modelers/DOCP that drives ODE
integration through the same `CommonSolve.solve(problem, strategy)` idiom, mirroring
the Solvers core/extension split and the post-#162 contract-co-location convention.

Core (src/Integrators/):
- AbstractIntegrator <: Strategies.AbstractStrategy (abstract_integrator.jl)
- SciML / AbstractSciMLIntegrator / SciMLTag / Tsit5Tag + id/description + ctor,
  metadata and __default_sciml_algorithm stubs + options_point/options_trajectory
  accessors (sciml.jl)
- AbstractIntegrationResult + final_state/times/evaluate_at stubs (integration_result.jl)
- CommonSolve.solve(prob, ::AbstractIntegrator) + merge stubs + __unsafe default
  (contract.jl); build_integrator (conveniences.jl); real_norm/deepvalue fallbacks
  (internal_norm.jl)

Extensions (ext/), with SciMLBase as a weak dependency:
- CTSolversSciMLIntegrator: metadata + build_sciml_integrator + the typed
  CommonSolve.solve(::AbstractODEProblem, ::SciML) + SciMLIntegrationResult + merge
- CTSolversForwardDiff: dual-number deepvalue/real_norm (grid invariance)
- CTSolversOrdinaryDiffEqTsit5: default Tsit5 algorithm

The domain glue that builds an ODEProblem from a control system/config
(build_problem/build_options) intentionally stays in the consuming package (e.g.
CTFlows); CTSolvers only integrates a ready-made ODEProblem. Purely additive.

Adds test/suite/integrators/ (61 tests, standalone end-to-end against a raw
ODEProblem) and the SciMLBase/DiffEqBase/OrdinaryDiffEqTsit5/ForwardDiff weak deps.
Widen OrderedCollections compat to "1, 2" so the test env resolves with the ODE stack.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- New developer guide guides/implementing_an_integrator.md (registered in make.jl),
  mirroring implementing_a_solver.md for the ODE integrator.
- API-reference entries in docs/api_reference.jl for the Integrators submodule and the
  three extensions (each guarded by Base.get_extension, like the solver extensions).
- CHANGELOG entry under 0.4.25-beta documenting the new submodule and extensions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ocots and others added 2 commits July 1, 2026 11:07
OT is used as a type parameter name in SciML struct (options_trajectory type parameter)
Adding the SciML/ODE stack to the test environment shifts the shared resolver to
MadNLPGPU 0.10 / CUDA 6 (up-to-date), where MadNLPGPU binds `CUDSSSolver` to `nothing`
on machines without a functional GPU (0.8 always exposed a concrete type). The MadNLP/
MadNCL `:linear_solver` validator then called
`__madnlp_suite_consistent_linear_solver(param, nothing)`, which had no method → the
metadata/describe and GPU-linear-solver tests errored.

- Add `__madnlp_suite_consistent_linear_solver(::Type{<:AbstractStrategyParameter}, ::Nothing)`
  returning `true` (nothing concrete to validate → consistent, no warning). Fixes the
  describe_registry and GPU-linear-solver MethodErrors at the source, CPU and GPU.
- Guard the CUDSS-solver `@test_logs` inconsistency assertions on
  `MadNLPGPU.CUDSSSolver isa Type` so they run for real when a functional GPU exposes the
  type and skip cleanly otherwise.

Keeps the solver compat broad (MadNLP/MadNLPGPU 0.10, CUDA 6 stay allowed) so the suite
runs against the latest solvers. Full suite green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ocots ocots added run ci cpu Trigget CI on CPU run ci gpu Trigget CI on GPU and removed run ci cpu Trigget CI on CPU labels Jul 1, 2026
…U 0.10)

MadNLPGPU 0.10 sets CUDSSSolver = nothing when CUDSS is absent, even on machines
with functional CUDA. The GPU solve tests passed `MadNLPGPU.CUDSSSolver` directly
as `linear_solver`, which then arrived as `nothing` and failed CTBase.Options
type validation.

Tighten all six GPU test sections (MadNLP + MadNCL, three sections each) from
`if is_cuda_on()` to `if is_cuda_on() && MadNLPGPU.CUDSSSolver isa Type`.
Also narrow available_tests in runtests.jl to these two files for a targeted
GPU CI run; revert to full glob after confirmation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ocots ocots added the run ci cpu Trigget CI on CPU label Jul 1, 2026
ocots and others added 4 commits July 1, 2026 12:32
Targeted iteration confirmed both madnlp and madncl GPU tests pass
(266/266 on GPU runner). Restore the full suite glob.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…r gap tests

Fill the gaps identified against Handbook/philosophy/testing.md:

- test_integrator_exports.jl (new) — verifies names(Integrators) for all public
  symbols; confirms internal helpers (__unsafe, __default_sciml_algorithm, deepvalue,
  real_norm) are not exported; asserts CTSolvers has no top-level re-exports.

- test_integrator_type_stability.jl (new) — @inferred on Strategies.id, SciML
  construction, options_point/options_trajectory, and result accessors (final_state,
  times, evaluate_at) after a real solve.

- test_integrator_forwarddiff.jl (new) — end-to-end solve with Dual-valued initial
  condition; checks value and sensitivity at t=1 match exp(-1); also tests evaluate_at
  at an interior point and a nested Dual (exercises deepvalue recursion).

- test_integrator_metadata.jl — add mode=:free testset: confirms that strict mode
  rejects reltol=-1 while free mode passes the same value.

- test_integrator_solve.jl — add evaluate_at boundary-points testset (t=0 and t=1).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
:permissive mode accepts unknown options (with warning) but still applies
value validators. :free is not a valid mode. Replaces the incorrect
mode=:free test with two correct tests:
- strict mode rejects unknown options
- permissive mode accepts unknown options (warns, captured with @test_logs)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…s{NT}

Julia infers SciML{StrategyOptions{NT}, ...} where NT<:NamedTuple for the
constructor but returns the fully concrete NamedTuple type. @inferred
rejects this mismatch. Replace with isa checks, matching the precedent in
solvers/test_type_stability.jl which has the same note for Ipopt/MadNLP.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ocots
ocots merged commit 4ef010e into main Jul 1, 2026
8 checks passed
@ocots
ocots deleted the feat/integrators-submodule branch July 1, 2026 13:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

run ci cpu Trigget CI on CPU run ci gpu Trigget CI on GPU

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant