From 205ddde124c1e043f1ad0f06f6076b1701bdba3b Mon Sep 17 00:00:00 2001 From: Olivier Cots Date: Thu, 20 Aug 2026 20:14:13 +0200 Subject: [PATCH 01/10] fix(docs): correct methods() docstring count and un-reexported constructor paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit methods.jl's docstring claimed 11 methods (9 CPU + 2 GPU); the real tuple has 12 (10 CPU + 2 GPU), and methods()[9] is a CPU entry, not GPU. Also fixes three module docstrings that showed CTDirect.Collocation()/CTSolvers.Modelers.ADNLP()/CTSolvers.Solvers.Ipopt(), none of which are re-exported and so are undefined under `using OptimalControl` — corrected to the OptimalControl.* spelling that actually resolves. Co-Authored-By: Claude Sonnet 5 --- src/OptimalControl.jl | 8 ++++---- src/helpers/methods.jl | 9 ++++++--- src/solve/canonical.jl | 6 +++--- src/solve/dispatch.jl | 4 ++-- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/OptimalControl.jl b/src/OptimalControl.jl index 0feab05bf..1efec5989 100644 --- a/src/OptimalControl.jl +++ b/src/OptimalControl.jl @@ -29,10 +29,10 @@ ocp = Model(...) sol = solve(ocp, :collocation, :adnlp, :ipopt) # Or solve using explicit mode (typed components) -sol = solve(ocp; - discretizer=CTDirect.Collocation(), - modeler=CTSolvers.Modelers.ADNLP(), - solver=CTSolvers.Solvers.Ipopt() +sol = solve(ocp; + discretizer=OptimalControl.Collocation(), + modeler=OptimalControl.ADNLP(), + solver=OptimalControl.Ipopt() ) ``` diff --git a/src/helpers/methods.jl b/src/helpers/methods.jl index ea2a50d5f..64a0e81fd 100644 --- a/src/helpers/methods.jl +++ b/src/helpers/methods.jl @@ -18,21 +18,24 @@ julia> m = methods() ((:collocation, :adnlp, :ipopt, :cpu), (:collocation, :adnlp, :madnlp, :cpu), ...) julia> length(m) -11 # 9 CPU methods + 2 GPU methods +12 # 10 CPU methods + 2 GPU methods julia> # CPU methods julia> methods()[1] (:collocation, :adnlp, :ipopt, :cpu) -julia> # GPU methods julia> methods()[9] +(:collocation, :exa, :madncl, :cpu) + +julia> # GPU methods +julia> methods()[11] (:collocation, :exa, :madnlp, :gpu) ``` # Notes - Returns a precomputed constant tuple (allocation-free, type-stable) - All methods currently use `:collocation` discretization -- CPU methods (9 total): All combinations of `{adnlp, exa}` × `{ipopt, madnlp, uno, madncl, knitro}` +- CPU methods (10 total): All combinations of `{adnlp, exa}` × `{ipopt, madnlp, uno, madncl, knitro}` - GPU methods (2 total): Only GPU-capable combinations `exa` × `{madnlp, madncl}` - GPU-capable strategies use parameterized types with automatic defaults - Used by `CTBase.Descriptions.complete` to complete partial method descriptions diff --git a/src/solve/canonical.jl b/src/solve/canonical.jl index 5949cb6c3..9afb99a3e 100644 --- a/src/solve/canonical.jl +++ b/src/solve/canonical.jl @@ -36,9 +36,9 @@ normalized. It discretizes the problem and passes it to the underlying `solve` p ocp = Model(time=:final) # ... define OCP ... init = CTModels.build_initial_guess(ocp, nothing) -disc = CTDirect.Collocation(grid_size=100) -mod = CTSolvers.Modelers.ADNLP() -sol = CTSolvers.Solvers.Ipopt() +disc = OptimalControl.Collocation(grid_size=100) +mod = OptimalControl.ADNLP() +sol = OptimalControl.Ipopt() solution = solve(ocp, init, disc, mod, sol; display=true) ``` diff --git a/src/solve/dispatch.jl b/src/solve/dispatch.jl index 0e511fdb3..f02bfcd8b 100644 --- a/src/solve/dispatch.jl +++ b/src/solve/dispatch.jl @@ -28,8 +28,8 @@ solve(ocp, :collocation, :adnlp, :ipopt) solve(ocp, :collocation; init=x0, display=false) # Explicit mode (typed components) -solve(ocp; discretizer=CTDirect.Collocation(), - modeler=CTSolvers.Modelers.ADNLP(), solver=CTSolvers.Solvers.Ipopt()) +solve(ocp; discretizer=OptimalControl.Collocation(), + modeler=OptimalControl.ADNLP(), solver=OptimalControl.Ipopt()) ``` # Throws From 209e81fcc569035628f04701496d3c99ed47ef75 Mon Sep 17 00:00:00 2001 From: Olivier Cots Date: Thu, 20 Aug 2026 20:54:01 +0200 Subject: [PATCH 02/10] docs(solve): write Overview page Co-Authored-By: Claude Sonnet 5 --- docs/src/solve/overview.md | 115 ++++++++++++++++++++++++++++++++++++- 1 file changed, 113 insertions(+), 2 deletions(-) diff --git a/docs/src/solve/overview.md b/docs/src/solve/overview.md index 527fac4ba..b118ece95 100644 --- a/docs/src/solve/overview.md +++ b/docs/src/solve/overview.md @@ -1,4 +1,115 @@ # [Overview](@id solve-overview) -!!! warning "Under construction" - This page is being written. See the [specification reports](https://github.com/control-toolbox/OptimalControl.jl/tree/main/docs/reports). +```@meta +Draft = false +``` + +`solve` is the entry point for the direct methods: transcribe the problem, hand it to an NLP +solver, get a [`Solution`](@ref results-solution) back. This page shows the quickest way to call it, how to read +what it printed, and the two ways to steer it away from its defaults. + +## Quick start + +```@example main +using OptimalControl +using NLPModelsIpopt + +t0 = 0 +tf = 1 +x0 = [-1, 0] + +ocp = @def begin + t ∈ [t0, tf], time + x = (q, v) ∈ R², state + u ∈ R, control + x(t0) == x0 + x(tf) == [0, 0] + ẋ(t) == [v(t), u(t)] + 0.5∫(u(t)^2) → min +end + +sol = solve(ocp) +nothing # hide +``` + +A solver package must be loaded before calling `solve` — here `using NLPModelsIpopt` provides +the default `:ipopt`. Without it, `solve` raises an `ExtensionError` naming the missing +package and the exact `using` statement that fixes it. + +## Reading the display + +By default `solve` prints a configuration table before running: which discretizer, modeler, +and solver were selected, and every option that ends up applied to each — tagged by where the +value came from: + +- `:user` — you passed it explicitly, +- `:default` — the strategy's own default, +- `:computed` — derived from the problem (e.g. a grid size picked from the time span). + +This is the fastest way to answer "what did `solve` actually do with the call I just wrote?" +without reading source. + +## Turning the display off + +```@example main +sol = solve(ocp; display=false) +nothing # hide +``` + +Useful once you trust a configuration and are solving in a loop, a test, or a script. + +## The defaults + +Calling `solve(ocp)` with no strategy tokens is equivalent to: + +```julia +solve(ocp, :collocation, :adnlp, :ipopt, :cpu) +``` + +This particular quadruplet isn't special-cased — it's simply the first entry of [`methods`](@ref)`()`, +and completion always takes the first match, top to bottom (see +[Choosing a method](@ref solve-choosing-a-method) for the full list and how partial +descriptions are completed). + +## Two ways to steer it + +`solve` can be pointed at a different strategy in two styles: + +- **descriptive** — symbolic tokens, e.g. `solve(ocp, :madnlp)` (see + [Choosing a method](@ref solve-choosing-a-method)), +- **explicit** — typed component instances, e.g. `solve(ocp; solver=OptimalControl.MadNLP())` + (see [Explicit mode](@ref solve-explicit-mode)). + +**The one thing worth knowing before either of those pages**: which mode you're in is decided +by the *type* of a keyword's *value*, never by the keyword's *name*. Any keyword argument whose +value `isa` `AbstractDiscretizer`, `AbstractNLPModeler`, or `AbstractNLPSolver` switches `solve` +into explicit mode, no matter what that keyword is called. Mixing a typed component with a +non-empty symbolic description is rejected outright: + +```@example main +using MadNLP +try + solve(ocp, :collocation; solver=OptimalControl.MadNLP()) +catch e + println(e) +end +``` + +## When it fails + +A solve that doesn't converge still returns a [`Solution`](@ref results-solution) — inspect it rather than +assuming success: + +```@example main +println(successful(sol)) # true/false — did the solver report success? +println(status(sol)) # a Symbol, e.g. :first_order, :max_iter +println(message(sol)) # the solver's own message +println(constraints_violation(sol)) +``` + +## See also + +- [Choosing a method](@ref solve-choosing-a-method) — the full list of strategies and how + partial descriptions are completed. +- [Explicit mode](@ref solve-explicit-mode) — build and pass typed components directly. +- [Set an initial guess](@ref solve-initial-guess) — every way to hand `solve` a starting point. From 51d609708d36a8918cd32e7477ea09770cb88e84 Mon Sep 17 00:00:00 2001 From: Olivier Cots Date: Thu, 20 Aug 2026 20:54:01 +0200 Subject: [PATCH 03/10] docs(solve): write Initial guess page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ports the attic manual almost entirely, dropping the legacy NamedTuple-construction section (still works, just superseded by @init). Documents a real inconsistency found while verifying live: supplying both init= and initial_guess= throws a clean "conflicting aliases" error in explicit mode, but a confusing "unknown option :init" error in descriptive mode (the common case) — the two code paths don't share the same conflict check. Co-Authored-By: Claude Sonnet 5 --- docs/src/solve/initial-guess.md | 347 +++++++++++++++++++++++++++++++- 1 file changed, 345 insertions(+), 2 deletions(-) diff --git a/docs/src/solve/initial-guess.md b/docs/src/solve/initial-guess.md index c8841ad90..93d6fab1a 100644 --- a/docs/src/solve/initial-guess.md +++ b/docs/src/solve/initial-guess.md @@ -1,4 +1,347 @@ # [Initial guess](@id solve-initial-guess) -!!! warning "Under construction" - This page is being written. See the [specification reports](https://github.com/control-toolbox/OptimalControl.jl/tree/main/docs/reports). +```@meta +Draft = false +``` + +Every way to hand `solve` a starting point: constants, functions, grids, a previous solution, +or nothing at all. + +```@example main +using OptimalControl +using NLPModelsIpopt +using Plots +``` + +Two running examples, to show both default and custom component labels: + +```@example main +t0 = 0; tf = 10; α = 5 + +ocp1 = @def begin + t ∈ [t0, tf], time + x ∈ R², state + u ∈ R, control + x(t0) == [-1, 0] + x₁(tf) == 0 + ẋ(t) == [x₂(t), x₁(t) + α * x₁(t)^2 + u(t)] + x₂(tf)^2 + ∫(0.5u(t)^2) → min +end +nothing # hide +``` + +```@example main +ocp2 = @def begin + tf ∈ R, variable + s ∈ [0, tf], time + x = (q, v) ∈ R², state + u ∈ R, control + -1 ≤ u(s) ≤ 1 + tf ≥ 0 + q(0) == -1 + v(0) == 0 + q(tf) == 0 + v(tf) == 0 + ẋ(s) == [v(s), u(s)] + tf → min +end +nothing # hide +``` + +`@init` uses the **labels declared in `@def`**: for `ocp1` that's `x`, `x₁`, `x₂`, `u` (default +subscripted names, since `x ∈ R²` doesn't name its components); for `ocp2` it's `x`, `q`, `v`, +`u`, `tf`. It also uses the **time variable name** from `@def`: `t` for `ocp1`, `s` for `ocp2`. + +## The default guess + +With no initial guess, every component defaults to `0.1`. To see it without solving, run with +`max_iter=0`: + +```@example main +sol_init = solve(ocp1; init=nothing, max_iter=0, display=false) +plot(sol_init, :state, :control; size=(600, 450)) +``` + +Solving with no guess at all, `init=nothing`, and `init=()` are all equivalent — all three skip +straight to the default: + +```@example main +sol = solve(ocp1; display=false) +println("no init: ", iterations(sol), " iterations") + +sol = solve(ocp1; init=nothing, display=false) +println("init=nothing: ", iterations(sol), " iterations") + +sol = solve(ocp1; init=(), display=false) +println("init=(): ", iterations(sol), " iterations") +``` + +## The `@init` macro + +`@init` builds an initial guess with the syntax `label(t) := expression` (functions of time) or +`label := value` (for variables and aliases, which aren't functions of time): + +```julia +ig = @init ocp begin + # specifications +end +``` + +| Component | Has `(t)`? | Uses `:=`? | Example | +| --- | --- | --- | --- | +| State / control | yes | yes | `u(t) := 2` | +| Variable | no | yes | `tf := 2.0` | +| Alias | no | no (use `=`) | `a = 0.5` | + +1-D components take a scalar (`u(t) := 2`); multi-D components take a vector +(`x(t) := [1, 2]`). The right-hand side of `:=` can be a constant, a function of the time +variable, or a grid `label(T) := data` for a time vector `T`. + +The indexed syntax `x[1](t) := ...` is **not supported** — `@init` works at the level of +declared labels, not array positions; use `x₁(t) := ...` or a component's own name instead. + +### Constant + +```@example main +ig = @init ocp1 begin + x(t) := [-0.2, 0.1] + u(t) := -0.2 +end + +sol = solve(ocp1; init=ig, display=false) +println(iterations(sol), " iterations") +``` + +Constant functions also accept the shorter form without the time argument +(`u := 2` instead of `u(t) := 2`): + +```@example main +ig = @init ocp2 begin + q(s) := -0.2 + v(s) := 0.0 + u(s) := 0.1 + tf := 2.0 +end + +sol = solve(ocp2; init=ig, display=false) +println(iterations(sol), " iterations") +``` + +### Partial + +Uninitialized components fall back to `0.1`: + +```@example main +ig = @init ocp1 begin + u(t) := -0.2 +end + +sol = solve(ocp1; init=ig, display=false) +println(iterations(sol), " iterations") +``` + +### Time-dependent functions + +```@example main +ig = @init ocp1 begin + x(t) := [-0.2t, 0.1t] + u(t) := -0.2t +end + +sol = solve(ocp1; init=ig, display=false) +println(iterations(sol), " iterations") +``` + +### Aliases + +`=` (no time argument) defines a local alias, not a problem label: + +```@example main +ig = @init ocp2 begin + amplitude = 0.5 + φ = 2π * s + q(s) := amplitude * sin(φ) + v(s) := amplitude * cos(φ) + u(s) := sin(amplitude) + tf := 2.0 +end + +sol = solve(ocp2; init=ig, display=false) +println(iterations(sol), " iterations") +``` + +### Cross-spec references + +A spec can reference a label defined earlier in the same block, and references chain: + +```@example main +ig = @init ocp2 begin + q(s) := sin(s) + v(s) := 1.0 + q(s) # references q + u(s) := s + v(s)^2 # transitively references q via v + tf := 2.0 +end + +sol = solve(ocp2; init=ig, display=false) +println(iterations(sol), " iterations") +``` + +A grid-based spec (`label(T) := data`, see below) lives in a different evaluation context and +is not substituted into a spec written with the plain time variable, or vice versa — keep one +style per chain of references. + +## Constants, vectors, functions + +State and control 1-D reads through `@init` follow the "1-D is a scalar" rule, same as +everywhere else — no special case to remember here beyond what's already true on +[functional-API callbacks](@ref modelling-functional-api-shapes) and solutions. + +## Vector initial guess (interpolated) + +`label(T) := data`, with `T` a time vector, interpolates `data` onto the solve grid: + +```@example main +T = [0.0, 5.0, 10.0] +X = [[-1.0, 0.0], [-0.5, 0.5], [0.0, 0.0]] +U = [0.0, -0.5, 0.0] + +ig = @init ocp1 begin + x(T) := X + u(T) := U +end + +sol = solve(ocp1; init=ig, display=false) +println(iterations(sol), " iterations") +``` + +Different components can use different grids: + +```@example main +Sq = [0.0, 1.0, 2.0]; Dq = [-1.0, -0.5, 0.0] +Sv = [0.0, 2.0]; Dv = [0.0, 0.0] +Su = [0.0, 1.0, 2.0]; Du = [0.0, 0.5, 0.0] + +ig = @init ocp2 begin + q(Sq) := Dq + v(Sv) := Dv + u(Su) := Du + tf := 2.0 +end + +sol = solve(ocp2; init=ig, display=false) +println(iterations(sol), " iterations") +``` + +For state, a matrix (one row per time point) works too: + +```@example main +T = [0.0, 5.0, 10.0] +Xmat = [-1.0 0.0; -0.5 0.5; 0.0 0.0] +U = [0.0, -0.5, 0.0] + +ig = @init ocp1 begin + x(T) := Xmat + u(T) := U +end + +sol = solve(ocp1; init=ig, display=false) +println(iterations(sol), " iterations") +``` + +## Mixing them + +Constants, functions, and grids combine freely in one `@init` block: + +```@example main +T = [0.0, 5.0, 10.0] +X = [[-1.0, 0.0], [-0.5, 0.5], [0.0, 0.0]] + +ig = @init ocp1 begin + x(T) := X # grid + u(t) := -0.2 * sin(t) # function +end + +sol = solve(ocp1; init=ig, display=false) +println(iterations(sol), " iterations") +``` + +## Warm start from a solution + +Pass a [`Solution`](@ref results-solution) directly — dimensions of state, control, and variable must match. This +is the basis for discrete continuation: + +```@example main +sol_init = solve(ocp1; display=false) +sol = solve(ocp1; init=sol_init, display=false) +println(iterations(sol), " iterations") +``` + +Or extract functions from a solution and feed them through `@init`: + +```@example main +x_fun = state(sol_init) +u_fun = control(sol_init) + +ig = @init ocp1 begin + x(t) := x_fun(t) + u(t) := u_fun(t) +end + +sol = solve(ocp1; init=ig, display=false) +println(iterations(sol), " iterations") +``` + +`state`, `costate`, and `control` on a solution return functions of time; `variable` returns a +vector. + +## Costate and multipliers + +There is currently no way to seed the costate or the multipliers — only state, control, and +variable accept an initial guess. + +## `init` or `initial_guess` + +`init` is an alias for `initial_guess`; use whichever reads better. **In explicit mode**, +supplying both at once is rejected cleanly: + +```@example main +try + solve(ocp1; discretizer=OptimalControl.Collocation(), init=1, initial_guess=2, display=false) +catch e + println(e) +end +``` + +!!! warning "Same conflict, worse message in descriptive mode" + + In descriptive mode (the common case — no `discretizer=`/`modeler=`/`solver=`), supplying + both aliases does **not** raise this same clear error. `initial_guess` is consumed first, + and the leftover `init` falls through to strategy-option routing, where it's rejected as an + *unrecognized solver option* rather than as a conflicting alias — a much more confusing + message for the same mistake: + + ```@example main + try + solve(ocp1; init=1, initial_guess=2, display=false) + catch e + println(e) + end + ``` + + This is a real inconsistency between the two code paths, not intentional behavior — don't + rely on either message, just don't pass both. + +## Not (yet) part of the public API + +`CTModels.jl` has more initial-guess machinery than OptimalControl exposes: +`initial_guess`, `pre_initial_guess`, `validate_initial_guess`, `initial_state`, +`initial_control`, `initial_variable`, and `PreInitialGuess` all exist there but are not +re-exported here — only [`build_initial_guess`](@ref) is. Whether some of these should surface +under `using OptimalControl` is an open question, not a decision this page makes. + +## See also + +- [Overview](@ref solve-overview) — the rest of what `solve` accepts. +- [Solution object](@ref results-solution) — `state`, `costate`, `control`, `variable` on a + returned solution. +- [Abstract syntax](@ref modelling-abstract-syntax) — where the labels `@init` uses come from. From 796ad9d9c8f31dc144dd1c2b722299aedf31ef05 Mon Sep 17 00:00:00 2001 From: Olivier Cots Date: Thu, 20 Aug 2026 20:54:12 +0200 Subject: [PATCH 04/10] docs(solve): write Choosing a method page (new) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Scopes the "API covered" list down to what a caller can actually reach directly (methods, describe, id, metadata, option_*, parameter, default_parameter). strategy_ids/type_from_id/available_parameters/ create_registry all require a populated StrategyRegistry, and the only one with the real built-in strategies is internal (OptimalControl.get_strategy_registry, not re-exported) — documented as advanced/orchestration tooling rather than everyday API. Co-Authored-By: Claude Sonnet 5 --- docs/src/solve/choosing-a-method.md | 152 +++++++++++++++++++++++++++- 1 file changed, 150 insertions(+), 2 deletions(-) diff --git a/docs/src/solve/choosing-a-method.md b/docs/src/solve/choosing-a-method.md index 5c273f537..0b96c588e 100644 --- a/docs/src/solve/choosing-a-method.md +++ b/docs/src/solve/choosing-a-method.md @@ -1,4 +1,152 @@ # [Choosing a method](@id solve-choosing-a-method) -!!! warning "Under construction" - This page is being written. See the [specification reports](https://github.com/control-toolbox/OptimalControl.jl/tree/main/docs/reports). +```@meta +Draft = false +``` + +A **method** is a quadruplet `(discretizer, modeler, solver, parameter)`. This page maps out +what can be combined with what, how a partial description gets completed, and how to inspect +any one piece before you commit to it. + +## The four families + +- **Discretizer** — how the continuous problem is transcribed into a finite-dimensional one. + Only `:collocation` exists today. +- **Modeler** — how the resulting NLP is built: `:adnlp` (automatic differentiation via + ADNLPModels) or `:exa` (SIMD-friendly, GPU-capable, via ExaModels — only for problems whose + dynamics are written coordinatewise, see [Abstract syntax](@ref modelling-abstract-syntax)). +- **Solver** — which NLP solver runs: `:ipopt`, `:madnlp`, `:uno`, `:madncl`, `:knitro`. +- **Parameter** — execution backend: `:cpu` or `:gpu`. + +## What is available + +```@example main +using OptimalControl +methods() +``` + +There are 12 methods: every `{adnlp, exa} × {ipopt, madnlp, uno, madncl, knitro}` pair on +`:cpu` (10), plus the two GPU-capable combinations `:exa × {:madnlp, :madncl}` on `:gpu` (2). +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. + +## Partial descriptions + +`solve(ocp, :madnlp)` doesn't need the other three tokens — they're completed for you. +Completion walks `methods()` from top to bottom and returns the first entry containing every +token you gave: + +```julia +solve(ocp, :madnlp) # → (:collocation, :adnlp, :madnlp, :cpu) — first entry with :madnlp +solve(ocp, :exa) # → (:collocation, :exa, :ipopt, :cpu) — first entry with :exa +solve(ocp, :gpu) # → (:collocation, :exa, :madnlp, :gpu) — first GPU entry +``` + +This first-match-top-to-bottom rule is also why the plain `solve(ocp)` default is +`(:collocation, :adnlp, :ipopt, :cpu)`: it's simply `methods()[1]`. All of these are +equivalent: + +```julia +solve(ocp) # empty description → methods()[1] +solve(ocp, :collocation) +solve(ocp, :adnlp) +solve(ocp, :ipopt) +solve(ocp, :cpu) +solve(ocp, :collocation, :adnlp) +solve(ocp, :collocation, :adnlp, :ipopt, :cpu) # the complete description +``` + +## Ambiguity + +Two tokens from the *same* family never both fit one method — `:adnlp` and `:exa` can't both +be true of one quadruplet — so this raises `AmbiguousDescription` rather than silently picking +one: + +```@example main +try + solve(ocp, :adnlp, :exa; display=false) +catch e + println(e) +end +``` + +The exception lists every candidate whose tokens are a superset of what matched, so you can see +what's close. + +## What each solver needs installed + +| Solver | Load | +| --- | --- | +| `:ipopt` | `using NLPModelsIpopt` | +| `:madnlp` | `using MadNLP` (CPU) or `using MadNLPGPU` (GPU) | +| `:uno` | `using UnoSolver` | +| `:madncl` | `using MadNCL` and `using MadNLP` (both) | +| `:knitro` | `using NLPModelsKnitro` (commercial licence required) | + +Solving without the matching package loaded raises an `ExtensionError` naming exactly which +`using` statement to add. + +## Inspecting a strategy + +`describe` works on any strategy id, and covers more than the direct-solve side: it also +describes the indirect-method families (`:di`, `:sciml`) and the two parameters themselves. + +```@example main +describe(:collocation) +``` + +```@example main +describe(:adnlp) +``` + +```@example main +using NLPModelsIpopt +describe(:ipopt) +``` + +```@example main +describe(:cpu) +``` + +## Discretization schemes + +`: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 | + +plus `grid_size` (default `250`) or an explicit, possibly non-uniform, `time_grid`. + +!!! warning "Gauss-Legendre schemes are `:adnlp`-only" + + `:gauss_legendre_2`/`:gauss_legendre_3` are rejected under `:exa` — confirmed live: + + ```@example main + try + solve(ocp, :exa; scheme=:gauss_legendre_2, display=false) + catch e + println(e) + end + ``` + +## Advanced: the strategy registry + +`strategy_ids`, `type_from_id`, and `available_parameters` (and the [`create_registry`](@ref) +used to build one) all operate on a populated `StrategyRegistry`. The one that already knows +about every built-in strategy is internal (`OptimalControl.get_strategy_registry()`, not +re-exported) — these functions are orchestration/extension-authoring tools, not something a +typical solve caller reaches for. For everyday inspection, `methods()` and `describe` (above) +cover the same ground and need nothing extra. + +## See also + +- [Options and routing](@ref solve-options) — how keyword arguments reach the right strategy. +- [Solving on GPU](@ref solve-gpu) — the `:gpu` parameter in full. +- [API reference: Options and strategies](@ref api-options) — every symbol on this page, with full signatures. From a15d8165a94fec87e452ba98ab7f6263f4149de2 Mon Sep 17 00:00:00 2001 From: Olivier Cots Date: Thu, 20 Aug 2026 20:54:12 +0200 Subject: [PATCH 05/10] docs(solve): write Options page Co-Authored-By: Claude Sonnet 5 --- docs/src/solve/options.md | 158 +++++++++++++++++++++++++++++++++++++- 1 file changed, 156 insertions(+), 2 deletions(-) diff --git a/docs/src/solve/options.md b/docs/src/solve/options.md index ad69ba1f7..c2e5016ca 100644 --- a/docs/src/solve/options.md +++ b/docs/src/solve/options.md @@ -1,4 +1,158 @@ # [Options](@id solve-options) -!!! warning "Under construction" - This page is being written. See the [specification reports](https://github.com/control-toolbox/OptimalControl.jl/tree/main/docs/reports). +```@meta +Draft = false +``` + +Every keyword argument passed to `solve` ends up on exactly one strategy — the discretizer, +the modeler, or the solver. This page covers how that routing works, the two escape hatches +for the cases it doesn't handle automatically, and how to inspect where a value came from. + +```@example advanced +using OptimalControl +using NLPModelsIpopt + +t0 = 0 +tf = 1 +x0 = [-1, 0] + +ocp = @def begin + t ∈ [t0, tf], time + x = (q, v) ∈ R², state + u ∈ R, control + x(t0) == x0 + x(tf) == [0, 0] + ẋ(t) == [v(t), u(t)] + 0.5∫(u(t)^2) → min +end +nothing # hide +``` + +## Option routing + +`solve` runs in **strict** mode: every keyword you pass must be recognized by exactly one of +the three strategies in play, or it's an error — never a silent no-op. + +```@example advanced +sol = solve(ocp; + grid_size=100, # → Collocation (discretizer) + show_time=true, # → ADNLP (modeler) + max_iter=500, # → Ipopt (solver) + print_level=0, # → Ipopt (solver) +) +nothing # hide +``` + +An option nobody recognizes is rejected with a "did you mean" suggestion: + +```@example advanced +try + solve(ocp, :ipopt; max_iter=100, mumps_print_level=1, display=false) +catch e + println(e) +end +``` + +## Ambiguous options + +If two strategies from *different* families declare the same option name, using it bare is +ambiguous — `solve` won't guess which one you meant. Disambiguate with `route_to`, which takes +a strategy id and a value: + +```julia +sol = solve(ocp, :exa, :madnlp; + common_option_name=route_to(:exa, 12), + max_iter=500, +) +``` + +`route_to` also accepts alternating id/value pairs, to send the same option name to several +strategies with different values at once: + +```julia +sol = solve(ocp, :exa, :madnlp; + common_option_name=route_to(:exa, 12, :madnlp, true), +) +``` + +`route_to` works even when there's no ambiguity to resolve — it's fine to use it just to be +explicit: + +```@example advanced +using MadNLP +sol = solve(ocp, :madnlp; + grid_size=50, # auto-routed to the discretizer + max_iter=route_to(:madnlp, 1000), # explicitly routed + print_level=MadNLP.ERROR, # auto-routed to the solver +) +nothing # hide +``` + +## Undeclared solver options + +The three strategies here declare their own options, but not every option the underlying +solver accepts is declared — Ipopt's `mumps_print_level`, for instance, isn't in the strategy +metadata, so it's rejected by strict validation (shown above). Combine `route_to` with +`bypass` to force it through, unvalidated: + +```@example advanced +sol = solve(ocp, :ipopt; + max_iter=100, + mumps_print_level=route_to(:ipopt, bypass(1)), +) +nothing # hide +``` + +`bypass` is needed in addition to `route_to` because `route_to` alone still validates against +the strategy's declared options — `bypass` is what skips that check. `force` is a plain alias +for `bypass` (`force === bypass`); use whichever name reads better: +`route_to(:ipopt, force(1))`. + +Both `bypass` and `route_to` return values of internal types (`BypassValue`, `RoutedOption`) — +these types are imported but not exported, so only the functions ever appear in your code, not +the type names. + +!!! warning "Use `bypass` sparingly" + + It skips type checking and validation entirely. Reach for it only when you're certain the + option name and value are correct and the strategy genuinely doesn't declare it. + +## Where a value came from + +Every option on a built strategy instance knows whether it was set by you, left at its +default, or computed from the problem: + +```@example advanced +s = OptimalControl.Ipopt(max_iter=200) +println(option_value(s, :max_iter)) # 200 — what will actually be used +println(option_value(s, :tol)) # 1.0e-8 — the strategy's own default + +opts = options(s) +println(is_user(opts, :max_iter)) # true +println(is_default(opts, :tol)) # true +println(is_computed(opts, :max_iter)) # false +``` + +`option_source` returns which of the three it was, as a `Symbol`: + +```@example advanced +println(option_source(s, :max_iter)) +println(option_source(s, :tol)) +``` + +This is the same provenance information the `📦 Configuration` table shows when `solve` prints +its display (see [Overview](@ref solve-overview)) — these functions let you query it +programmatically instead of reading it off the printout. + +## Action options vs strategy options + +Two keywords — `init`/`initial_guess` and `display` — are handled *before* routing even +starts; they're never sent to a strategy. If a strategy happens to declare an option with the +same name, the action option wins by default. `route_to` is the way around that, if you ever +need to target the strategy's own option of that name explicitly instead. + +## See also + +- [Choosing a method](@ref solve-choosing-a-method) — the strategies these options are routed to. +- [Explicit mode](@ref solve-explicit-mode) — configure a strategy by passing options directly + to its constructor instead of routing them through `solve`. From c16ad6ae2ff326352e284d39d96616b9ad605989 Mon Sep 17 00:00:00 2001 From: Olivier Cots Date: Thu, 20 Aug 2026 20:54:12 +0200 Subject: [PATCH 06/10] docs(solve): write Explicit mode page Co-Authored-By: Claude Sonnet 5 --- docs/src/solve/explicit-mode.md | 155 +++++++++++++++++++++++++++++++- 1 file changed, 153 insertions(+), 2 deletions(-) diff --git a/docs/src/solve/explicit-mode.md b/docs/src/solve/explicit-mode.md index c4437e77b..634b7e66b 100644 --- a/docs/src/solve/explicit-mode.md +++ b/docs/src/solve/explicit-mode.md @@ -1,4 +1,155 @@ # [Explicit mode](@id solve-explicit-mode) -!!! warning "Under construction" - This page is being written. See the [specification reports](https://github.com/control-toolbox/OptimalControl.jl/tree/main/docs/reports). +```@meta +Draft = false +``` + +Instead of symbolic tokens, pass `solve` typed strategy instances — full control over each +component's configuration, no completion-order guessing. + +## When you want this + +- building the strategy configuration programmatically (from a data structure, a search over + hyperparameters, etc.), +- reusing one carefully-configured strategy instance across several `solve` calls, +- avoiding any ambiguity about which options went where. + +## Basic usage + +```@example explicit +using OptimalControl +using NLPModelsIpopt + +t0 = 0 +tf = 1 +x0 = [-1, 0] + +ocp = @def begin + t ∈ [t0, tf], time + x = (q, v) ∈ R², state + u ∈ R, control + x(t0) == x0 + x(tf) == [0, 0] + ẋ(t) == [v(t), u(t)] + 0.5∫(u(t)^2) → min +end + +disc = OptimalControl.Collocation(grid_size=100, scheme=:trapeze) +mod = OptimalControl.ADNLP(backend=:optimized) +sol = OptimalControl.Ipopt(max_iter=1000, print_level=0) + +result = solve(ocp; discretizer=disc, modeler=mod, solver=sol) +nothing # hide +``` + +`discretizer`, `modeler`, and `solver` are just three of many keyword names — mode detection +never looks at names, only at whether a keyword's *value* is a typed component (see +[Overview](@ref solve-overview)). Any keyword holding a typed instance triggers explicit mode. + +The component types are `import`ed but not `@reexport`ed by `OptimalControl`, which is why +every constructor above is written `OptimalControl.Collocation(...)` rather than bare +`Collocation(...)` — writing `using CTDirect: Collocation` yourself would also work, but the +qualified spelling needs nothing extra loaded beyond `using OptimalControl`. + +## Partial components + +Give one component, and the other two are completed the same way descriptive mode completes a +partial token list — first match, top to bottom in [`methods`](@ref)`()`: + +```@example explicit +result = solve(ocp; solver=OptimalControl.Ipopt(max_iter=2000, print_level=0), display=true) +nothing # hide +``` + +`solver=Ipopt(...)` alone completes to `Collocation()` (first discretizer) and `ADNLP()` (first +modeler compatible with Ipopt) — visible in the printed configuration above. Mixing a custom +component with defaults works the same way for any subset: + +```@example explicit +result = solve(ocp; + discretizer=OptimalControl.Collocation(grid_size=200, scheme=:trapeze), + solver=OptimalControl.Ipopt(max_iter=100, print_level=0), + display=false, +) +nothing # hide +``` + +## Per-component options + +Every option a strategy accepts is set when it's constructed — never routed in from `solve` +afterward, unlike descriptive mode: + +```@example explicit +disc = OptimalControl.Collocation(grid_size=150, scheme=:gauss_legendre_2) +mod = OptimalControl.ADNLP(backend=:optimized, show_time=true) +sol = OptimalControl.Ipopt(max_iter=1000, tol=1e-8, print_level=5, acceptable_tol=1e-6) +nothing # hide +``` + +Undeclared options still need `bypass` (or its alias `force`), same reasoning as in descriptive +mode — but here it's passed straight into the constructor, not through `route_to`: + +```@example explicit +solver = OptimalControl.Ipopt(max_iter=500, print_level=0, mumps_print_level=bypass(1)) +nothing # hide +``` + +A flat option keyword handed to `solve` itself, rather than to the component constructor, is +rejected — even one a completed default component would otherwise recognize: + +```@example explicit +try + solve(ocp; discretizer=OptimalControl.Collocation(), backend=:generic, display=false) +catch e + println(e) +end +``` + +The error names the strategy that owns the option and tells you exactly how to fix it: +construct that strategy with the option set, and pass the configured instance in. `route_to` +plays no role here — it only makes sense in descriptive mode, where options don't yet belong to +a concrete instance. + +## Mixing modes is forbidden + +Symbolic tokens and typed components can't appear in the same call: + +```@example explicit +try + solve(ocp, :adnlp, :ipopt; discretizer=OptimalControl.Collocation(), display=false) +catch e + println(e) +end +``` + +Pick one: `solve(ocp, :collocation, :adnlp, :ipopt; options...)` or +`solve(ocp; discretizer=..., modeler=..., solver=...)`. + +## Inspecting the components you built + +```@example explicit +solver = OptimalControl.Ipopt(max_iter=1000, tol=1e-6, print_level=0) +opts = options(solver) + +is_user(opts, :max_iter) +``` + +```@example explicit +is_default(opts, :mu_strategy) +``` + +```@example explicit +opts[:max_iter] +``` + +```@example explicit +collect(keys(opts)) +``` + +## See also + +- [Overview](@ref solve-overview) — how mode detection decides between the two styles. +- [Options and routing](@ref solve-options) — the descriptive-mode counterpart (`route_to`, + automatic routing) to per-component construction here. +- [Choosing a method](@ref solve-choosing-a-method) — the full strategy catalogue these + constructors build from. From b6df4aae9696d1ba2d16d30a0d27e19f28b20b81 Mon Sep 17 00:00:00 2001 From: Olivier Cots Date: Thu, 20 Aug 2026 20:54:12 +0200 Subject: [PATCH 07/10] docs(solve): write GPU page (Draft, does not execute) No CUDA-capable device in CI or in dev environments; loading CUDA/MadNLPGPU builds CPU-side handles fine but the GPU-parameterized strategies pull in extensions that only finish loading with real hardware present. Kept as the one page in this section that does not flip to Draft = false, with that explained at the top. Co-Authored-By: Claude Sonnet 5 --- docs/src/solve/gpu.md | 111 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 109 insertions(+), 2 deletions(-) diff --git a/docs/src/solve/gpu.md b/docs/src/solve/gpu.md index 9ec304d47..fd65bf112 100644 --- a/docs/src/solve/gpu.md +++ b/docs/src/solve/gpu.md @@ -1,4 +1,111 @@ # [GPU](@id solve-gpu) -!!! warning "Under construction" - This page is being written. See the [specification reports](https://github.com/control-toolbox/OptimalControl.jl/tree/main/docs/reports). +```@meta +Draft = true +``` + +GPU support runs through [ExaModels.jl](https://exanauts.github.io/ExaModels.jl/stable) and +[MadNLPGPU.jl](https://github.com/MadNLP/MadNLP.jl), NVIDIA GPUs only, via +[CUDA.jl](https://github.com/JuliaGPU/CUDA.jl). + +!!! note "This page doesn't execute" + + Unlike every other page in this section, the code blocks here are not run when the docs + are built — there is no CUDA-capable GPU in CI or in this development environment. Loading + `CUDA`/`MadNLPGPU` and *constructing* CPU-side handles works fine without a device, but the + GPU-parameterized solver strategies pull in extensions (CUDSS in particular) that only + finish loading with real GPU hardware present. Everything below is accurate as prose and + matches the source it describes, but treat it as reference, not as tested output. + +## Prerequisites + +```julia +using OptimalControl +using ExaModels +using MadNLPGPU +using CUDA +``` + +Check `CUDA.functional()` before assuming a `:gpu` solve will actually run on the device. + +## The problem must be coordinatewise + +`:exa` — the only GPU-capable modeler — requires dynamics (and any path constraint) written +one coordinate at a time, `∂(x₁)(t) == ...`, not `ẋ(t) == [...]`. See +[Abstract syntax](@ref modelling-abstract-syntax) for the two forms side by side. + +```julia +ocp = @def begin + t ∈ [0, 1], time + x ∈ R², state + u ∈ R, control + v ∈ R, variable + x(0) == [0, 1] + x(1) == [0, -1] + ∂(x₁)(t) == x₂(t) # coordinatewise + ∂(x₂)(t) == u(t) # — not ẋ(t) == [x₂(t), u(t)] + 0 ≤ x₁(t) + v^2 ≤ 1.1 + -10 ≤ u(t) ≤ 10 + 1 ≤ v ≤ 2 + ∫(u(t)^2 + v) → min +end +``` + +## Descriptive mode + +The `:gpu` parameter token selects GPU-optimized defaults: + +```julia +sol = solve(ocp, :exa, :madnlp, :gpu; grid_size=100, print_level=MadNLP.ERROR) + +# or, letting completion fill in the rest — first match with :gpu: +sol = solve(ocp, :gpu; grid_size=100, print_level=MadNLP.ERROR) +``` + +`:gpu` changes what a strategy's own defaults are: `Exa{GPU}` uses a CUDA differentiation +backend, `MadNLP{GPU}` uses the `CUDSSSolver` linear solver instead of MUMPS. `describe(:gpu)` +lists every strategy with a GPU-parameterized variant (`:exa`, `:madnlp`, `:madncl`, plus the +indirect-side `:di` and `:sciml`) — this call needs nothing GPU-specific and runs fine on CPU +alone. + +## Explicit mode + +```julia +disc = OptimalControl.Collocation(grid_size=100, scheme=:midpoint) +mod = OptimalControl.Exa{GPU}() +sol = OptimalControl.MadNLP{GPU}(print_level=MadNLP.ERROR) + +result = solve(ocp; discretizer=disc, modeler=mod, solver=sol) +``` + +## What combinations work + +Only `:exa × {:madnlp, :madncl}` on `:gpu` — the two entries at the end of +[`methods`](@ref)`()` (see [Choosing a method](@ref solve-choosing-a-method)). Everything else +is a compile-time or runtime error, confirmed directly against the type system: + +- `OptimalControl.ADNLP{GPU}()` — `TypeError`, `ADNLP`'s parameter is constrained to `<:CPU`. +- `OptimalControl.Ipopt{GPU}()` — same, `Ipopt`'s parameter is `<:CPU`-only. +- Descriptively, `solve(ocp, :adnlp, :gpu)` or `solve(ocp, :ipopt, :gpu)` fail as + `AmbiguousDescription`: no entry in `methods()` has `:adnlp` or `:ipopt` together with `:gpu`. + +## Performance notes + +GPU solving amortizes best on large-scale problems (thousands of variables/constraints) or +repeated solves in a loop, where the per-call setup overhead is paid once. For small problems, +plain CPU solving is typically faster. + +```julia +if CUDA.functional() + sol = solve(ocp, :gpu) +else + sol = solve(ocp, :cpu) +end +``` + +## See also + +- [Overview](@ref solve-overview) — CPU solving basics. +- [Choosing a method](@ref solve-choosing-a-method) — the full method list, GPU entries included. +- [Explicit mode](@ref solve-explicit-mode) — typed components in general. +- The same `:cpu`/`:gpu` distinction applies to `Flow`; see [Flows overview](@ref flows-overview). From bb0ca7ebf56041ca4b4c231933497e723b62739e Mon Sep 17 00:00:00 2001 From: Olivier Cots Date: Thu, 20 Aug 2026 21:03:38 +0200 Subject: [PATCH 08/10] docs(solve): close small API-coverage gaps found on first full build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit has_option was listed but never invoked on options.md, methods() likewise on explicit-mode.md, build_initial_guess on initial-guess.md — add one small live example each so every symbol in each page's covered-API list actually executes, not just links. Also fixes the two real build failures from the first pass: the CTModelsPlots VBox bug (CTModels.jl#392, same as PR 5 — explicit description= on the affected plot() call) and two unresolved [`Solution`](@ref) links, retargeted to the results-solution stub anchor. Co-Authored-By: Claude Sonnet 5 --- docs/src/solve/explicit-mode.md | 4 ++++ docs/src/solve/initial-guess.md | 12 ++++++++++++ docs/src/solve/options.md | 2 ++ 3 files changed, 18 insertions(+) diff --git a/docs/src/solve/explicit-mode.md b/docs/src/solve/explicit-mode.md index 634b7e66b..d771f0833 100644 --- a/docs/src/solve/explicit-mode.md +++ b/docs/src/solve/explicit-mode.md @@ -56,6 +56,10 @@ qualified spelling needs nothing extra loaded beyond `using OptimalControl`. Give one component, and the other two are completed the same way descriptive mode completes a partial token list — first match, top to bottom in [`methods`](@ref)`()`: +```@example explicit +methods()[1] # (:collocation, :adnlp, :ipopt, :cpu) — what a bare solve(ocp) completes to +``` + ```@example explicit result = solve(ocp; solver=OptimalControl.Ipopt(max_iter=2000, print_level=0), display=true) nothing # hide diff --git a/docs/src/solve/initial-guess.md b/docs/src/solve/initial-guess.md index 93d6fab1a..827033447 100644 --- a/docs/src/solve/initial-guess.md +++ b/docs/src/solve/initial-guess.md @@ -100,6 +100,18 @@ variable, or a grid `label(T) := data` for a time vector `T`. The indexed syntax `x[1](t) := ...` is **not supported** — `@init` works at the level of declared labels, not array positions; use `x₁(t) := ...` or a component's own name instead. +Whatever you pass as `init=`/`initial_guess=` — `@init` output, `nothing`, a `Solution`, a +constant — `solve` normalizes it the same way internally, via `build_initial_guess(ocp, ...)`. +Calling it yourself is occasionally useful to inspect what got built: + +```@example main +ig = @init ocp1 begin + u(t) := -0.2 +end +built = build_initial_guess(ocp1, ig) +typeof(built) +``` + ### Constant ```@example main diff --git a/docs/src/solve/options.md b/docs/src/solve/options.md index c2e5016ca..d9e752b75 100644 --- a/docs/src/solve/options.md +++ b/docs/src/solve/options.md @@ -124,6 +124,8 @@ default, or computed from the problem: ```@example advanced s = OptimalControl.Ipopt(max_iter=200) +println(has_option(s, :max_iter)) # true — Ipopt declares this option +println(has_option(s, :not_an_option)) # false println(option_value(s, :max_iter)) # 200 — what will actually be used println(option_value(s, :tol)) # 1.0e-8 — the strategy's own default From 7dc198c8cfcf726b8d079676e732dca2ba359af5 Mon Sep 17 00:00:00 2001 From: Olivier Cots Date: Thu, 20 Aug 2026 21:03:38 +0200 Subject: [PATCH 09/10] docs: verify PR 6 acceptance criteria against a real build, update work board Full julia --project=docs docs/make.jl + npx vitepress build rebuild, clean: 0 undefined-binding/no-docs/duplicate-docs warnings, all six solve/ pages' @example blocks execute (gpu.md deliberately excepted, Draft = true), 0 unresolved @refs from them. test/suite/flows/test_gpu_routing.jl re-run (31 passed, 1 broken as expected without a GPU). typos clean. Acceptance criteria ticked against that build; two left honestly unticked with reasoning (DirectShooting silently omitted per the page-level spec instruction, not "documented as a limitation"; three choosing-a-method.md registry functions described in prose rather than executed, since they need an internal-only populated registry). Co-Authored-By: Claude Sonnet 5 --- docs/reports/04-solve-direct.md | 52 ++++++-- docs/reports/README.md | 2 +- docs/src/assets/Manifest.toml | 203 +++++++++++++++++++------------- 3 files changed, 168 insertions(+), 89 deletions(-) diff --git a/docs/reports/04-solve-direct.md b/docs/reports/04-solve-direct.md index 7a29e1f50..aa2b084d6 100644 --- a/docs/reports/04-solve-direct.md +++ b/docs/reports/04-solve-direct.md @@ -215,12 +215,46 @@ advanced page. ## Acceptance criteria -- [ ] `methods()` is printed live on `choosing-a-method.md`, never quoted as a number. -- [ ] `src/helpers/methods.jl`'s docstring reports 12 methods and the right `methods()[9]`. -- [ ] The three module docstrings no longer show `CTSolvers.Modelers.ADNLP()` / - `CTDirect.Collocation()`. -- [ ] `DirectShooting` is either reachable and documented, or explicitly listed as a known - limitation. -- [ ] Every symbol in the per-page "API covered" lists appears and executes. -- [ ] `gpu.md` is honest about not executing in CI. -- [ ] Mode detection **by type** is stated on `overview.md`, not only in the advanced page. +- [x] `methods()` is printed live on `choosing-a-method.md`, never quoted as a number. +- [x] `src/helpers/methods.jl`'s docstring reports 12 methods and the right `methods()[9]`. + Verified live: `length(methods()) == 12`, `methods()[9] == (:collocation, :exa, :madncl, + :cpu)`. +- [x] The three module docstrings no longer show `CTSolvers.Modelers.ADNLP()` / + `CTDirect.Collocation()`. Fixed in `src/OptimalControl.jl`, `src/solve/dispatch.jl`, + `src/solve/canonical.jl`; the corrected `OptimalControl.Collocation()` / + `OptimalControl.ADNLP()` / `OptimalControl.Ipopt()` spelling verified to actually resolve. +- ~~[ ] `DirectShooting` is either reachable and documented, or explicitly listed as a known + limitation.~~ Neither, deliberately: the per-page spec for `choosing-a-method.md` + explicitly says "Do not wire it in and do not mention it on the page" (confirmed still + unreachable — not in `src/imports/ctdirect.jl`, not in the registry). That instruction is + more specific than this checkbox and takes precedence; the page presents `:collocation` + as the only discretizer, unqualified, per the spec's own page-level direction. +- ~~[ ] Every symbol in the per-page "API covered" lists appears and executes.~~ True for every + page except `choosing-a-method.md`'s `strategy_ids`/`type_from_id`/`available_parameters`: + verified live that these require a populated `StrategyRegistry`, and the only one with the + real built-in strategies is internal (`OptimalControl.get_strategy_registry()`, not + re-exported) — calling them as the spec's outline implies (on a bare strategy type, or on + an empty `create_registry()`) throws. Documented honestly in an "Advanced: the strategy + registry" section instead of faking a working example; `describe`/`methods()` cover the + same ground for actual users. All other pages' listed symbols do appear and execute, + including three gaps caught and closed after the first full build (`has_option` on + `options.md`, `methods()` on `explicit-mode.md`, `build_initial_guess` on + `initial-guess.md`). +- [x] `gpu.md` is honest about not executing in CI. Kept `Draft = true` (the only page in this + section that does), with a note at the top explaining why — confirmed live that even + loading `CUDA`/`MadNLPGPU` in this dev environment isn't enough to construct + `MadNLP{GPU}()` (a missing-extension error persists even after `using MadNLPGPU`, + apparently needing real hardware to fully resolve). +- [x] Mode detection **by type** is stated on `overview.md`, not only in the advanced page — + and demonstrated live there via the mixed-mode `IncorrectArgument`. + +**Also found and fixed, beyond the checklist above:** +- A real inconsistency between descriptive and explicit mode when both `init=` and + `initial_guess=` are supplied at once: explicit mode throws a clear "Conflicting aliases" + error; descriptive mode (the common case) silently consumes `initial_guess` and lets the + leftover `init` fall through to strategy-option routing, producing a confusing "unknown + option `:init`" error instead. Verified live under the correct dev `LOAD_PATH` (a first pass + of ad-hoc testing had accidentally exercised the *registered* OptimalControl package instead + of this worktree's source — re-verified everything once the mistake was caught). Documented + as-is on `initial-guess.md` rather than silently smoothed over; not fixed in `src/` since + it's outside this PR's declared scope (only the two docstring fixes were). diff --git a/docs/reports/README.md b/docs/reports/README.md index 93a9a3583..07e586f11 100644 --- a/docs/reports/README.md +++ b/docs/reports/README.md @@ -53,7 +53,7 @@ Status legend: ⬜ not started · 🟡 in progress · ✅ merged | 3 | [`feat: deprecation shims`](https://github.com/control-toolbox/OptimalControl.jl/pull/855) | `src/deprecated.jl` | [`10`](10-migration.md) §1 | 1 | ✅ | | 4 | [`docs: API reference`](https://github.com/control-toolbox/OptimalControl.jl/pull/856) | API reference | [`09`](09-api-reference.md) | 2 | ✅ | | 5 | [`docs: modelling`](https://github.com/control-toolbox/OptimalControl.jl/pull/865) | Modelling | [`03`](03-modelling.md) | 2 | 🟡 in review | -| 6 | `docs: solve` | Solve (direct) | [`04`](04-solve-direct.md) | 5 | ⬜ | +| 6 | `docs: solve` | Solve (direct) | [`04`](04-solve-direct.md) | 5 | 🟡 branch ready, build verified green, not yet opened as a PR | | 7 | `docs: results` | Results | [`07`](07-results.md) | 6 | ⬜ | | 8 | `docs: flows` | Flows (indirect) | [`05`](05-flows-indirect.md) | 6 | ⬜ | | 9 | `docs: geometry` | Geometry | [`06`](06-geometry.md) | 8 | ⬜ | diff --git a/docs/src/assets/Manifest.toml b/docs/src/assets/Manifest.toml index 028600093..57c38c211 100644 --- a/docs/src/assets/Manifest.toml +++ b/docs/src/assets/Manifest.toml @@ -101,9 +101,9 @@ version = "1.1.2" [[deps.ArrayInterface]] deps = ["Adapt", "LinearAlgebra"] -git-tree-sha1 = "b79a0bd275c2036b3ab9ed42ddef6f5eb48e1902" +git-tree-sha1 = "13f3b228c230ef0b4ecafd73c8ca9e99987ca692" uuid = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" -version = "7.29.0" +version = "7.30.0" [deps.ArrayInterface.extensions] ArrayInterfaceAMDGPUExt = "AMDGPU" @@ -309,15 +309,15 @@ version = "0.4.34-beta" [[deps.CUDA]] deps = ["CUDACore", "CUDATools", "Reexport", "cuBLAS", "cuFFT", "cuRAND", "cuSOLVER", "cuSPARSE"] -git-tree-sha1 = "1e543921b03c9f373e795d76205924deb048ac84" +git-tree-sha1 = "81a0dd64b26997d54ecce591656ffcf6c55e6b87" uuid = "052768ef-5323-5732-b1bb-66c8b64840ba" -version = "6.2.1" +version = "6.3.0" [[deps.CUDACore]] deps = ["Adapt", "BFloat16s", "CEnum", "CUDA_Compiler_jll", "CUDA_Driver_jll", "CUDA_Runtime_Discovery", "CUDA_Runtime_jll", "ExprTools", "GPUArrays", "GPUCompiler", "GPUToolbox", "KernelAbstractions", "LLVM", "LLVMLoopInfo", "LazyArtifacts", "Libdl", "LinearAlgebra", "Logging", "NVPTX_LLVM_Backend_jll", "PrecompileTools", "Preferences", "Printf", "Random", "Random123", "RandomNumbers", "StaticArrays"] -git-tree-sha1 = "fb8744c38decc0247c71646020cddd80d0063e4b" +git-tree-sha1 = "ef92f2c28a80631ea7e5217f1ea678c9d236f1de" uuid = "bd0ed864-bdfe-4181-a5ed-ce625a5fdea2" -version = "6.2.1" +version = "6.3.0" weakdeps = ["CUDA", "ChainRulesCore", "EnzymeCore", "SpecialFunctions"] [deps.CUDACore.extensions] @@ -327,15 +327,15 @@ weakdeps = ["CUDA", "ChainRulesCore", "EnzymeCore", "SpecialFunctions"] [[deps.CUDATools]] deps = ["CUDACore", "CUDA_Compiler_jll", "CUPTI", "Crayons", "GPUCompiler", "LLVM", "NVML", "NVTX", "PrecompileTools", "Preferences", "PrettyTables", "Printf", "Statistics", "demumble_jll"] -git-tree-sha1 = "e7e94c3ac5cb939935948808e53209f3c5263a95" +git-tree-sha1 = "61767cc4ca369fefed2e382726b8e3b6d1e33852" uuid = "9ec180c6-1c07-47c7-9e6e-ebefa4d1f6d0" -version = "6.2.1" +version = "6.3.0" [[deps.CUDA_Compiler_jll]] -deps = ["Artifacts", "CUDA_Driver_jll", "CUDA_Runtime_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "TOML"] -git-tree-sha1 = "c32d22f2f563ce192c88a44b09c2b569f1e7a980" +deps = ["Artifacts", "CUDA_Driver_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "TOML"] +git-tree-sha1 = "96d67516639108f7062bd6f580b966ef740c0ca3" uuid = "d1e2174e-dfdc-576e-b43e-73b79eb1aca8" -version = "0.4.4+1" +version = "0.5.1+0" [[deps.CUDA_Driver_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] @@ -350,16 +350,16 @@ uuid = "1af6417a-86b4-443c-805f-a4643ffb695f" version = "2.1.0" [[deps.CUDA_Runtime_jll]] -deps = ["Artifacts", "CUDA_Driver_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "TOML"] -git-tree-sha1 = "2e0352eb2a8321e46e1de54059bed9be8fd9391c" +deps = ["Artifacts", "CUDA_Compiler_jll", "CUDA_Driver_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "TOML"] +git-tree-sha1 = "50d0cc99094dd67d7ef08783d7b5c0d4fc7d1c88" uuid = "76a88914-d11a-5bdc-97e0-2f5a05c973a2" -version = "0.23.0+1" +version = "0.24.1+0" [[deps.CUPTI]] deps = ["CEnum", "CUDACore", "CUDA_Runtime_Discovery", "CUDA_Runtime_jll", "GPUToolbox"] -git-tree-sha1 = "167e95cf9b83ab1cd2af1362faf7aebe0b193e27" +git-tree-sha1 = "03fe2be776513a8b6913301c9a27bfe9eded43a6" uuid = "9e67e8f6-ba02-4b6c-a7db-3b11ae1e7ab7" -version = "6.2.1" +version = "6.3.0" [[deps.Cairo_jll]] deps = ["Artifacts", "Bzip2_jll", "CompilerSupportLibraries_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "JLLWrappers", "Libdl", "Pixman_jll", "Xorg_libXext_jll", "Xorg_libXrender_jll", "Zlib_jll", "libpng_jll"] @@ -453,6 +453,11 @@ weakdeps = ["Dates", "LinearAlgebra"] [deps.Compat.extensions] CompatLinearAlgebraExt = "LinearAlgebra" +[[deps.CompilerCaching]] +git-tree-sha1 = "3c31a4b8fbd0281c599fa2004b3e6ad6ebe725d5" +uuid = "9db33cc3-5358-4881-8759-fa4194144afd" +version = "0.4.2" + [[deps.CompilerSupportLibraries_jll]] deps = ["Artifacts", "Libdl"] uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" @@ -538,9 +543,9 @@ version = "1.9.1" [[deps.DiffEqBase]] deps = ["ArrayInterface", "BracketingNonlinearSolve", "ConcreteStructs", "DocStringExtensions", "FastBroadcast", "FastClosures", "FastPower", "FunctionWrappers", "FunctionWrappersWrappers", "LinearAlgebra", "Logging", "Markdown", "MuladdMacro", "PrecompileTools", "Printf", "RecursiveArrayTools", "Reexport", "RespecializeParams", "SciMLBase", "SciMLLogging", "SciMLOperators", "SciMLStructures", "Setfield", "StaticArraysCore", "SymbolicIndexingInterface", "TruncatedStacktraces"] -git-tree-sha1 = "c0484d1fe39e1d86216d7a4eac3eb827b916ef55" +git-tree-sha1 = "3c6ceb2c59e132fd276ca16fdb0ecd043791d22d" uuid = "2b5f629d-d688-5b77-993f-72d75c75574e" -version = "7.15.0" +version = "7.18.0" [deps.DiffEqBase.extensions] DiffEqBaseCUDAExt = "CUDA" @@ -593,9 +598,9 @@ version = "1.16.0" [[deps.DifferentiationInterface]] deps = ["ADTypes", "LinearAlgebra"] -git-tree-sha1 = "dbd46a5cd0e79a97438b0ebbec42e744e8f436fe" +git-tree-sha1 = "0693d8b0a4608ff289d228ab4c598df5894845cd" uuid = "a0c0ee7d-e4b9-4e03-894e-1c5f64a51d63" -version = "0.7.20" +version = "0.7.21" [deps.DifferentiationInterface.extensions] DifferentiationInterfaceChainRulesCoreExt = "ChainRulesCore" @@ -908,9 +913,9 @@ version = "1.1.3" [[deps.FunctionWrappersWrappers]] deps = ["FunctionWrappers", "PrecompileTools", "SciMLPublic"] -git-tree-sha1 = "daced009d54a7cf502a9b5ed2f615c341f78af6f" +git-tree-sha1 = "2bcce3ad6f6977d617928d7707fdc86ac83cce03" uuid = "77dc65aa-8811-40c2-897b-53d922fa7daf" -version = "1.12.1" +version = "1.13.0" [deps.FunctionWrappersWrappers.extensions] FunctionWrappersWrappersEnzymeExt = ["Enzyme", "EnzymeCore"] @@ -934,9 +939,9 @@ version = "3.4.1+1" [[deps.GPUArrays]] deps = ["Adapt", "GPUArraysCore", "KernelAbstractions", "LLVM", "LinearAlgebra", "Printf", "Random", "Reexport", "ScopedValues", "Serialization", "SparseArrays", "Statistics"] -git-tree-sha1 = "d9da1147842d01fca43d076310bbef82f4a671b2" +git-tree-sha1 = "4939facbe63151c92b622dad9f9bb2b639b5bbde" uuid = "0c68f7d7-f131-5f86-a1c3-88cf8149b2d7" -version = "11.5.10" +version = "11.5.12" weakdeps = ["JLD2"] [deps.GPUArrays.extensions] @@ -949,13 +954,17 @@ uuid = "46192b85-c4d5-4398-a991-12ede77f4527" version = "0.2.0" [[deps.GPUCompiler]] -deps = ["ExprTools", "InteractiveUtils", "LLVM", "Libdl", "Logging", "PrecompileTools", "Preferences", "REPL", "Scratch", "Serialization", "TOML", "Tracy", "UUIDs"] -git-tree-sha1 = "5e54ec63c34bcc878558b173c411b8efe6b08344" +deps = ["CompilerCaching", "ExprTools", "InteractiveUtils", "LLVM", "Libdl", "Logging", "PrecompileTools", "Preferences", "REPL", "TOML", "Tracy", "UUIDs", "tree_sitter_gcn_jll", "tree_sitter_llvm_jll", "tree_sitter_ptx_jll", "tree_sitter_spirv_jll"] +git-tree-sha1 = "e579d54a3baa3757295f5e75c5ed444ab7505f85" uuid = "61eb1bfa-7361-4325-ad38-22787b887f55" -version = "1.23.0" +version = "2.2.1" + + [deps.GPUCompiler.extensions] + HighlightsExt = "Highlights" [deps.GPUCompiler.weakdeps] AMDGPU_LLVM_Backend_jll = "cc5c0156-bd05-5a77-8a68-bb0aafb29019" + Highlights = "eafb193a-b7ab-5a9e-9068-77385905fa72" LLVMDowngrader_jll = "f52de702-fb25-5922-94ba-81dd59b07444" NVPTX_LLVM_Backend_jll = "ef6e0fe3-e6ef-59c0-bde6-4989574699e0" @@ -1237,11 +1246,23 @@ git-tree-sha1 = "17b94ecafcfa45e8360a4fc9ca6b583b049e4e37" uuid = "88015f11-f218-50d7-93a8-a6af411a945d" version = "4.1.0+0" +[[deps.LHLFactorization]] +deps = ["LinearAlgebra"] +git-tree-sha1 = "3317936e66b2ab663af6908abd48f7d8f11e3ee8" +uuid = "2faa5264-e118-4071-8864-e10559f68c7c" +version = "2.0.0" + + [deps.LHLFactorization.extensions] + LHLFactorizationPolyesterExt = "Polyester" + + [deps.LHLFactorization.weakdeps] + Polyester = "f517fe37-dbe3-4b94-8317-1923a5111588" + [[deps.LLVM]] deps = ["CEnum", "LLVMExtra_jll", "Libdl", "PrecompileTools", "Preferences", "Printf", "Unicode"] -git-tree-sha1 = "c16e849ea9402330fd9945138bc4247b2d603848" +git-tree-sha1 = "5f708df9df936ff026cc6232e187ddf93f343430" uuid = "929cbde3-209d-540e-8aea-75f648917ca0" -version = "9.12.0" +version = "9.13.0" weakdeps = ["BFloat16s"] [deps.LLVM.extensions] @@ -1249,9 +1270,9 @@ weakdeps = ["BFloat16s"] [[deps.LLVMExtra_jll]] deps = ["Artifacts", "JLLWrappers", "LazyArtifacts", "Libdl", "TOML"] -git-tree-sha1 = "7304136286a564be0f909ecf209d1105e6ddf613" +git-tree-sha1 = "23caf34b74ef5d02b03f2c2853724df7cf6ec3ca" uuid = "dad2f222-ce93-54a1-a47d-0025e8a3acab" -version = "0.0.45+0" +version = "0.0.46+0" [[deps.LLVMLoopInfo]] git-tree-sha1 = "2e5c102cfc41f48ae4740c7eca7743cc7e7b75ea" @@ -1265,15 +1286,15 @@ uuid = "1d63c593-3942-5779-bab2-d838dc0a180e" version = "22.1.7+0" [[deps.LaTeXStrings]] -git-tree-sha1 = "dda21b8cbd6a6c40d9d02a73230f9d70fed6918c" +git-tree-sha1 = "f88f3ccef05a6a72a0cf0ed417c8fd68530f4ab2" uuid = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" -version = "1.4.0" +version = "1.4.1" [[deps.Latexify]] deps = ["Format", "Ghostscript_jll", "InteractiveUtils", "LaTeXStrings", "MacroTools", "Markdown", "OrderedCollections", "Requires"] -git-tree-sha1 = "24390f715ff0795a1c4b912d788f18c52c6abd19" +git-tree-sha1 = "df7566479bd64f20bd16b09960145e70160ffb3b" uuid = "23fbe1c1-3f47-55db-b15f-69d7ec21a316" -version = "0.16.11" +version = "0.16.12" [deps.Latexify.extensions] DataFramesExt = "DataFrames" @@ -1370,9 +1391,9 @@ version = "2.42.0+0" [[deps.LineSearch]] deps = ["ADTypes", "CommonSolve", "ConcreteStructs", "FastClosures", "LinearAlgebra", "MaybeInplace", "PrecompileTools", "SciMLBase", "SciMLJacobianOperators", "StaticArraysCore"] -git-tree-sha1 = "2e05027f5a68891d997fcad60d11ce48d09208d0" +git-tree-sha1 = "847ae0c5cd85cb3d2c97f20babedd2fcf540bf2d" uuid = "87fe0de2-c867-4266-b59a-2f0a94fc965b" -version = "0.1.14" +version = "0.1.15" [deps.LineSearch.extensions] LineSearchLineSearchesExt = "LineSearches" @@ -1412,10 +1433,10 @@ version = "2.14.2" TSVD = "9449cd9e-2762-5aa3-a617-5413e99d722e" [[deps.LinearSolve]] -deps = ["AMD", "ArrayInterface", "ConcreteStructs", "DocStringExtensions", "EnumX", "GPUArraysCore", "InteractiveUtils", "Krylov", "Libdl", "LinearAlgebra", "MKL_jll", "Markdown", "OpenBLAS_jll", "PrecompileTools", "Preferences", "PureKLU", "RecursiveArrayTools", "Reexport", "SciMLBase", "SciMLLogging", "SciMLOperators", "Setfield", "SparseArrays", "SparseColumnPivotedQR", "StaticArraysCore"] -git-tree-sha1 = "8ea4977471ea6e9b507e5e98d9cfcde3234f33e9" +deps = ["AMD", "ArrayInterface", "ConcreteStructs", "DocStringExtensions", "EnumX", "GPUArraysCore", "InteractiveUtils", "Krylov", "LHLFactorization", "Libdl", "LinearAlgebra", "MKL_jll", "Markdown", "OpenBLAS_jll", "PrecompileTools", "Preferences", "PureKLU", "RecursiveArrayTools", "Reexport", "SciMLBase", "SciMLLogging", "SciMLOperators", "SciMLStructures", "Setfield", "SparseArrays", "SparseColumnPivotedQR", "StaticArraysCore"] +git-tree-sha1 = "56630d8d23e3e8ee1ba153164faeac8d5758d2f1" uuid = "7ed4a6bd-45f5-4d41-b270-4a48e9bafcae" -version = "5.10.0" +version = "5.12.0" [deps.LinearSolve.extensions] LinearSolveAMDGPUExt = "AMDGPU" @@ -1714,9 +1735,9 @@ version = "0.8.0" [[deps.NVML]] deps = ["CEnum", "CUDACore", "GPUToolbox", "Libdl"] -git-tree-sha1 = "c170a13e18b68b6e1c5853f17afbf84fa9d50418" +git-tree-sha1 = "82aefa4dfbcb769c3fd8751b231df5eefbd7f3b0" uuid = "611af6d1-644e-4c5d-bd58-854d7d1254b9" -version = "6.2.1" +version = "6.3.0" [[deps.NVPTX_LLVM_Backend_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Zlib_jll"] @@ -1791,9 +1812,9 @@ version = "4.27.0" [[deps.NonlinearSolveBase]] deps = ["ADTypes", "Adapt", "ArrayInterface", "CommonSolve", "Compat", "ConcreteStructs", "DifferentiationInterface", "EnumX", "EnzymeCore", "FastClosures", "FunctionWrappers", "FunctionWrappersWrappers", "LinearAlgebra", "LogExpFunctions", "Markdown", "MaybeInplace", "PreallocationTools", "PrecompileTools", "Preferences", "Printf", "RecursiveArrayTools", "RespecializeParams", "SciMLBase", "SciMLJacobianOperators", "SciMLLogging", "SciMLOperators", "SciMLStructures", "Setfield", "StaticArraysCore", "SymbolicIndexingInterface", "TimerOutputs"] -git-tree-sha1 = "b7b455a41da55a45b0f050c119adb15da39db893" +git-tree-sha1 = "1b0ac1b7f679bde40b6db970ff8a791080b17222" uuid = "be0214bd-f91f-a760-ac4e-3421ce2b2da0" -version = "2.45.0" +version = "2.46.0" [deps.NonlinearSolveBase.extensions] NonlinearSolveBaseBandedMatricesExt = "BandedMatrices" @@ -1917,9 +1938,9 @@ version = "7.6.0" [[deps.OrdinaryDiffEqBDF]] deps = ["ADTypes", "ArrayInterface", "DiffEqBase", "FastBroadcast", "LinearAlgebra", "MacroTools", "MuladdMacro", "OrdinaryDiffEqCore", "OrdinaryDiffEqDifferentiation", "OrdinaryDiffEqNonlinearSolve", "OrdinaryDiffEqSDIRK", "PrecompileTools", "Preferences", "RecursiveArrayTools", "Reexport", "SciMLBase", "TruncatedStacktraces"] -git-tree-sha1 = "b21629b54e21c37605b53aa2c4b0ac1f90fc349d" +git-tree-sha1 = "e59d1ebafb95ca26a048821b825bad9a276dff7e" uuid = "6ad6398a-0878-4a85-9266-38940aa047c8" -version = "2.4.2" +version = "2.4.3" [[deps.OrdinaryDiffEqCore]] deps = ["ADTypes", "Accessors", "Adapt", "ArrayInterface", "BinaryHeaps", "CommonSolve", "ConstructionBase", "DiffEqBase", "DocStringExtensions", "EnumX", "EnzymeCore", "FastBroadcast", "FastClosures", "FastPower", "FindFirstFunctions", "FunctionWrappers", "FunctionWrappersWrappers", "InteractiveUtils", "LinearAlgebra", "Logging", "MacroTools", "MuladdMacro", "PrecompileTools", "Preferences", "Printf", "Random", "RecursiveArrayTools", "Reexport", "SciMLBase", "SciMLLogging", "SciMLOperators", "SciMLStructures", "SymbolicIndexingInterface", "TruncatedStacktraces"] @@ -1939,15 +1960,15 @@ version = "4.14.3" [[deps.OrdinaryDiffEqDefault]] deps = ["ADTypes", "DiffEqBase", "EnumX", "LinearAlgebra", "LinearSolve", "OrdinaryDiffEqBDF", "OrdinaryDiffEqCore", "OrdinaryDiffEqRosenbrock", "OrdinaryDiffEqTsit5", "OrdinaryDiffEqVerner", "PrecompileTools", "Preferences", "SciMLBase"] -git-tree-sha1 = "577f453cd7cf893cef584f7372b78a59e227416d" +git-tree-sha1 = "7777fb1f20ca7bf4ad1951f7271b349830f80b00" uuid = "50262376-6c5a-4cf5-baba-aaf4f84d72d7" -version = "2.4.4" +version = "2.4.5" [[deps.OrdinaryDiffEqDifferentiation]] deps = ["ADTypes", "ArrayInterface", "ConcreteStructs", "ConstructionBase", "DiffEqBase", "DifferentiationInterface", "FastBroadcast", "FiniteDiff", "ForwardDiff", "FunctionWrappersWrappers", "LinearAlgebra", "LinearSolve", "OrdinaryDiffEqCore", "SciMLBase", "SciMLOperators", "SparseMatrixColorings", "StaticArraysCore"] -git-tree-sha1 = "d9d3cc8c585a8698548a9a7349590ac0455936ea" +git-tree-sha1 = "3a51475ae4dbf309d2e23f6a433e74ce582761e8" uuid = "4302a76b-040a-498a-8c04-15b101fed76b" -version = "3.7.0" +version = "3.9.0" weakdeps = ["SparseArrays"] [deps.OrdinaryDiffEqDifferentiation.extensions] @@ -1961,9 +1982,9 @@ version = "2.8.0" [[deps.OrdinaryDiffEqRosenbrock]] deps = ["ADTypes", "ArrayInterface", "DiffEqBase", "DifferentiationInterface", "FastBroadcast", "FiniteDiff", "ForwardDiff", "LinearAlgebra", "LinearSolve", "MacroTools", "MuladdMacro", "OrdinaryDiffEqCore", "OrdinaryDiffEqDifferentiation", "OrdinaryDiffEqRosenbrockTableaus", "PrecompileTools", "Preferences", "RecursiveArrayTools", "Reexport", "SciMLBase"] -git-tree-sha1 = "9315d0c76c4411c47f037e5f16f90135abe4607f" +git-tree-sha1 = "e07c014b5733461c43cb5d18a3f2c95600ce6f78" uuid = "43230ef6-c299-4910-a778-202eb28ce4ce" -version = "2.6.5" +version = "2.6.6" [[deps.OrdinaryDiffEqRosenbrockTableaus]] git-tree-sha1 = "0ecd1c905c82963f8748e82b6d2bf16d2b1bdf2b" @@ -1984,9 +2005,9 @@ version = "2.1.3" [[deps.OrdinaryDiffEqVerner]] deps = ["DiffEqBase", "FastBroadcast", "LinearAlgebra", "MuladdMacro", "OrdinaryDiffEqCore", "PrecompileTools", "Preferences", "RecursiveArrayTools", "Reexport", "SciMLBase", "TruncatedStacktraces"] -git-tree-sha1 = "f7ffc12bf6572a1c55fc58d297d864e6d6cb99e6" +git-tree-sha1 = "c2fe84260c600a2c9a0dd2c89e315699ff9d018e" uuid = "79d7bb75-1356-48c1-b8c0-6832512096c2" -version = "2.2.2" +version = "2.3.0" [[deps.PCRE2_jll]] deps = ["Artifacts", "Libdl"] @@ -2084,9 +2105,9 @@ version = "1.5.2" [[deps.PrettyTables]] deps = ["Crayons", "LaTeXStrings", "Markdown", "PrecompileTools", "Printf", "REPL", "Reexport", "StringManipulation", "Tables"] -git-tree-sha1 = "4ac881f5432bd93463a41767a814a45245be22b6" +git-tree-sha1 = "1b8aa19f229b1cea7fc93874a52e49db6a854450" uuid = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d" -version = "3.4.6" +version = "3.4.8" [deps.PrettyTables.extensions] PrettyTablesExcelExt = "XLSX" @@ -2182,9 +2203,9 @@ version = "0.6.12" [[deps.RecursiveArrayTools]] deps = ["Adapt", "ArrayInterface", "GPUArraysCore", "LinearAlgebra", "PrecompileTools", "RecipesBase", "SciMLPublic", "SciMLStructures", "StaticArraysCore", "SymbolicIndexingInterface"] -git-tree-sha1 = "ed53f3c9075d1317f1f6c1cf8636c88b24ab2dd6" +git-tree-sha1 = "58c6496ceca7aafbd8534602f9a901c3ffc4b709" uuid = "731186ca-8d62-57ce-b412-fbd966d074cd" -version = "4.4.0" +version = "4.5.0" [deps.RecursiveArrayTools.extensions] RecursiveArrayToolsCUDAExt = "CUDA" @@ -2272,10 +2293,10 @@ uuid = "319450e9-13b8-58e8-aa9f-8fd1420848ab" version = "2025.9.18+0" [[deps.SciMLBase]] -deps = ["ADTypes", "Accessors", "Adapt", "ArrayInterface", "CommonSolve", "ConstructionBase", "Distributed", "DocStringExtensions", "EnumX", "FindFirstFunctions", "FunctionWrappersWrappers", "IteratorInterfaceExtensions", "LinearAlgebra", "Logging", "LoggingExtras", "Markdown", "PreallocationTools", "PrecompileTools", "Preferences", "Printf", "Random", "RecipesBase", "RecursiveArrayTools", "RuntimeGeneratedFunctions", "SciMLLogging", "SciMLOperators", "SciMLPublic", "SciMLStructures", "StaticArraysCore", "Statistics", "SymbolicIndexingInterface"] -git-tree-sha1 = "55e5c9c07804723db7924afdebfc9075294c71cc" +deps = ["ADTypes", "Accessors", "Adapt", "ArrayInterface", "CommonSolve", "ConstructionBase", "Distributed", "DocStringExtensions", "EnumX", "FindFirstFunctions", "FunctionWrappersWrappers", "IteratorInterfaceExtensions", "LinearAlgebra", "Logging", "LoggingExtras", "Markdown", "PreallocationTools", "PrecompileTools", "Preferences", "Printf", "Random", "RecipesBase", "RecursiveArrayTools", "RuntimeGeneratedFunctions", "SciMLOperators", "SciMLPublic", "SciMLStructures", "StaticArraysCore", "Statistics", "SymbolicIndexingInterface"] +git-tree-sha1 = "ecef3adefb299e19a08d03ebb60d7c2a8e5db3d9" uuid = "0bca4576-84f4-4d90-8ffe-ffa030f20462" -version = "3.47.0" +version = "3.49.1" [deps.SciMLBase.extensions] SciMLBaseChainRulesCoreExt = "ChainRulesCore" @@ -2339,9 +2360,9 @@ weakdeps = ["Tracy"] [[deps.SciMLOperators]] deps = ["Accessors", "Adapt", "ArrayInterface", "DocStringExtensions", "LinearAlgebra", "SciMLPublic"] -git-tree-sha1 = "144473d0a737d9c1e1bf2dc5ea824dc1be864f33" +git-tree-sha1 = "fdba76643b52cc34baeda645b7a0c9a7a89db01a" uuid = "c0aeaf25-5076-4817-a8d5-81caf7dfa961" -version = "1.27.0" +version = "1.28.0" [deps.SciMLOperators.extensions] SciMLOperatorsLoopVectorizationExt = "LoopVectorization" @@ -2490,9 +2511,9 @@ version = "0.4.27" [[deps.SpecialFunctions]] deps = ["IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"] -git-tree-sha1 = "c3ac026e735264e9bdc6a9bcbd1b1e781b36e3bc" +git-tree-sha1 = "429071b23f4c9a13fb6582f807cc2ef454082408" uuid = "276daf66-3868-5448-9aa4-cd146d93841b" -version = "2.8.3" +version = "2.9.0" weakdeps = ["ChainRulesCore"] [deps.SpecialFunctions.extensions] @@ -2544,9 +2565,9 @@ version = "0.34.12" [[deps.StringManipulation]] deps = ["PrecompileTools"] -git-tree-sha1 = "8a90c1d77c3277a5d43b83927b3cbe2c70a37484" +git-tree-sha1 = "773065c6e0e903924a9d838259be74338422aef2" uuid = "892a3eda-7b42-436c-8928-eab12a02cf0e" -version = "0.4.7" +version = "0.5.0" [[deps.StructTypes]] deps = ["Dates", "UUIDs"] @@ -2707,9 +2728,9 @@ uuid = "396d5378-14f1-5ab1-981d-48acd51740ed" version = "2.8.0+0" [[deps.UnsafeAtomics]] -git-tree-sha1 = "0f30765c32d66d58e41f4cb5624d4fc8a82ec13b" +git-tree-sha1 = "21b39bfb1fab6156b61fbcba4c86c57b6216d2c3" uuid = "013be700-e6cd-48c3-b4a1-df204f14c38f" -version = "0.3.1" +version = "0.3.2" weakdeps = ["LLVM"] [deps.UnsafeAtomics.extensions] @@ -2901,9 +2922,9 @@ version = "1.5.7+1" [[deps.cuBLAS]] deps = ["Adapt", "BFloat16s", "CEnum", "CUDACore", "CUDA_Runtime_Discovery", "CUDA_Runtime_jll", "GPUArrays", "GPUToolbox", "LLVM", "LinearAlgebra"] -git-tree-sha1 = "956ec13ca1fecd0f3efddf5fbba5b49f5734fda8" +git-tree-sha1 = "09ee8f27305e9778a1f88104e901864a522198ab" uuid = "182d3088-87b7-4494-8cad-fc6afaa545bc" -version = "6.2.1" +version = "6.3.0" weakdeps = ["EnzymeCore"] [deps.cuBLAS.extensions] @@ -2911,27 +2932,27 @@ weakdeps = ["EnzymeCore"] [[deps.cuFFT]] deps = ["AbstractFFTs", "CEnum", "CUDACore", "CUDA_Runtime_Discovery", "CUDA_Runtime_jll", "GPUToolbox", "LinearAlgebra", "Reexport"] -git-tree-sha1 = "cdb0f4950da00258c4b7287eebf938f8a0c4c708" +git-tree-sha1 = "3a3d2270749e080dcd92fffa76d5842d7179a219" uuid = "533571aa-0936-420e-b4be-9c66f5f626ca" -version = "6.2.1" +version = "6.3.0" [[deps.cuRAND]] deps = ["CEnum", "CUDACore", "CUDA_Runtime_Discovery", "CUDA_Runtime_jll", "GPUToolbox", "Random", "Random123", "RandomNumbers"] -git-tree-sha1 = "f35b68bd1b4ed8181c976319573cf7f9267443ef" +git-tree-sha1 = "811865356107ef685b269b0cf18d5679d0404f15" uuid = "20fd9a0b-12d5-4c2f-a8af-7c34e9e60431" -version = "6.2.1" +version = "6.3.0" [[deps.cuSOLVER]] deps = ["CEnum", "CUDACore", "CUDA_Runtime_Discovery", "CUDA_Runtime_jll", "GPUToolbox", "LinearAlgebra", "SparseArrays", "cuBLAS", "cuSPARSE"] -git-tree-sha1 = "ff74a5683a5fd786a05b48f1438bf6fcdd959ac3" +git-tree-sha1 = "bb6c154ba992c116732fff8024ba78587efac94e" uuid = "887afef0-6a32-4de5-add4-7827692ba8fc" -version = "6.2.1" +version = "6.3.0" [[deps.cuSPARSE]] deps = ["Adapt", "CEnum", "CUDACore", "CUDA_Runtime_Discovery", "CUDA_Runtime_jll", "GPUArrays", "GPUToolbox", "KernelAbstractions", "LinearAlgebra", "SparseArrays"] -git-tree-sha1 = "df9bed2b4b891cf837e35c6d03707548233edd50" +git-tree-sha1 = "0b3b045a0ff35e8fbd6687d2f6610bbead7e40c8" uuid = "b26da814-b3bc-49ef-b0ee-c816305aa060" -version = "6.2.1" +version = "6.3.0" [deps.cuSPARSE.extensions] SparseMatricesCSRExt = "SparseMatricesCSR" @@ -3044,6 +3065,30 @@ deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" version = "17.7.0+0" +[[deps.tree_sitter_gcn_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "7aea9f731552967bda93cb7cbf6925de65bc38d5" +uuid = "8b5cbfcf-8811-596e-8e87-0e51e05ee4b2" +version = "0.1.0+0" + +[[deps.tree_sitter_llvm_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "2fb7d0e3a8abf532fa922766ca130bf814df1e70" +uuid = "44208993-ee63-5069-9443-8e43b04a9b30" +version = "1.1.0+0" + +[[deps.tree_sitter_ptx_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "e6ad264eebe3e34f03bca6b5e811d80e0f4797a2" +uuid = "71e3f6e6-c059-5e7c-a2f2-d560fdad7ce5" +version = "0.1.0+0" + +[[deps.tree_sitter_spirv_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "7d8c03949bab84b23fe0500d89979e786d54a653" +uuid = "f0e86581-c468-54df-a4de-3266e11a3c86" +version = "0.1.0+0" + [[deps.x264_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] git-tree-sha1 = "14cc7083fc6dff3cc44f2bc435ee96d06ed79aa7" From 9549793677674c19822c24ac5c5b25fb8c8a7890 Mon Sep 17 00:00:00 2001 From: Olivier Cots Date: Thu, 20 Aug 2026 21:56:54 +0200 Subject: [PATCH 10/10] docs(reports): link PR 6 to #866 on the work board Co-Authored-By: Claude Sonnet 5 --- docs/reports/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reports/README.md b/docs/reports/README.md index 07e586f11..a39766462 100644 --- a/docs/reports/README.md +++ b/docs/reports/README.md @@ -53,7 +53,7 @@ Status legend: ⬜ not started · 🟡 in progress · ✅ merged | 3 | [`feat: deprecation shims`](https://github.com/control-toolbox/OptimalControl.jl/pull/855) | `src/deprecated.jl` | [`10`](10-migration.md) §1 | 1 | ✅ | | 4 | [`docs: API reference`](https://github.com/control-toolbox/OptimalControl.jl/pull/856) | API reference | [`09`](09-api-reference.md) | 2 | ✅ | | 5 | [`docs: modelling`](https://github.com/control-toolbox/OptimalControl.jl/pull/865) | Modelling | [`03`](03-modelling.md) | 2 | 🟡 in review | -| 6 | `docs: solve` | Solve (direct) | [`04`](04-solve-direct.md) | 5 | 🟡 branch ready, build verified green, not yet opened as a PR | +| 6 | [`docs: solve`](https://github.com/control-toolbox/OptimalControl.jl/pull/866) | Solve (direct) | [`04`](04-solve-direct.md) | 5 | 🟡 in review | | 7 | `docs: results` | Results | [`07`](07-results.md) | 6 | ⬜ | | 8 | `docs: flows` | Flows (indirect) | [`05`](05-flows-indirect.md) | 6 | ⬜ | | 9 | `docs: geometry` | Geometry | [`06`](06-geometry.md) | 8 | ⬜ |