Skip to content

CompatHelper: bump compat for SciMLBase to 3, (keep existing compat)#194

Open
github-actions[bot] wants to merge 5 commits intomainfrom
compathelper/new_version/2026-04-09-00-38-48-645-02219475484
Open

CompatHelper: bump compat for SciMLBase to 3, (keep existing compat)#194
github-actions[bot] wants to merge 5 commits intomainfrom
compathelper/new_version/2026-04-09-00-38-48-645-02219475484

Conversation

@github-actions
Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot commented Apr 9, 2026

This pull request changes the compat entry for the SciMLBase package from 2.128 to 2.128, 3.
This keeps the compat entries for earlier versions.

Note: I have not tested your package with this new compat entry.
It is your responsibility to make sure that your package tests pass before you merge this pull request.

Closes #195, closes #196, closes #197, closes #198, closes #199, closes #201, closes #202, closes #203, closes #204, closes #205, closes #207, closes #208, closes #209, closes #210.

@SKopecz SKopecz force-pushed the compathelper/new_version/2026-04-09-00-38-48-645-02219475484 branch from 1217f08 to 7cbea1a Compare April 9, 2026 00:38
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 9, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@coveralls
Copy link
Copy Markdown

Coverage Report for CI Build 24166208798

Coverage remained the same at 97.305%

Details

  • Coverage remained the same as the base build.
  • Patch coverage: No coverable lines changed in this PR.
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 1707
Covered Lines: 1661
Line Coverage: 97.31%
Coverage Strength: 112142415.18 hits per line

💛 - Coveralls

Copy link
Copy Markdown
Member

@JoshuaLampert JoshuaLampert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to wait for other upstream packages to update their compat bounds (especially OrdinaryDiffEq*.jl packages).

@JoshuaLampert
Copy link
Copy Markdown
Member

The remaining errors look like a bug in OrdinaryDiffEqCore.jl to me. @ChrisRackauckas, in https://github.com/SciML/OrdinaryDiffEq.jl/blob/3a72e5e213ee1bdb8d731361b192f0c9accf05ee/lib/OrdinaryDiffEqCore/src/integrators/integrator_utils.jl#L71 qmin is accessed via integrator.opts.qmin, which is the old interface and was removed in v7. Looking at https://github.com/search?q=repo%3ASciML%2FOrdinaryDiffEq.jl%20integrator.opts.qmin&type=code also shows two more occurrences, they are in docstrings though (should still be fixed, I guess).

@ChrisRackauckas
Copy link
Copy Markdown
Contributor

Yup thanks, looks like the isoutofdomain code path must've been missed on this.

ChrisRackauckas-Claude pushed a commit to ChrisRackauckas-Claude/OrdinaryDiffEq.jl that referenced this pull request Apr 27, 2026
`qmin` (alongside `qmax`, `gamma`, `beta1/beta2`, `qsteady_*`,
`qoldinit`) moved off `DEOptions` and onto the controller object in v7.
The out-of-domain rejection path in `handle_step_rejection!` was still
reaching for the old `integrator.opts.qmin`, which throws on the v7
`DEOptions` struct — only the legacy DelayDiffEq constructor still
mentions it.

Surface this through the controller interface rather than a one-off
`hasfield` walk: a new `get_qmin(integrator)` (with implementations
dispatched on each concrete controller cache) returns the minimum
step-size shrinkage factor used by the integrator's controller.
`handle_step_rejection!` calls it instead of `integrator.opts.qmin`.

Per-cache implementations:

- `IControllerCache`, `PIControllerCache`, `PredictiveControllerCache`
  return their controller's `qmin` field.
- `PIDControllerCache` has no `qmin` (it limits dt via `limiter` +
  `accept_safety` instead) and returns the historical default `1 // 5`
  so the out-of-domain shrink path still has something to multiply by.
- `DummyControllerCache` (BDF / Nordsieck — algorithms that own the
  step-size logic) reads `integrator.alg.qmin` if present, else falls
  back to the historical default.
- `CompositeControllerCache` delegates to the currently active
  sub-cache, mirroring how `stepsize_controller!` and friends dispatch.

Refresh the `PredictiveController` docstring (which still showed the
old `integrator.opts.qmin/qmax/qsteady_*/gamma` interface) to match the
actual v7 implementation that destructures from `cache.controller`,
and update two stale `# equivalent to integrator.opts.gamma` comments
in `lib/OrdinaryDiffEqBDF/src/controllers.jl`.

Reported in
NumericalMathematics/PositiveIntegrators.jl#194 (comment)

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
ChrisRackauckas-Claude pushed a commit to ChrisRackauckas-Claude/OrdinaryDiffEq.jl that referenced this pull request Apr 27, 2026
… for BDF/JVODE

In v7, `qmin` (alongside `qmax`, `gamma`, `beta1/beta2`, `qsteady_*`,
`qoldinit`) moved off `DEOptions` and onto the controller object. The
out-of-domain rejection path in `handle_step_rejection!` was still
reaching for the old `integrator.opts.qmin`, which throws on the v7
`DEOptions` struct — only the legacy DelayDiffEq constructor still
mentions it. The same was true of `integrator.opts.failfactor` in
`post_newton_controller!`.

Reported in
NumericalMathematics/PositiveIntegrators.jl#194 (comment)

Rather than papering over with a one-off `hasfield` walk, this lifts
the standard step-size knobs into a composable building block and
retires the `DummyController` workaround that BDF / Nordsieck were
using to keep the knobs on their algorithm structs:

## `BasicController` + accessors

A new `BasicController` struct holds `qmin`, `qmax`, `qmax_first_step`,
`gamma`, `qsteady_min`, `qsteady_max`, `failfactor` — the seven scalars
the integrator-level paths actually read. All fields default to
`nothing`; algorithm-specific defaults are filled in by `resolve_basic`
at `setup_controller_cache` time. Concrete controllers
(`IController`, `PIController`, `PIDController`, `PredictiveController`,
`ExtrapolationController`, `KantorovichTypeController`, plus the new
`BDFController` and `JVODEController`) all embed a `BasicController` as
`controller.basic`.

Seven generic accessors — `get_qmin`, `get_qmax`, `get_qmax_first_step`,
`get_gamma`, `get_qsteady_min`, `get_qsteady_max`, `get_failfactor` —
dispatch on `cache::AbstractControllerCache` and read through
`cache.controller.basic`. `CompositeControllerCache` overrides each one
to delegate to the active sub-cache (mirroring how
`stepsize_controller!` already dispatched). `DummyControllerCache` keeps
its alg-field fallback for any SDE algorithms still using it.

## `handle_step_rejection!` / `post_newton_controller!`

`integrator.opts.qmin` → `get_qmin(integrator)`,
`integrator.opts.failfactor` → `get_failfactor(integrator)`. Same in
the BDF post-Newton paths.

## `BDFController` (replaces `DummyController` for QNDF / FBDF / DFBDF)

QNDF/FBDF/DFBDF used to keep `qmax`, `qsteady_min`, `qsteady_max` as
fields on the algorithm struct itself, with a `DummyController`
hard-wired into `default_controller`. The stepsize logic read
`alg.qmax` / `alg.qsteady_min` / `alg.qsteady_max` directly, so the
controller surface was unsettable.

`BDFController` embeds `BasicController` and has a cache that delegates
back to alg-level dispatch (the existing BDF order-selection logic is
left intact). `default_controller(QT, alg::Union{QNDF, FBDF, DFBDF})`
threads `alg.qmax` / `alg.qsteady_min` / `alg.qsteady_max` through to
the controller, so existing usage like `QNDF(qmax = 20)` keeps working,
but users can now also pass `controller = BDFController(qmin = …, gamma = …)`
to set knobs that previously had no surface (and weren't read by the
BDF logic, which still gets default behavior). BDF-tuned per-algorithm
defaults (`qmax = 5//1`, `qsteady_min = 9//10`, `qsteady_max = 12//10`)
are encoded as `qmax_default(::QNDF)` etc.

## `JVODEController` (replaces `DummyController` for Nordsieck JVODE)

Same pattern. `setη!` / `chooseη!` / `step_accept_controller!(::JVODE)`
now read `get_qmin(integrator)` / `get_qmax(integrator)` /
`get_qsteady_*(integrator)` instead of `alg.qmin` etc.

## Other refactors for consistency

- `IController` / `PIController` / `PredictiveController` /
  `PIDController` shed their flat `qmin/qmax/...` fields and embed
  `BasicController` instead. PI-specific knobs (`beta1`, `beta2`,
  `qoldinit`) and PID-specific knobs (`beta`, `accept_safety`,
  `limiter`) stay on the controller alongside `basic`.
- `ExtrapolationController` and `KantorovichTypeController` likewise
  embed `BasicController` so the accessors work uniformly.

## Verification

Reproducer (`isoutofdomain` predicate that fires once on the first
proposed step) plus a smoke test of every controller path (default
`solve`, user-supplied `BDFController`, BasicController construction,
controller-composition invariants) — 21/21 pass on Julia 1.12.

- On master (without this fix): all algorithms error out — accessing
  `integrator.opts.qmin` throws because the v7 `DEOptions` struct
  doesn't have the field.
- With this fix: `Tsit5` / `Vern7` / `Rosenbrock23` / `FBDF` / `QNDF`
  all complete the isout-rejection problem successfully, and
  `BDFController(qmax = 3)` is honored end-to-end.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
ChrisRackauckas-Claude pushed a commit to ChrisRackauckas-Claude/OrdinaryDiffEq.jl that referenced this pull request Apr 28, 2026
…ntroller for BDF/JVODE

In v7, `qmin` (alongside `qmax`, `gamma`, `beta1/beta2`, `qsteady_*`,
`qoldinit`) moved off `DEOptions` and onto the controller object. The
out-of-domain rejection path in `handle_step_rejection!` was still
reaching for the old `integrator.opts.qmin`, which throws on the v7
`DEOptions` struct — only the legacy DelayDiffEq constructor still
mentions it. The same was true of `integrator.opts.failfactor` in
`post_newton_controller!`.

Reported in
NumericalMathematics/PositiveIntegrators.jl#194 (comment)

Rather than papering over with a one-off `hasfield` walk, this lifts
the standard step-size knobs into a composable building block and
retires the `DummyController` workaround that BDF / Nordsieck were
using to keep the knobs on their algorithm structs.

## `CommonControllerOptions` + accessors

A new `CommonControllerOptions{T}` struct holds `qmin`, `qmax`,
`qmax_first_step`, `gamma`, `qsteady_min`, `qsteady_max`, `failfactor`
— the seven scalars the integrator-level paths actually read. A single
type parameter `T` keeps the type signatures simple even if more knobs
are added later. All fields default to `nothing`; algorithm-specific
defaults are filled in by `resolve_basic` at `setup_controller_cache`
time. Concrete controllers (`IController`, `PIController`,
`PIDController`, `PredictiveController`, `ExtrapolationController`,
`KantorovichTypeController`, plus the new `BDFController` and
`JVODEController`) all embed a `CommonControllerOptions` as
`controller.basic`.

Seven generic accessors — `get_qmin`, `get_qmax`, `get_qmax_first_step`,
`get_gamma`, `get_qsteady_min`, `get_qsteady_max`, `get_failfactor` —
dispatch on `cache::AbstractControllerCache` and read through
`cache.controller.basic`. `CompositeControllerCache` overrides each one
to delegate to the active sub-cache. `DummyControllerCache` keeps an
alg-field fallback for any SDE algorithm still using it.

## `handle_step_rejection!` / `post_newton_controller!`

`integrator.opts.qmin` → `get_qmin(integrator)`,
`integrator.opts.failfactor` → `get_failfactor(integrator)`. Same in
the BDF post-Newton paths.

## `BDFController` (replaces `DummyController` for QNDF / FBDF / DFBDF)

QNDF/FBDF/DFBDF used to keep `qmax`, `qsteady_min`, `qsteady_max` as
fields on the algorithm struct itself, with a `DummyController`
hard-wired into `default_controller`. The stepsize logic read
`alg.qmax` / `alg.qsteady_min` / `alg.qsteady_max` directly, plus a
hard-coded `zₛ = 1.2` magic-number gamma, so the controller surface was
unsettable.

`BDFController` embeds `CommonControllerOptions` and has a cache that
delegates back to alg-level dispatch (the existing BDF order-selection
logic is left intact). The hard-coded `zₛ = 1.2` is now
`get_gamma(integrator)`. `default_controller(QT, alg::Union{QNDF, FBDF, DFBDF})`
threads `alg.qmax` / `alg.qsteady_min` / `alg.qsteady_max` through to
the controller, so existing usage like `QNDF(qmax = 20)` keeps working.
Users can now also pass `controller = BDFController(qmin = …, gamma = …)`
to set knobs that previously had no surface (incl. `qmin` and `gamma`).
BDF-tuned per-algorithm defaults (`qmax = 5//1`, `qsteady_min = 9//10`,
`qsteady_max = 12//10`, `gamma = 12//10`) are encoded as
`qmax_default(::QNDF)` / `gamma_default(::QNDF)` etc.

## `JVODEController` (replaces `DummyController` for Nordsieck JVODE)

Same pattern. `setη!` / `chooseη!` / `step_accept_controller!(::JVODE)`
now read `get_qmin(integrator)` / `get_qmax(integrator)` /
`get_qsteady_*(integrator)` instead of `alg.qmin` etc.

## Other refactors for consistency

- `IController` / `PIController` / `PredictiveController` /
  `PIDController` shed their flat `qmin/qmax/...` fields and embed
  `CommonControllerOptions` instead. PI-specific knobs (`beta1`,
  `beta2`, `qoldinit`) and PID-specific knobs (`beta`, `accept_safety`,
  `limiter`) stay on the controller alongside `basic`.
- `ExtrapolationController` and `KantorovichTypeController` likewise
  embed `CommonControllerOptions`. Their stepsize logic reads
  `get_qmax(integrator)` / `get_qmin(integrator)` rather than direct
  field access.

## Test orchestration

`test/runtests.jl` walks transitive `[sources]` dependencies and
`Pkg.develop`s them. Pre-seed the `developed` set with the active
project so a `[sources]` entry that points back to it (e.g. via the
umbrella `OrdinaryDiffEq`'s transitive sources) is skipped — `Pkg.develop`
cannot develop the active project itself, and that error was the
"package X has the same name or UUID as the active project" failure
across the sublibrary CI matrix.

## Verification

Reproducer (`isoutofdomain` predicate that fires once on the first
proposed step) plus a smoke test of every controller path (default
`solve`, user-supplied `BDFController`, `CommonControllerOptions`
construction, controller-composition invariants) — 21/21 pass on Julia
1.12.

- On master (without this fix): all algorithms error out — accessing
  `integrator.opts.qmin` throws because the v7 `DEOptions` struct
  doesn't have the field.
- With this fix: `Tsit5` / `Vern7` / `Rosenbrock23` / `FBDF` / `QNDF`
  all complete the isout-rejection problem successfully, and
  `BDFController(qmax = 3)` is honored end-to-end.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
ChrisRackauckas-Claude pushed a commit to ChrisRackauckas-Claude/OrdinaryDiffEq.jl that referenced this pull request Apr 28, 2026
…ntroller for BDF/JVODE

In v7, `qmin` (alongside `qmax`, `gamma`, `beta1/beta2`, `qsteady_*`,
`qoldinit`) moved off `DEOptions` and onto the controller object. The
out-of-domain rejection path in `handle_step_rejection!` was still
reaching for the old `integrator.opts.qmin`, which throws on the v7
`DEOptions` struct — only the legacy DelayDiffEq constructor still
mentions it. The same was true of `integrator.opts.failfactor` in
`post_newton_controller!`.

Reported in
NumericalMathematics/PositiveIntegrators.jl#194 (comment)

Rather than papering over with a one-off `hasfield` walk, this lifts
the standard step-size knobs into a composable building block and
retires the `DummyController` workaround that BDF / Nordsieck were
using to keep the knobs on their algorithm structs.

A new `CommonControllerOptions{T}` struct holds `qmin`, `qmax`,
`qmax_first_step`, `gamma`, `qsteady_min`, `qsteady_max`, `failfactor`
— the seven scalars the integrator-level paths actually read. A single
type parameter `T` keeps the type signatures simple even if more knobs
are added later. All fields default to `nothing`; algorithm-specific
defaults are filled in by `resolve_basic` at `setup_controller_cache`
time. Concrete controllers (`IController`, `PIController`,
`PIDController`, `PredictiveController`, `ExtrapolationController`,
`KantorovichTypeController`, plus the new `BDFController` and
`JVODEController`) all embed a `CommonControllerOptions` as
`controller.basic`.

Seven generic accessors — `get_qmin`, `get_qmax`, `get_qmax_first_step`,
`get_gamma`, `get_qsteady_min`, `get_qsteady_max`, `get_failfactor` —
dispatch on `cache::AbstractControllerCache` and read through
`cache.controller.basic`. `CompositeControllerCache` overrides each one
to delegate to the active sub-cache. `DummyControllerCache` keeps an
alg-field fallback for any SDE algorithm still using it.

`integrator.opts.qmin` → `get_qmin(integrator)`,
`integrator.opts.failfactor` → `get_failfactor(integrator)`. Same in
the BDF post-Newton paths.

QNDF/FBDF/DFBDF used to keep `qmax`, `qsteady_min`, `qsteady_max` as
fields on the algorithm struct itself, with a `DummyController`
hard-wired into `default_controller`. The stepsize logic read
`alg.qmax` / `alg.qsteady_min` / `alg.qsteady_max` directly, plus a
hard-coded `zₛ = 1.2` magic-number gamma, so the controller surface was
unsettable.

`BDFController` embeds `CommonControllerOptions` and has a cache that
delegates back to alg-level dispatch (the existing BDF order-selection
logic is left intact). The hard-coded `zₛ = 1.2` is now
`get_gamma(integrator)`. `default_controller(QT, alg::Union{QNDF, FBDF, DFBDF})`
threads `alg.qmax` / `alg.qsteady_min` / `alg.qsteady_max` through to
the controller, so existing usage like `QNDF(qmax = 20)` keeps working.
Users can now also pass `controller = BDFController(qmin = …, gamma = …)`
to set knobs that previously had no surface (incl. `qmin` and `gamma`).
BDF-tuned per-algorithm defaults (`qmax = 5//1`, `qsteady_min = 9//10`,
`qsteady_max = 12//10`, `gamma = 12//10`) are encoded as
`qmax_default(::QNDF)` / `gamma_default(::QNDF)` etc.

Same pattern. `setη!` / `chooseη!` / `step_accept_controller!(::JVODE)`
now read `get_qmin(integrator)` / `get_qmax(integrator)` /
`get_qsteady_*(integrator)` instead of `alg.qmin` etc.

- `IController` / `PIController` / `PredictiveController` /
  `PIDController` shed their flat `qmin/qmax/...` fields and embed
  `CommonControllerOptions` instead. PI-specific knobs (`beta1`,
  `beta2`, `qoldinit`) and PID-specific knobs (`beta`, `accept_safety`,
  `limiter`) stay on the controller alongside `basic`.
- `ExtrapolationController` and `KantorovichTypeController` likewise
  embed `CommonControllerOptions`. Their stepsize logic reads
  `get_qmax(integrator)` / `get_qmin(integrator)` rather than direct
  field access.

`test/runtests.jl` walks transitive `[sources]` dependencies and
`Pkg.develop`s them. Pre-seed the `developed` set with the active
project so a `[sources]` entry that points back to it (e.g. via the
umbrella `OrdinaryDiffEq`'s transitive sources) is skipped — `Pkg.develop`
cannot develop the active project itself, and that error was the
"package X has the same name or UUID as the active project" failure
across the sublibrary CI matrix.

Reproducer (`isoutofdomain` predicate that fires once on the first
proposed step) plus a smoke test of every controller path (default
`solve`, user-supplied `BDFController`, `CommonControllerOptions`
construction, controller-composition invariants) — 21/21 pass on Julia
1.12.

- On master (without this fix): all algorithms error out — accessing
  `integrator.opts.qmin` throws because the v7 `DEOptions` struct
  doesn't have the field.
- With this fix: `Tsit5` / `Vern7` / `Rosenbrock23` / `FBDF` / `QNDF`
  all complete the isout-rejection problem successfully, and
  `BDFController(qmax = 3)` is honored end-to-end.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
ChrisRackauckas-Claude pushed a commit to ChrisRackauckas-Claude/OrdinaryDiffEq.jl that referenced this pull request Apr 30, 2026
…ntroller for BDF/JVODE

In v7, `qmin` (alongside `qmax`, `gamma`, `beta1/beta2`, `qsteady_*`,
`qoldinit`) moved off `DEOptions` and onto the controller object. The
out-of-domain rejection path in `handle_step_rejection!` was still
reaching for the old `integrator.opts.qmin`, which throws on the v7
`DEOptions` struct — only the legacy DelayDiffEq constructor still
mentions it. The same was true of `integrator.opts.failfactor` in
`post_newton_controller!`.

Reported in
NumericalMathematics/PositiveIntegrators.jl#194 (comment)

Rather than papering over with a one-off `hasfield` walk, this lifts
the standard step-size knobs into a composable building block and
retires the `DummyController` workaround that BDF / Nordsieck were
using to keep the knobs on their algorithm structs.

A new `CommonControllerOptions{T}` struct holds `qmin`, `qmax`,
`qmax_first_step`, `gamma`, `qsteady_min`, `qsteady_max`, `failfactor`
— the seven scalars the integrator-level paths actually read. A single
type parameter `T` keeps the type signatures simple even if more knobs
are added later. All fields default to `nothing`; algorithm-specific
defaults are filled in by `resolve_basic` at `setup_controller_cache`
time. Concrete controllers (`IController`, `PIController`,
`PIDController`, `PredictiveController`, `ExtrapolationController`,
`KantorovichTypeController`, plus the new `BDFController` and
`JVODEController`) all embed a `CommonControllerOptions` as
`controller.basic`.

Seven generic accessors — `get_qmin`, `get_qmax`, `get_qmax_first_step`,
`get_gamma`, `get_qsteady_min`, `get_qsteady_max`, `get_failfactor` —
dispatch on `cache::AbstractControllerCache` and read through
`cache.controller.basic`. `CompositeControllerCache` overrides each one
to delegate to the active sub-cache. `DummyControllerCache` keeps an
alg-field fallback for any SDE algorithm still using it.

`integrator.opts.qmin` → `get_qmin(integrator)`,
`integrator.opts.failfactor` → `get_failfactor(integrator)`. Same in
the BDF post-Newton paths.

QNDF/FBDF/DFBDF used to keep `qmax`, `qsteady_min`, `qsteady_max` as
fields on the algorithm struct itself, with a `DummyController`
hard-wired into `default_controller`. The stepsize logic read
`alg.qmax` / `alg.qsteady_min` / `alg.qsteady_max` directly, plus a
hard-coded `zₛ = 1.2` magic-number gamma, so the controller surface was
unsettable.

`BDFController` embeds `CommonControllerOptions` and has a cache that
delegates back to alg-level dispatch (the existing BDF order-selection
logic is left intact). The hard-coded `zₛ = 1.2` is now
`get_gamma(integrator)`. `default_controller(QT, alg::Union{QNDF, FBDF, DFBDF})`
threads `alg.qmax` / `alg.qsteady_min` / `alg.qsteady_max` through to
the controller, so existing usage like `QNDF(qmax = 20)` keeps working.
Users can now also pass `controller = BDFController(qmin = …, gamma = …)`
to set knobs that previously had no surface (incl. `qmin` and `gamma`).
BDF-tuned per-algorithm defaults (`qmax = 5//1`, `qsteady_min = 9//10`,
`qsteady_max = 12//10`, `gamma = 12//10`) are encoded as
`qmax_default(::QNDF)` / `gamma_default(::QNDF)` etc.

Same pattern. `setη!` / `chooseη!` / `step_accept_controller!(::JVODE)`
now read `get_qmin(integrator)` / `get_qmax(integrator)` /
`get_qsteady_*(integrator)` instead of `alg.qmin` etc.

- `IController` / `PIController` / `PredictiveController` /
  `PIDController` shed their flat `qmin/qmax/...` fields and embed
  `CommonControllerOptions` instead. PI-specific knobs (`beta1`,
  `beta2`, `qoldinit`) and PID-specific knobs (`beta`, `accept_safety`,
  `limiter`) stay on the controller alongside `basic`.
- `ExtrapolationController` and `KantorovichTypeController` likewise
  embed `CommonControllerOptions`. Their stepsize logic reads
  `get_qmax(integrator)` / `get_qmin(integrator)` rather than direct
  field access.

`test/runtests.jl` walks transitive `[sources]` dependencies and
`Pkg.develop`s them. Pre-seed the `developed` set with the active
project so a `[sources]` entry that points back to it (e.g. via the
umbrella `OrdinaryDiffEq`'s transitive sources) is skipped — `Pkg.develop`
cannot develop the active project itself, and that error was the
"package X has the same name or UUID as the active project" failure
across the sublibrary CI matrix.

Reproducer (`isoutofdomain` predicate that fires once on the first
proposed step) plus a smoke test of every controller path (default
`solve`, user-supplied `BDFController`, `CommonControllerOptions`
construction, controller-composition invariants) — 21/21 pass on Julia
1.12.

- On master (without this fix): all algorithms error out — accessing
  `integrator.opts.qmin` throws because the v7 `DEOptions` struct
  doesn't have the field.
- With this fix: `Tsit5` / `Vern7` / `Rosenbrock23` / `FBDF` / `QNDF`
  all complete the isout-rejection problem successfully, and
  `BDFController(qmax = 3)` is honored end-to-end.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
ChrisRackauckas-Claude pushed a commit to ChrisRackauckas-Claude/OrdinaryDiffEq.jl that referenced this pull request Apr 30, 2026
…ntroller for BDF/JVODE

In v7, `qmin` (alongside `qmax`, `gamma`, `beta1/beta2`, `qsteady_*`,
`qoldinit`) moved off `DEOptions` and onto the controller object. The
out-of-domain rejection path in `handle_step_rejection!` was still
reaching for the old `integrator.opts.qmin`, which throws on the v7
`DEOptions` struct — only the legacy DelayDiffEq constructor still
mentions it. The same was true of `integrator.opts.failfactor` in
`post_newton_controller!`.

Reported in
NumericalMathematics/PositiveIntegrators.jl#194 (comment)

Rather than papering over with a one-off `hasfield` walk, this lifts
the standard step-size knobs into a composable building block and
retires the `DummyController` workaround that BDF / Nordsieck were
using to keep the knobs on their algorithm structs.

A new `CommonControllerOptions{T}` struct holds `qmin`, `qmax`,
`qmax_first_step`, `gamma`, `qsteady_min`, `qsteady_max`, `failfactor`
— the seven scalars the integrator-level paths actually read. A single
type parameter `T` keeps the type signatures simple even if more knobs
are added later. All fields default to `nothing`; algorithm-specific
defaults are filled in by `resolve_basic` at `setup_controller_cache`
time. Concrete controllers (`IController`, `PIController`,
`PIDController`, `PredictiveController`, `ExtrapolationController`,
`KantorovichTypeController`, plus the new `BDFController` and
`JVODEController`) all embed a `CommonControllerOptions` as
`controller.basic`.

Seven generic accessors — `get_qmin`, `get_qmax`, `get_qmax_first_step`,
`get_gamma`, `get_qsteady_min`, `get_qsteady_max`, `get_failfactor` —
dispatch on `cache::AbstractControllerCache` and read through
`cache.controller.basic`. `CompositeControllerCache` overrides each one
to delegate to the active sub-cache. `DummyControllerCache` keeps an
alg-field fallback for any SDE algorithm still using it.

`integrator.opts.qmin` → `get_qmin(integrator)`,
`integrator.opts.failfactor` → `get_failfactor(integrator)`. Same in
the BDF post-Newton paths.

QNDF/FBDF/DFBDF used to keep `qmax`, `qsteady_min`, `qsteady_max` as
fields on the algorithm struct itself, with a `DummyController`
hard-wired into `default_controller`. The stepsize logic read
`alg.qmax` / `alg.qsteady_min` / `alg.qsteady_max` directly, plus a
hard-coded `zₛ = 1.2` magic-number gamma, so the controller surface was
unsettable.

`BDFController` embeds `CommonControllerOptions` and has a cache that
delegates back to alg-level dispatch (the existing BDF order-selection
logic is left intact). The hard-coded `zₛ = 1.2` is now
`get_gamma(integrator)`. `default_controller(QT, alg::Union{QNDF, FBDF, DFBDF})`
threads `alg.qmax` / `alg.qsteady_min` / `alg.qsteady_max` through to
the controller, so existing usage like `QNDF(qmax = 20)` keeps working.
Users can now also pass `controller = BDFController(qmin = …, gamma = …)`
to set knobs that previously had no surface (incl. `qmin` and `gamma`).
BDF-tuned per-algorithm defaults (`qmax = 5//1`, `qsteady_min = 9//10`,
`qsteady_max = 12//10`, `gamma = 12//10`) are encoded as
`qmax_default(::QNDF)` / `gamma_default(::QNDF)` etc.

Same pattern. `setη!` / `chooseη!` / `step_accept_controller!(::JVODE)`
now read `get_qmin(integrator)` / `get_qmax(integrator)` /
`get_qsteady_*(integrator)` instead of `alg.qmin` etc.

- `IController` / `PIController` / `PredictiveController` /
  `PIDController` shed their flat `qmin/qmax/...` fields and embed
  `CommonControllerOptions` instead. PI-specific knobs (`beta1`,
  `beta2`, `qoldinit`) and PID-specific knobs (`beta`, `accept_safety`,
  `limiter`) stay on the controller alongside `basic`.
- `ExtrapolationController` and `KantorovichTypeController` likewise
  embed `CommonControllerOptions`. Their stepsize logic reads
  `get_qmax(integrator)` / `get_qmin(integrator)` rather than direct
  field access.

`test/runtests.jl` walks transitive `[sources]` dependencies and
`Pkg.develop`s them. Pre-seed the `developed` set with the active
project so a `[sources]` entry that points back to it (e.g. via the
umbrella `OrdinaryDiffEq`'s transitive sources) is skipped — `Pkg.develop`
cannot develop the active project itself, and that error was the
"package X has the same name or UUID as the active project" failure
across the sublibrary CI matrix.

Reproducer (`isoutofdomain` predicate that fires once on the first
proposed step) plus a smoke test of every controller path (default
`solve`, user-supplied `BDFController`, `CommonControllerOptions`
construction, controller-composition invariants) — 21/21 pass on Julia
1.12.

- On master (without this fix): all algorithms error out — accessing
  `integrator.opts.qmin` throws because the v7 `DEOptions` struct
  doesn't have the field.
- With this fix: `Tsit5` / `Vern7` / `Rosenbrock23` / `FBDF` / `QNDF`
  all complete the isout-rejection problem successfully, and
  `BDFController(qmax = 3)` is honored end-to-end.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
ChrisRackauckas-Claude pushed a commit to ChrisRackauckas-Claude/OrdinaryDiffEq.jl that referenced this pull request Apr 30, 2026
…ntroller for BDF/JVODE

In v7, `qmin` (alongside `qmax`, `gamma`, `beta1/beta2`, `qsteady_*`,
`qoldinit`) moved off `DEOptions` and onto the controller object. The
out-of-domain rejection path in `handle_step_rejection!` was still
reaching for the old `integrator.opts.qmin`, which throws on the v7
`DEOptions` struct — only the legacy DelayDiffEq constructor still
mentions it. The same was true of `integrator.opts.failfactor` in
`post_newton_controller!`.

Reported in
NumericalMathematics/PositiveIntegrators.jl#194 (comment)

Rather than papering over with a one-off `hasfield` walk, this lifts
the standard step-size knobs into a composable building block and
retires the `DummyController` workaround that BDF / Nordsieck were
using to keep the knobs on their algorithm structs.

A new `CommonControllerOptions{T}` struct holds `qmin`, `qmax`,
`qmax_first_step`, `gamma`, `qsteady_min`, `qsteady_max`, `failfactor`
— the seven scalars the integrator-level paths actually read. A single
type parameter `T` keeps the type signatures simple even if more knobs
are added later. All fields default to `nothing`; algorithm-specific
defaults are filled in by `resolve_basic` at `setup_controller_cache`
time. Concrete controllers (`IController`, `PIController`,
`PIDController`, `PredictiveController`, `ExtrapolationController`,
`KantorovichTypeController`, plus the new `BDFController` and
`JVODEController`) all embed a `CommonControllerOptions` as
`controller.basic`.

Seven generic accessors — `get_qmin`, `get_qmax`, `get_qmax_first_step`,
`get_gamma`, `get_qsteady_min`, `get_qsteady_max`, `get_failfactor` —
dispatch on `cache::AbstractControllerCache` and read through
`cache.controller.basic`. `CompositeControllerCache` overrides each one
to delegate to the active sub-cache. `DummyControllerCache` keeps an
alg-field fallback for any SDE algorithm still using it.

`integrator.opts.qmin` → `get_qmin(integrator)`,
`integrator.opts.failfactor` → `get_failfactor(integrator)`. Same in
the BDF post-Newton paths.

QNDF/FBDF/DFBDF used to keep `qmax`, `qsteady_min`, `qsteady_max` as
fields on the algorithm struct itself, with a `DummyController`
hard-wired into `default_controller`. The stepsize logic read
`alg.qmax` / `alg.qsteady_min` / `alg.qsteady_max` directly, plus a
hard-coded `zₛ = 1.2` magic-number gamma, so the controller surface was
unsettable.

`BDFController` embeds `CommonControllerOptions` and has a cache that
delegates back to alg-level dispatch (the existing BDF order-selection
logic is left intact). The hard-coded `zₛ = 1.2` is now
`get_gamma(integrator)`. `default_controller(QT, alg::Union{QNDF, FBDF, DFBDF})`
threads `alg.qmax` / `alg.qsteady_min` / `alg.qsteady_max` through to
the controller, so existing usage like `QNDF(qmax = 20)` keeps working.
Users can now also pass `controller = BDFController(qmin = …, gamma = …)`
to set knobs that previously had no surface (incl. `qmin` and `gamma`).
BDF-tuned per-algorithm defaults (`qmax = 5//1`, `qsteady_min = 9//10`,
`qsteady_max = 12//10`, `gamma = 12//10`) are encoded as
`qmax_default(::QNDF)` / `gamma_default(::QNDF)` etc.

Same pattern. `setη!` / `chooseη!` / `step_accept_controller!(::JVODE)`
now read `get_qmin(integrator)` / `get_qmax(integrator)` /
`get_qsteady_*(integrator)` instead of `alg.qmin` etc.

- `IController` / `PIController` / `PredictiveController` /
  `PIDController` shed their flat `qmin/qmax/...` fields and embed
  `CommonControllerOptions` instead. PI-specific knobs (`beta1`,
  `beta2`, `qoldinit`) and PID-specific knobs (`beta`, `accept_safety`,
  `limiter`) stay on the controller alongside `basic`.
- `ExtrapolationController` and `KantorovichTypeController` likewise
  embed `CommonControllerOptions`. Their stepsize logic reads
  `get_qmax(integrator)` / `get_qmin(integrator)` rather than direct
  field access.

`test/runtests.jl` walks transitive `[sources]` dependencies and
`Pkg.develop`s them. Pre-seed the `developed` set with the active
project so a `[sources]` entry that points back to it (e.g. via the
umbrella `OrdinaryDiffEq`'s transitive sources) is skipped — `Pkg.develop`
cannot develop the active project itself, and that error was the
"package X has the same name or UUID as the active project" failure
across the sublibrary CI matrix.

Reproducer (`isoutofdomain` predicate that fires once on the first
proposed step) plus a smoke test of every controller path (default
`solve`, user-supplied `BDFController`, `CommonControllerOptions`
construction, controller-composition invariants) — 21/21 pass on Julia
1.12.

- On master (without this fix): all algorithms error out — accessing
  `integrator.opts.qmin` throws because the v7 `DEOptions` struct
  doesn't have the field.
- With this fix: `Tsit5` / `Vern7` / `Rosenbrock23` / `FBDF` / `QNDF`
  all complete the isout-rejection problem successfully, and
  `BDFController(qmax = 3)` is honored end-to-end.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
ChrisRackauckas-Claude pushed a commit to ChrisRackauckas-Claude/OrdinaryDiffEq.jl that referenced this pull request Apr 30, 2026
…ntroller for BDF/JVODE

In v7, `qmin` (alongside `qmax`, `gamma`, `beta1/beta2`, `qsteady_*`,
`qoldinit`) moved off `DEOptions` and onto the controller object. The
out-of-domain rejection path in `handle_step_rejection!` was still
reaching for the old `integrator.opts.qmin`, which throws on the v7
`DEOptions` struct — only the legacy DelayDiffEq constructor still
mentions it. The same was true of `integrator.opts.failfactor` in
`post_newton_controller!`.

Reported in
NumericalMathematics/PositiveIntegrators.jl#194 (comment)

Rather than papering over with a one-off `hasfield` walk, this lifts
the standard step-size knobs into a composable building block and
retires the `DummyController` workaround that BDF / Nordsieck were
using to keep the knobs on their algorithm structs.

A new `CommonControllerOptions{T}` struct holds `qmin`, `qmax`,
`qmax_first_step`, `gamma`, `qsteady_min`, `qsteady_max`, `failfactor`
— the seven scalars the integrator-level paths actually read. A single
type parameter `T` keeps the type signatures simple even if more knobs
are added later. All fields default to `nothing`; algorithm-specific
defaults are filled in by `resolve_basic` at `setup_controller_cache`
time. Concrete controllers (`IController`, `PIController`,
`PIDController`, `PredictiveController`, `ExtrapolationController`,
`KantorovichTypeController`, plus the new `BDFController` and
`JVODEController`) all embed a `CommonControllerOptions` as
`controller.basic`.

Seven generic accessors — `get_qmin`, `get_qmax`, `get_qmax_first_step`,
`get_gamma`, `get_qsteady_min`, `get_qsteady_max`, `get_failfactor` —
dispatch on `cache::AbstractControllerCache` and read through
`cache.controller.basic`. `CompositeControllerCache` overrides each one
to delegate to the active sub-cache. `DummyControllerCache` keeps an
alg-field fallback for any SDE algorithm still using it.

`integrator.opts.qmin` → `get_qmin(integrator)`,
`integrator.opts.failfactor` → `get_failfactor(integrator)`. Same in
the BDF post-Newton paths.

QNDF/FBDF/DFBDF used to keep `qmax`, `qsteady_min`, `qsteady_max` as
fields on the algorithm struct itself, with a `DummyController`
hard-wired into `default_controller`. The stepsize logic read
`alg.qmax` / `alg.qsteady_min` / `alg.qsteady_max` directly, plus a
hard-coded `zₛ = 1.2` magic-number gamma, so the controller surface was
unsettable.

`BDFController` embeds `CommonControllerOptions` and has a cache that
delegates back to alg-level dispatch (the existing BDF order-selection
logic is left intact). The hard-coded `zₛ = 1.2` is now
`get_gamma(integrator)`. `default_controller(QT, alg::Union{QNDF, FBDF, DFBDF})`
threads `alg.qmax` / `alg.qsteady_min` / `alg.qsteady_max` through to
the controller, so existing usage like `QNDF(qmax = 20)` keeps working.
Users can now also pass `controller = BDFController(qmin = …, gamma = …)`
to set knobs that previously had no surface (incl. `qmin` and `gamma`).
BDF-tuned per-algorithm defaults (`qmax = 5//1`, `qsteady_min = 9//10`,
`qsteady_max = 12//10`, `gamma = 12//10`) are encoded as
`qmax_default(::QNDF)` / `gamma_default(::QNDF)` etc.

Same pattern. `setη!` / `chooseη!` / `step_accept_controller!(::JVODE)`
now read `get_qmin(integrator)` / `get_qmax(integrator)` /
`get_qsteady_*(integrator)` instead of `alg.qmin` etc.

- `IController` / `PIController` / `PredictiveController` /
  `PIDController` shed their flat `qmin/qmax/...` fields and embed
  `CommonControllerOptions` instead. PI-specific knobs (`beta1`,
  `beta2`, `qoldinit`) and PID-specific knobs (`beta`, `accept_safety`,
  `limiter`) stay on the controller alongside `basic`.
- `ExtrapolationController` and `KantorovichTypeController` likewise
  embed `CommonControllerOptions`. Their stepsize logic reads
  `get_qmax(integrator)` / `get_qmin(integrator)` rather than direct
  field access.

`test/runtests.jl` walks transitive `[sources]` dependencies and
`Pkg.develop`s them. Pre-seed the `developed` set with the active
project so a `[sources]` entry that points back to it (e.g. via the
umbrella `OrdinaryDiffEq`'s transitive sources) is skipped — `Pkg.develop`
cannot develop the active project itself, and that error was the
"package X has the same name or UUID as the active project" failure
across the sublibrary CI matrix.

Reproducer (`isoutofdomain` predicate that fires once on the first
proposed step) plus a smoke test of every controller path (default
`solve`, user-supplied `BDFController`, `CommonControllerOptions`
construction, controller-composition invariants) — 21/21 pass on Julia
1.12.

- On master (without this fix): all algorithms error out — accessing
  `integrator.opts.qmin` throws because the v7 `DEOptions` struct
  doesn't have the field.
- With this fix: `Tsit5` / `Vern7` / `Rosenbrock23` / `FBDF` / `QNDF`
  all complete the isout-rejection problem successfully, and
  `BDFController(qmax = 3)` is honored end-to-end.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
ChrisRackauckas-Claude pushed a commit to ChrisRackauckas-Claude/OrdinaryDiffEq.jl that referenced this pull request Apr 30, 2026
…ntroller for BDF/JVODE

In v7, `qmin` (alongside `qmax`, `gamma`, `beta1/beta2`, `qsteady_*`,
`qoldinit`) moved off `DEOptions` and onto the controller object. The
out-of-domain rejection path in `handle_step_rejection!` was still
reaching for the old `integrator.opts.qmin`, which throws on the v7
`DEOptions` struct — only the legacy DelayDiffEq constructor still
mentions it. The same was true of `integrator.opts.failfactor` in
`post_newton_controller!`.

Reported in
NumericalMathematics/PositiveIntegrators.jl#194 (comment)

Rather than papering over with a one-off `hasfield` walk, this lifts
the standard step-size knobs into a composable building block and
retires the `DummyController` workaround that BDF / Nordsieck were
using to keep the knobs on their algorithm structs.

A new `CommonControllerOptions{T}` struct holds `qmin`, `qmax`,
`qmax_first_step`, `gamma`, `qsteady_min`, `qsteady_max`, `failfactor`
— the seven scalars the integrator-level paths actually read. A single
type parameter `T` keeps the type signatures simple even if more knobs
are added later. All fields default to `nothing`; algorithm-specific
defaults are filled in by `resolve_basic` at `setup_controller_cache`
time. Concrete controllers (`IController`, `PIController`,
`PIDController`, `PredictiveController`, `ExtrapolationController`,
`KantorovichTypeController`, plus the new `BDFController` and
`JVODEController`) all embed a `CommonControllerOptions` as
`controller.basic`.

Seven generic accessors — `get_qmin`, `get_qmax`, `get_qmax_first_step`,
`get_gamma`, `get_qsteady_min`, `get_qsteady_max`, `get_failfactor` —
dispatch on `cache::AbstractControllerCache` and read through
`cache.controller.basic`. `CompositeControllerCache` overrides each one
to delegate to the active sub-cache. `DummyControllerCache` keeps an
alg-field fallback for any SDE algorithm still using it.

`integrator.opts.qmin` → `get_qmin(integrator)`,
`integrator.opts.failfactor` → `get_failfactor(integrator)`. Same in
the BDF post-Newton paths.

QNDF/FBDF/DFBDF used to keep `qmax`, `qsteady_min`, `qsteady_max` as
fields on the algorithm struct itself, with a `DummyController`
hard-wired into `default_controller`. The stepsize logic read
`alg.qmax` / `alg.qsteady_min` / `alg.qsteady_max` directly, plus a
hard-coded `zₛ = 1.2` magic-number gamma, so the controller surface was
unsettable.

`BDFController` embeds `CommonControllerOptions` and has a cache that
delegates back to alg-level dispatch (the existing BDF order-selection
logic is left intact). The hard-coded `zₛ = 1.2` is now
`get_gamma(integrator)`. `default_controller(QT, alg::Union{QNDF, FBDF, DFBDF})`
threads `alg.qmax` / `alg.qsteady_min` / `alg.qsteady_max` through to
the controller, so existing usage like `QNDF(qmax = 20)` keeps working.
Users can now also pass `controller = BDFController(qmin = …, gamma = …)`
to set knobs that previously had no surface (incl. `qmin` and `gamma`).
BDF-tuned per-algorithm defaults (`qmax = 5//1`, `qsteady_min = 9//10`,
`qsteady_max = 12//10`, `gamma = 12//10`) are encoded as
`qmax_default(::QNDF)` / `gamma_default(::QNDF)` etc.

Same pattern. `setη!` / `chooseη!` / `step_accept_controller!(::JVODE)`
now read `get_qmin(integrator)` / `get_qmax(integrator)` /
`get_qsteady_*(integrator)` instead of `alg.qmin` etc.

- `IController` / `PIController` / `PredictiveController` /
  `PIDController` shed their flat `qmin/qmax/...` fields and embed
  `CommonControllerOptions` instead. PI-specific knobs (`beta1`,
  `beta2`, `qoldinit`) and PID-specific knobs (`beta`, `accept_safety`,
  `limiter`) stay on the controller alongside `basic`.
- `ExtrapolationController` and `KantorovichTypeController` likewise
  embed `CommonControllerOptions`. Their stepsize logic reads
  `get_qmax(integrator)` / `get_qmin(integrator)` rather than direct
  field access.

`test/runtests.jl` walks transitive `[sources]` dependencies and
`Pkg.develop`s them. Pre-seed the `developed` set with the active
project so a `[sources]` entry that points back to it (e.g. via the
umbrella `OrdinaryDiffEq`'s transitive sources) is skipped — `Pkg.develop`
cannot develop the active project itself, and that error was the
"package X has the same name or UUID as the active project" failure
across the sublibrary CI matrix.

Reproducer (`isoutofdomain` predicate that fires once on the first
proposed step) plus a smoke test of every controller path (default
`solve`, user-supplied `BDFController`, `CommonControllerOptions`
construction, controller-composition invariants) — 21/21 pass on Julia
1.12.

- On master (without this fix): all algorithms error out — accessing
  `integrator.opts.qmin` throws because the v7 `DEOptions` struct
  doesn't have the field.
- With this fix: `Tsit5` / `Vern7` / `Rosenbrock23` / `FBDF` / `QNDF`
  all complete the isout-rejection problem successfully, and
  `BDFController(qmax = 3)` is honored end-to-end.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
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.

3 participants