Skip to content
9 changes: 9 additions & 0 deletions docs/Project.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
[deps]
DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e"
DiffEqDevTools = "f3b72e0c-5b89-59e1-b016-84e28bfd966d"
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
ImplicitDiscreteSolve = "3263718b-31ed-49cf-8a0f-35a466e8af96"
Expand All @@ -8,6 +9,7 @@ OrdinaryDiffEqAdamsBashforthMoulton = "89bda076-bce5-4f1c-845f-551c83cdda9a"
OrdinaryDiffEqBDF = "6ad6398a-0878-4a85-9266-38940aa047c8"
OrdinaryDiffEqCore = "bbf590c4-e513-4bbe-9b18-05decba2e5d8"
OrdinaryDiffEqDefault = "50262376-6c5a-4cf5-baba-aaf4f84d72d7"
OrdinaryDiffEqDifferentiation = "4302a76b-040a-498a-8c04-15b101fed76b"
OrdinaryDiffEqExplicitRK = "9286f039-9fbf-40e8-bf65-aa933bdc4db0"
OrdinaryDiffEqExponentialRK = "e0540318-69ee-4070-8777-9e2de6de23de"
OrdinaryDiffEqExtrapolation = "becaefa8-8ca2-5cf9-886d-c06f3d2bd2c4"
Expand All @@ -19,6 +21,7 @@ OrdinaryDiffEqLinear = "521117fe-8c41-49f8-b3b6-30780b3f0fb5"
OrdinaryDiffEqLowOrderRK = "1344f307-1e59-4825-a18e-ace9aa3fa4c6"
OrdinaryDiffEqLowStorageRK = "b0944070-b475-4768-8dec-fb6eb410534d"
OrdinaryDiffEqNordsieck = "c9986a66-5c92-4813-8696-a7ec84c806c8"
OrdinaryDiffEqNonlinearSolve = "127b3ac7-2247-4354-8eb6-78cf4e7c58e8"
OrdinaryDiffEqPDIRK = "5dd0a6cf-3d4b-4314-aa06-06d4e299bc89"
OrdinaryDiffEqPRK = "5b33eab2-c0f1-4480-b2c3-94bc1e80bda1"
OrdinaryDiffEqQPRK = "04162be5-8125-4266-98ed-640baecc6514"
Expand All @@ -33,13 +36,15 @@ OrdinaryDiffEqTsit5 = "b1df2697-797e-41e3-8120-5422d3b24e4a"
OrdinaryDiffEqVerner = "79d7bb75-1356-48c1-b8c0-6832512096c2"

[sources]
DiffEqBase = {path = "../lib/DiffEqBase"}
DiffEqDevTools = {path = "../lib/DiffEqDevTools"}
ImplicitDiscreteSolve = {path = "../lib/ImplicitDiscreteSolve"}
OrdinaryDiffEqAMF = {path = "../lib/OrdinaryDiffEqAMF"}
OrdinaryDiffEqAdamsBashforthMoulton = {path = "../lib/OrdinaryDiffEqAdamsBashforthMoulton"}
OrdinaryDiffEqBDF = {path = "../lib/OrdinaryDiffEqBDF"}
OrdinaryDiffEqCore = {path = "../lib/OrdinaryDiffEqCore"}
OrdinaryDiffEqDefault = {path = "../lib/OrdinaryDiffEqDefault"}
OrdinaryDiffEqDifferentiation = {path = "../lib/OrdinaryDiffEqDifferentiation"}
OrdinaryDiffEqExplicitRK = {path = "../lib/OrdinaryDiffEqExplicitRK"}
OrdinaryDiffEqExponentialRK = {path = "../lib/OrdinaryDiffEqExponentialRK"}
OrdinaryDiffEqExtrapolation = {path = "../lib/OrdinaryDiffEqExtrapolation"}
Expand All @@ -51,6 +56,7 @@ OrdinaryDiffEqLinear = {path = "../lib/OrdinaryDiffEqLinear"}
OrdinaryDiffEqLowOrderRK = {path = "../lib/OrdinaryDiffEqLowOrderRK"}
OrdinaryDiffEqLowStorageRK = {path = "../lib/OrdinaryDiffEqLowStorageRK"}
OrdinaryDiffEqNordsieck = {path = "../lib/OrdinaryDiffEqNordsieck"}
OrdinaryDiffEqNonlinearSolve = {path = "../lib/OrdinaryDiffEqNonlinearSolve"}
OrdinaryDiffEqPDIRK = {path = "../lib/OrdinaryDiffEqPDIRK"}
OrdinaryDiffEqPRK = {path = "../lib/OrdinaryDiffEqPRK"}
OrdinaryDiffEqQPRK = {path = "../lib/OrdinaryDiffEqQPRK"}
Expand All @@ -65,6 +71,7 @@ OrdinaryDiffEqTsit5 = {path = "../lib/OrdinaryDiffEqTsit5"}
OrdinaryDiffEqVerner = {path = "../lib/OrdinaryDiffEqVerner"}

[compat]
DiffEqBase = "7"
DiffEqDevTools = "3, 4.0"
Documenter = "0.27, 1"
ImplicitDiscreteSolve = "2"
Expand All @@ -74,6 +81,7 @@ OrdinaryDiffEqAdamsBashforthMoulton = "2.0.0"
OrdinaryDiffEqBDF = "2.0.0"
OrdinaryDiffEqCore = "4.1"
OrdinaryDiffEqDefault = "2.0.0"
OrdinaryDiffEqDifferentiation = "3"
OrdinaryDiffEqExplicitRK = "2.0.0"
OrdinaryDiffEqExponentialRK = "2.0.0"
OrdinaryDiffEqExtrapolation = "2.0.0"
Expand All @@ -85,6 +93,7 @@ OrdinaryDiffEqLinear = "2.0.0"
OrdinaryDiffEqLowOrderRK = "2.0.0"
OrdinaryDiffEqLowStorageRK = "3.0.0"
OrdinaryDiffEqNordsieck = "2.0.0"
OrdinaryDiffEqNonlinearSolve = "2"
OrdinaryDiffEqPDIRK = "2.0.0"
OrdinaryDiffEqPRK = "2.0.0"
OrdinaryDiffEqQPRK = "2.0.0"
Expand Down
6 changes: 6 additions & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using Documenter, OrdinaryDiffEq, DiffEqDevTools
using DiffEqBase
using OrdinaryDiffEqDifferentiation
using OrdinaryDiffEqNonlinearSolve
using OrdinaryDiffEqCore
# Bring controller API symbols into Main so unqualified @ref links in
# docs/src/api/controllers.md resolve. These are not exported by
Expand Down Expand Up @@ -47,7 +50,9 @@ makedocs(
doctest = false,
modules = [
OrdinaryDiffEq,
DiffEqBase,
OrdinaryDiffEqCore,
OrdinaryDiffEqDifferentiation,
OrdinaryDiffEqAdamsBashforthMoulton,
OrdinaryDiffEqBDF,
OrdinaryDiffEqDefault,
Expand All @@ -62,6 +67,7 @@ makedocs(
OrdinaryDiffEqLowOrderRK,
OrdinaryDiffEqLowStorageRK,
OrdinaryDiffEqNordsieck,
OrdinaryDiffEqNonlinearSolve,
OrdinaryDiffEqPDIRK,
OrdinaryDiffEqPRK,
OrdinaryDiffEqQPRK,
Expand Down
5 changes: 5 additions & 0 deletions docs/pages.jl
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ pages = [
"misc.md",
],
"APIs" => [
"api/diffeqbase.md",
"api/ordinarydiffeqcore.md",
"api/controllers.md",
],
"Developer Documentation" => [
Expand All @@ -72,6 +74,9 @@ pages = [
],
"Internal Documentation" => [
"devtools/internals/notes_on_algorithms.md",
"devtools/internals/public_api.md",
"devtools/internals/algorithm_traits.md",
"devtools/internals/differentiation.md",
"devtools/internals/tableaus.md",
],
],
Expand Down
68 changes: 48 additions & 20 deletions docs/src/api/controllers.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ controller — not on `integrator.opts` — and the per-step state
This page documents:

1. The built-in controllers users can pick from.
2. The [`CommonControllerOptions`](@ref) bundle of standard step-size
2. The [`CommonControllerOptions`](@ref OrdinaryDiffEqCore.CommonControllerOptions) bundle of standard step-size
knobs that every controller composes.
3. The accessor functions the integrator uses to read knobs
(`get_qmin`, `get_qmax`, …).
Expand Down Expand Up @@ -59,7 +59,7 @@ OrdinaryDiffEqCore.CompositeController

## CommonControllerOptions

Every controller embeds a `CommonControllerOptions{T}` as its `basic`
Every controller embeds a `CommonControllerOptions{T, NLPType}` as its `basic`
field. This holds the seven standard step-size scalars and is the
target of [`resolve_basic`](@ref).

Expand Down Expand Up @@ -119,22 +119,48 @@ To plug in a new step-size strategy, you implement two types — the
controller (immutable knob holder) and the controller cache (mutable
per-solve state) — and a small set of methods.

This is developer extension API. Application code should select among
documented controllers or pass a custom controller object to `solve`;
it should not depend on concrete cache structs or fields under
`integrator.cache`.

### Interface contract

The controller protocol follows the same separation used by the SciML
array/function interfaces: the public surface is the set of generic
functions and the rules they obey, not a promise that internal cache
types will remain stable.

| Rule | Requirement |
|---|---|
| Descriptor/state split | A controller subtype of [`AbstractController`](@ref OrdinaryDiffEqCore.AbstractController) stores user configuration. Mutable per-solve state lives in an [`AbstractControllerCache`](@ref OrdinaryDiffEqCore.AbstractControllerCache). |
| Standard knobs | Controllers that want the default knob accessors store standard knobs in `basic`. Before setup this may be a `NamedTuple` of keyword overrides; after setup it should be [`CommonControllerOptions`](@ref OrdinaryDiffEqCore.CommonControllerOptions). |
| Setup | [`setup_controller_cache`](@ref OrdinaryDiffEqCore.setup_controller_cache) resolves defaults, allocates all per-solve state, and returns a cache ready for the first step. |
| Error estimate | Caches either provide a mutable `EEst` field or override [`get_EEst`](@ref OrdinaryDiffEqCore.get_EEst) and [`set_EEst!`](@ref OrdinaryDiffEqCore.set_EEst!). |
| Step-size factor | [`stepsize_controller!`](@ref OrdinaryDiffEqCore.stepsize_controller!) returns the divisor `q`. OrdinaryDiffEq interprets `q > 1` as shrinking `dt` and `q < 1` as growing `dt`. |
| Accepted step | [`step_accept_controller!`](@ref OrdinaryDiffEqCore.step_accept_controller!) returns the proposed next `dt`; later integrator logic may still adjust it to satisfy stops and final-time constraints. |
| Rejected step | [`step_reject_controller!`](@ref OrdinaryDiffEqCore.step_reject_controller!) mutates `integrator.dt` so the failed step can be retried. |
| Optional hooks | Override optional hooks only for behavior that differs from the defaults; default acceptance is `get_EEst(integrator) <= 1`, and default reinit/sync/reset hooks are no-ops. |

### Required types

```julia
struct MyController{B} <: OrdinaryDiffEqCore.AbstractController
basic::B # NamedTuple of overrides (unresolved) or CommonControllerOptions{T} (resolved)
basic::B # NamedTuple overrides or resolved CommonControllerOptions{T, NLPType}
end

mutable struct MyControllerCache{T, E} <: OrdinaryDiffEqCore.AbstractControllerCache
controller::MyController{OrdinaryDiffEqCore.CommonControllerOptions{T}}
mutable struct MyControllerCache{T, E, NLPType} <: OrdinaryDiffEqCore.AbstractControllerCache
controller::MyController{OrdinaryDiffEqCore.CommonControllerOptions{T, NLPType}}
# ... per-step scratch state owned by the controller ...
EEst::E # scalar error estimate; required unless you override `get_EEst` / `set_EEst!`
end
```

The cache must expose the scalar error estimate via an `EEst` field, or
provide its own [`get_EEst`](@ref) / [`set_EEst!`](@ref) overrides.
provide its own [`get_EEst`](@ref OrdinaryDiffEqCore.get_EEst) /
[`set_EEst!`](@ref OrdinaryDiffEqCore.set_EEst!) overrides.
Concrete cache types are developer extension types for solver authors;
they should not be treated as application-level API.

### Required methods

Expand All @@ -151,7 +177,7 @@ In summary:

| Method | What it does | When called |
|---|---|---|
| `setup_controller_cache(alg, alg_cache, controller, ::Type{EEstT})` | Allocate the controller cache, resolve any unresolved [`CommonControllerOptions`](@ref). | `init`, once per solve. |
| `setup_controller_cache(alg, alg_cache, controller, ::Type{EEstT}, disco_probs)` | Allocate the controller cache, resolve any unresolved [`CommonControllerOptions`](@ref OrdinaryDiffEqCore.CommonControllerOptions). | `init`, once per solve. |
| `stepsize_controller!(integrator, cache, alg)` | Compute and return the step-size factor `q` based on the current error estimate. | Every step, after `perform_step!`. |
| `step_accept_controller!(integrator, cache, alg, q)` | Return the new `dt` for the next step when the current one is accepted. | Once per accepted step. |
| `step_reject_controller!(integrator, cache, alg)` | Mutate `integrator.dt` so the rejected step can be retried. | Once per rejected step. |
Expand All @@ -177,13 +203,13 @@ OrdinaryDiffEqCore.reset_alg_dependent_opts!
| `reinit_controller!(integrator, cache)` | no-op | Stateful controllers that need to reset `q11`, `qold`, `dtacc`, etc. on `reinit!`. |
| `sync_controllers!(cache1, cache2)` | no-op | Composite-alg switching, when state must transfer between sub-controllers. |
| `reset_alg_dependent_opts!(cache, alg1, alg2)` | no-op | Re-derive `beta1` / `beta2` etc. when a composite algorithm switches between branches. |
| `get_qmin(integrator, cache)` (and `get_qmax` / `get_gamma` / `get_qsteady_min` / `get_qsteady_max` / `get_qmax_first_step` / `get_failfactor`) | Reads `cache.controller.basic.X` | Controllers that don't embed a [`CommonControllerOptions`](@ref) or store the knobs somewhere other than `cache.controller.basic`. |
| `get_qmin(integrator, cache)` (and `get_qmax` / `get_gamma` / `get_qsteady_min` / `get_qsteady_max` / `get_qmax_first_step` / `get_failfactor`) | Reads `cache.controller.basic.X` | Controllers that don't embed a [`CommonControllerOptions`](@ref OrdinaryDiffEqCore.CommonControllerOptions) or store the knobs somewhere other than `cache.controller.basic`. |
| `get_EEst(cache)` / `set_EEst!(cache, val)` | Read/write the `EEst` field on the cache | Caches that store the error differently (vector, per-stage, …). |

### Worked example

Here is a stripped-down implementation of an I-controller using the new
interface. It composes [`CommonControllerOptions`](@ref) so users can
interface. It composes [`CommonControllerOptions`](@ref OrdinaryDiffEqCore.CommonControllerOptions) so users can
pass `qmin` / `qmax` / `gamma` as kwargs, and it picks up
algorithm-specific defaults automatically.

Expand All @@ -199,13 +225,13 @@ using OrdinaryDiffEqCore
using OrdinaryDiffEqCore: AbstractController, AbstractControllerCache,
CommonControllerOptions, resolve_basic,
_resolved_QT, get_EEst, get_current_adaptive_order,
get_current_qmax, fastpower
get_current_qmax
import OrdinaryDiffEqCore: setup_controller_cache, stepsize_controller!,
step_accept_controller!, step_reject_controller!

# Type-stability trick: `basic` is either a `NamedTuple` of user-supplied
# overrides (before setup_controller_cache resolves it) or a fully-resolved
# `CommonControllerOptions{T}` (after). The Union constraint lets the
# `CommonControllerOptions{T, NLPType}` (after). The Union constraint lets the
# compiler still specialize on both branches.
struct MyIController{B <: Union{NamedTuple, CommonControllerOptions}} <: AbstractController
basic::B
Expand All @@ -216,21 +242,23 @@ end
# algorithm's defaults.
MyIController(; kwargs...) = MyIController(NamedTuple(kwargs))

# The cache holds the resolved controller (concrete `CommonControllerOptions{T}`)
# The cache holds the resolved controller (concrete `CommonControllerOptions{T, NLPType}`)
# and any per-solve scratch state — for an I-controller, just the scalar EEst.
mutable struct MyIControllerCache{T, E} <: AbstractControllerCache
controller::MyIController{CommonControllerOptions{T}}
mutable struct MyIControllerCache{T, E, NLPType} <: AbstractControllerCache
controller::MyIController{CommonControllerOptions{T, NLPType}}
EEst::E
end

# Called once at `init` time. Picks the scalar type `QT` for the step-size
# factors, then fills in every unset knob from `qmin_default(alg)`,
# `qmax_default(alg)`, … via `resolve_basic`. After this, every read of
# `cache.controller.basic.X` is type-stable.
function setup_controller_cache(alg, alg_cache, controller::MyIController, ::Type{E}) where {E}
function setup_controller_cache(
alg, alg_cache, controller::MyIController, ::Type{E}, disco_probs) where {E}
QT = _resolved_QT(controller.basic)
basic = resolve_basic(controller.basic, alg, QT)
return MyIControllerCache{QT, E}(MyIController(basic), oneunit(E))
basic = resolve_basic(controller.basic, alg, QT; disco_probs)
return MyIControllerCache{QT, E, eltype(disco_probs)}(
MyIController(basic), oneunit(E))
end

# Called after each `perform_step!`. Computes the divisor `q` from the
Expand All @@ -246,7 +274,7 @@ end
return inv(qmax) # grow as fast as allowed
end
expo = 1 / (get_current_adaptive_order(alg, integrator.cache) + 1)
qtmp = fastpower(EEst, expo) / gamma # raw factor predicted by I controller
qtmp = EEst^expo / gamma # raw factor predicted by I controller
return max(inv(qmax), min(inv(qmin), qtmp))
end

Expand Down Expand Up @@ -285,12 +313,12 @@ etc., at `init` time.
### Adding a controller-specific knob

If your controller relies on a knob that doesn't exist on
[`CommonControllerOptions`](@ref), add it as a new field on your
[`CommonControllerOptions`](@ref OrdinaryDiffEqCore.CommonControllerOptions), add it as a new field on your
controller struct (alongside `basic`). The built-in controllers follow
this pattern: `PIController` keeps `beta1` / `beta2` / `qoldinit`
separate from the standard knobs, and `PIDController` keeps `beta` /
`accept_safety` / `limiter`. Separating them keeps
[`CommonControllerOptions`](@ref) small enough to be useful as the
[`CommonControllerOptions`](@ref OrdinaryDiffEqCore.CommonControllerOptions) small enough to be useful as the
"every adaptive controller has these" surface, while still letting
specialized controllers carry their own tuning state. The new field
moves through `setup_controller_cache` the same way `basic` does —
Expand Down
45 changes: 45 additions & 0 deletions docs/src/api/diffeqbase.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# DiffEqBase API

This page lists the user-facing DiffEqBase API documented with OrdinaryDiffEq.
Solver-author hooks, callback machinery, and cache types are documented separately
in the [developer extension API](@ref Developer-Extension-API).

## Default callback behavior

```@docs
DiffEqBase.DEVerbosity
DiffEqBase.ODE_DEFAULT_ISOUTOFDOMAIN
DiffEqBase.ODE_DEFAULT_NORM
DiffEqBase.ODE_DEFAULT_PROG_MESSAGE
DiffEqBase.ODE_DEFAULT_UNSTABLE_CHECK
```

## Runge-Kutta tableau types

```@docs
DiffEqBase.Tableau
DiffEqBase.ODERKTableau
DiffEqBase.ExplicitRKTableau
DiffEqBase.ImplicitRKTableau
```

## Cost and convergence helpers

```@docs
DiffEqBase.ConvergenceSetup
DiffEqBase.DECostFunction
```

## DAE initialization

```@docs
DiffEqBase.DefaultInit
DiffEqBase.BrownFullBasicInit
DiffEqBase.ShampineCollocationInit
```

## Sensitivity passthrough

```@docs
DiffEqBase.SensitivityADPassThrough
```
51 changes: 51 additions & 0 deletions docs/src/api/ordinarydiffeqcore.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# OrdinaryDiffEqCore API

This page lists user-facing OrdinaryDiffEqCore API. The controller API has its
own page, and solver-author hooks are documented separately in the
[developer extension API](@ref Developer-Extension-API).

## Integrator objects

```@docs
OrdinaryDiffEqCore.ODEIntegrator
```

## Threading options

```@docs
OrdinaryDiffEqCore.AbstractThreadingOption
OrdinaryDiffEqCore.Sequential
OrdinaryDiffEqCore.BaseThreads
OrdinaryDiffEqCore.PolyesterThreads
```

## Automatic algorithm switching

```@docs
OrdinaryDiffEqCore.AutoAlgSwitch
OrdinaryDiffEqCore.AutoSwitch
```

## SSP helpers

```@docs
OrdinaryDiffEqCore.ssp_coefficient
```

## Implicit method predictors

```@docs
OrdinaryDiffEqCore.Predictor
```

## Nonlinear solver algorithms

These OrdinaryDiffEqNonlinearSolve algorithms are public user API because they
are passed directly through implicit solver constructors as `nlsolve = ...`.
Lower-level nonlinear-solve hooks are developer extension API.

```@docs
OrdinaryDiffEqNonlinearSolve.NLAnderson
OrdinaryDiffEqNonlinearSolve.NLFunctional
OrdinaryDiffEqNonlinearSolve.NLNewton
```
1 change: 1 addition & 0 deletions docs/src/devtools/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ Depth = 2
```@contents
Pages = [
"internals/fem_tools.md",
"internals/public_api.md",
"internals/notes_on_algorithms.md",
"internals/tableaus.md"
]
Expand Down
Loading