diff --git a/docs/src/flows/accessors.md b/docs/src/flows/accessors.md index 50fb52ce0..c52fb08c6 100644 --- a/docs/src/flows/accessors.md +++ b/docs/src/flows/accessors.md @@ -12,14 +12,6 @@ the map of what you can pull back out, and from which kind of flow. using OptimalControl using OrdinaryDiffEqTsit5 using NLPModelsIpopt -import CTFlows.Systems: # hide - hamiltonian, # hide - hamiltonian_vector_field, # hide - vector_field, # hide - get_hamiltonian_gradient, # hide - get_variable_gradient, # hide - get_pseudo_hamiltonian_gradient, # hide - get_pseudo_variable_gradient # hide nothing # hide ``` diff --git a/docs/src/flows/overview.md b/docs/src/flows/overview.md index f5c1fa287..79adc44fa 100644 --- a/docs/src/flows/overview.md +++ b/docs/src/flows/overview.md @@ -68,6 +68,7 @@ Every flow needs an ODE integrator, and none is a hard dependency — load one, ```@example main using OptimalControl +using OrdinaryDiffEqTsit5 using NLPModelsIpopt t0 = 0 @@ -84,20 +85,32 @@ ocp = @def begin 0.5∫(u(t)^2) → min end -try - Flow(ocp, (x, p) -> p[2]) -catch e - println(e) -end -``` - -```@example main -using OrdinaryDiffEqTsit5 f = Flow(ocp, (x, p) -> p[2]) nothing # hide ``` -Every page in this section opens with `using OrdinaryDiffEqTsit5` — no exceptions. +Every page in this section opens with `using OrdinaryDiffEqTsit5` — no exceptions. Forget it +and `Flow` says so at construction time, naming the `using` to add: + +```julia +julia> f = Flow(ocp, (x, p) -> p[2]) +ERROR: ExtensionError → top-level scope, REPL[6]:1 +│ +│ missing dependencies to access SciML options metadata +│ +│ Missing OrdinaryDiffEqTsit5 +│ +│ Context Load OrdinaryDiffEqTsit5, OrdinaryDiffEq, or DifferentialEquations to activate the CTSolversSciMLIntegrator extension. +│ Hint Run: using OrdinaryDiffEqTsit5 +└─ +``` + +!!! note "Why that block is not executed" + + The documentation build loads `OrdinaryDiffEq` once, for the whole site, and a Julia + extension stays armed for the rest of the session — so no page here can demonstrate this + failure live. The transcript above comes from a session loading `OptimalControl` and + `NLPModelsIpopt` and nothing else. ## Choosing an integrator and its options diff --git a/docs/src/migration.md b/docs/src/migration.md index df05e55fb..7e13fbeed 100644 --- a/docs/src/migration.md +++ b/docs/src/migration.md @@ -48,7 +48,7 @@ never write a flow. See [Installation](@ref getting-started-installation) and | `OptimalControl.VectorField`, `OptimalControl.Hamiltonian`, … | `VectorField`, `Hamiltonian`, … | exported at top level since v2.1.0-beta; the qualified form still works, it's just no longer necessary | | `Lie(X, f)` | `ad(X, f)` | renamed; `Lie(...)` now throws | | `X ⋅ f` | `ad(X, f)` | removed, no operator alias; `X ⋅ f` now throws | -| `HamiltonianLift` | `Lift(f)` to build; `CTLie.LiftedHamiltonianFunction` to name the type | renamed **and** re-parented — see [What changed meaning silently](@ref migration-silent) | +| `HamiltonianLift` | `Lift(f)` to build; `OptimalControl.LiftedHamiltonianFunction` to name the type | renamed **and** re-parented — see [What changed meaning silently](@ref migration-silent) | | `autonomous=`, `variable=`, `inplace=` (constructor keywords) | `is_autonomous=`, `is_variable=`, `is_inplace=` | prefixed, on `VectorField`, `Hamiltonian`, `@Lie`, and friends | | `Flow(f)` with `f::Function` | `Flow(VectorField(f))`, `Flow(Hamiltonian(f))`, `Flow(HamiltonianVectorField(f))` | the bare function no longer says what kind of flow to build | | `Flow(ocp, u, g, μ)` (3 positional) | `Flow(ocp, u; constraint=g, multiplier=μ)` | keywords, and they come as a pair | diff --git a/docs/src/results/plot.md b/docs/src/results/plot.md index 11de3edc2..6f4796daa 100644 --- a/docs/src/results/plot.md +++ b/docs/src/results/plot.md @@ -294,9 +294,9 @@ time_grid(sol_flow) ``` For a denser plot, pass `saveat` **when constructing the flow**, not on the call — the call -itself only accepts `variable`/`unsafe` (and `augment`, for costate augmentation). `dense=false` -is required alongside `saveat`, since dense output and `saveat` conflict at the integrator -level: +itself only accepts `variable`/`unsafe` (and `variable_costate`, for costate augmentation). +`dense=false` is required alongside `saveat`, since dense output and `saveat` conflict at the +integrator level: ```@example main fine_grid = range(t0, tf, 100) diff --git a/docs/src/solve/choosing-a-method.md b/docs/src/solve/choosing-a-method.md index 0b96c588e..66cde76aa 100644 --- a/docs/src/solve/choosing-a-method.md +++ b/docs/src/solve/choosing-a-method.md @@ -30,6 +30,25 @@ There are 12 methods: every `{adnlp, exa} × {ipopt, madnlp, uno, madncl, knitro This list is not fixed prose to memorize — it is exactly what `methods()` returns, so it's printed live rather than quoted as a number anywhere on this page. +## A problem to try them on + +Every `solve` call on this page uses the same problem — the double integrator, with its +dynamics written coordinatewise so that both the `:adnlp` and the `:exa` modeler accept it: + +```@example main +ocp = @def begin + t ∈ [0, 1], time + x = (q, v) ∈ R², state + u ∈ R, control + x(0) == [-1, 0] + x(1) == [0, 0] + ∂(q)(t) == v(t) + ∂(v)(t) == u(t) + ∫(0.5u(t)^2) → min +end +nothing # hide +``` + ## Partial descriptions `solve(ocp, :madnlp)` doesn't need the other three tokens — they're completed for you. @@ -112,30 +131,50 @@ describe(:cpu) `:collocation` accepts a `scheme` option (alias `disc_method`): -| Value | Notes | -| --- | --- | -| `:trapeze` | second-order | -| `:midpoint` | second-order, **default** | -| `:euler`, `:euler_explicit`, `:euler_forward` | first-order, explicit | -| `:euler_implicit`, `:euler_backward` | first-order, implicit | -| `:gauss_legendre_2` | fourth-order, **`:adnlp` modeler only** | -| `:gauss_legendre_3` | sixth-order, **`:adnlp` modeler only** | -| `:variable` | variable-step ODE-based discretization | +| Value | Notes | `:adnlp` | `:exa` | +| --- | --- | :-: | :-: | +| `:trapeze` | second-order | ✅ | ✅ | +| `:midpoint` | second-order, **default** | ✅ | ✅ | +| `:euler` | first-order, explicit | ✅ | ✅ | +| `:euler_implicit` | first-order, implicit | ✅ | ✅ | +| `:euler_explicit`, `:euler_forward` | aliases of `:euler` | ✅ | ✗ | +| `:euler_backward` | alias of `:euler_implicit` | ✅ | ✗ | +| `:gauss_legendre_2` | fourth-order | ✅ | ✗ | +| `:gauss_legendre_3` | sixth-order | ✅ | ✗ | +| `:variable` | variable-step, ODE-based | ✗ | ✗ | plus `grid_size` (default `250`) or an explicit, possibly non-uniform, `time_grid`. -!!! warning "Gauss-Legendre schemes are `:adnlp`-only" +Every cell above was checked by solving with that scheme. Two of the results need spelling out. + +!!! warning "Under `:exa`, only the four canonical names work" - `:gauss_legendre_2`/`:gauss_legendre_3` are rejected under `:exa` — confirmed live: + `:exa` takes `:trapeze`, `:midpoint`, `:euler` and `:euler_implicit`, and nothing else. + Both Gauss-Legendre schemes are out — and so are the aliases, which is the surprising part: + `:euler_forward` is rejected where `:euler` is accepted, though under `:adnlp` the two name + the same scheme. Confirmed live: ```@example main + using Logging + # ExaModels warns about a deprecated `ExaCore()` call on every model it + # builds; silenced here so the scheme error is the only output. try - solve(ocp, :exa; scheme=:gauss_legendre_2, display=false) + with_logger(NullLogger()) do + solve(ocp, :exa; scheme=:gauss_legendre_2, display=false) + end catch e println(e) end ``` +!!! warning "`:variable` is advertised but does not run" + + The `describe(:collocation)` output above lists `:variable`, and the registry accepts it, + but no modeler can use it: the scheme dispatches to `CTDirect.VariableStepODE`, whose + implementation is not compiled into CTDirect, so a solve raises + `UndefVarError(:VariableStepODE, …, CTDirect)` rather than any typed error. It is kept in + the table only because the registry listing above advertises it. + ## Advanced: the strategy registry `strategy_ids`, `type_from_id`, and `available_parameters` (and the [`create_registry`](@ref)