From bc9c85b54dfbe4acf977a2afdb783726f80a704e Mon Sep 17 00:00:00 2001 From: Olivier Cots Date: Sun, 23 Aug 2026 00:04:44 +0200 Subject: [PATCH 1/8] docs(geometry): write the overview page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Maps the four-operation toolkit (Lift, ad, Poisson, ∂ₜ) that moved from CTFlows to CTLie in v2.1.0-beta, and demonstrates the bridge identity Poisson(Lift(X), Lift(Y)) ≈ Lift(ad(X, Y)) on a genuinely nonlinear pair of vector fields. Co-Authored-By: Claude Sonnet 5 --- docs/src/geometry/overview.md | 89 ++++++++++++++++++++++++++++++++++- 1 file changed, 87 insertions(+), 2 deletions(-) diff --git a/docs/src/geometry/overview.md b/docs/src/geometry/overview.md index 4cdd3e74c..fb736df6d 100644 --- a/docs/src/geometry/overview.md +++ b/docs/src/geometry/overview.md @@ -1,4 +1,89 @@ # [Overview](@id geometry-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 +``` + +Some flows can't be built directly from an optimal control problem — the control law itself +has to be *derived* first, for example a singular control on an arc where the usual +maximization condition degenerates. Deriving it needs differential-geometry tools: Lie +derivatives, Lie brackets, Poisson brackets. This section is that toolkit. It moved to its own +package, **CTLie**, in v2.1.0-beta. + +```@example main +using OptimalControl +``` + +## What this is for + +- Computing a **singular control** — the standard chain of iterated Poisson brackets + ($H_{01}$, $H_{001}$, $H_{101}$, ...) that gives $u_{\text{sing}}$ on a singular arc. +- Checking **controllability** via the Lie brackets of the system's vector fields. +- Building a **Hamiltonian from a vector field** — the canonical lift used throughout the + indirect-methods section. + +## The four operations + +| Operation | Signature | What it computes | +| --- | --- | --- | +| [`Lift`](@ref) | `Lift(X)` | the Hamiltonian $H_X(x,p) = \langle p, X(x)\rangle$ of a vector field $X$ | +| [`ad`](@ref) | `ad(X, f)` / `ad(X, Y)` | Lie derivative of a scalar `f`, or Lie bracket of a vector field `Y`, along $X$ | +| [`Poisson`](@ref) | `Poisson(H, G)` | the Poisson bracket of two Hamiltonians | +| [`∂ₜ`](@ref) | `∂ₜ(f)` | the partial time derivative of a non-autonomous `f` | + +## Two vocabularies + +Two kinds of objects appear throughout: **vector fields** live on the state space +($X : x \mapsto X(x)$), **Hamiltonians** live on the cotangent space +($H : (x, p) \mapsto H(x,p)$). `ad` and its bracket operate on the first vocabulary, `Poisson` +on the second. [`Lift`](@ref) is the bridge from one to the other. + +## The bridge identity + +Lifting turns a Lie bracket into a Poisson bracket: $\{H_X, H_Y\} = H_{[X,Y]}$, i.e. +`Poisson(Lift(X), Lift(Y)) ≈ Lift(ad(X, Y))`. It is the single best check that the two halves +of the toolkit agree — not a linear example, where the bracket is trivially zero: + +```@example main +X(x) = [x[1]^2, x[2]^2] +Y(x) = [x[2], -x[1]] + +lhs = Poisson(Lift(X), Lift(Y)) +rhs = Lift(ad(X, Y)) + +x, p = [1.0, 2.0], [3.0, 4.0] +lhs(x, p), rhs(x, p) +``` + +## Autonomous, non-autonomous, variable + +Every operation here takes `is_autonomous::Bool` and `is_variable::Bool` keywords (default +`true`/`false` — time-independent, no extra parameter). Operands that disagree — one +autonomous, one not, say — throw a `PreconditionError` naming both traits explicitly; see +[Lie derivative and Lie bracket](@ref geometry-ad) for the exact message. + +## Automatic differentiation + +Everything here is AD-backed **except `Lift`**, which is a purely algebraic rearrangement +($H(x,p) = p \cdot X(x)$, no derivative involved). See +[Choosing an AD backend](@ref geometry-ad-backend) for how the derivatives themselves are +computed and how to change the backend. + +## Coming from v2.0 + +| v2.0 | v2.1 | +| --- | --- | +| `Lie(X, f)` / `Lie(X, Y)` | `ad(X, f)` / `ad(X, Y)` | +| `X ⋅ f` | `ad(X, f)` — no operator replacement | +| `HamiltonianLift` | `LiftedHamiltonianFunction` (written `OptimalControl.LiftedHamiltonianFunction`) | + +See [Migrating to v2.1](@ref migration) for the full picture, including the throwing shims that +catch the old names. + +## See also + +- [Lifting a vector field](@ref geometry-lift) +- [Lie derivative and Lie bracket](@ref geometry-ad) +- [Poisson bracket](@ref geometry-poisson) +- [The `@Lie` macro](@ref geometry-lie-macro) +- [Choosing an AD backend](@ref geometry-ad-backend) From 921346d48ab4e0cb65472d61d199dbcd45abb669 Mon Sep 17 00:00:00 2001 From: Olivier Cots Date: Sun, 23 Aug 2026 00:04:52 +0200 Subject: [PATCH 2/8] docs(geometry): write the lift page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lift(f::Function) returns an OptimalControl.LiftedHamiltonianFunction, not an AbstractHamiltonian — a silent behavior change from v2.0 flagged with its own warning. Lift(X::AbstractVectorField) still returns a real Hamiltonian; only the plain-function overload changed. Co-Authored-By: Claude Sonnet 5 --- docs/src/geometry/lift.md | 112 +++++++++++++++++++++++++++++++++++++- 1 file changed, 110 insertions(+), 2 deletions(-) diff --git a/docs/src/geometry/lift.md b/docs/src/geometry/lift.md index 259b231ea..11c34c1d5 100644 --- a/docs/src/geometry/lift.md +++ b/docs/src/geometry/lift.md @@ -1,4 +1,112 @@ # [Lift](@id geometry-lift) -!!! 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 +``` + +Given a vector field $X : \mathbb{R}^n \to \mathbb{R}^n$, its **lift** is the Hamiltonian + +```math +H_X(x, p) = \langle p, X(x) \rangle = \sum_{i=1}^n p_i X_i(x). +``` + +It is a purely algebraic construction — no differentiation, no AD. + +```@example main +using OptimalControl +``` + +## From a plain function + +```@example main +X(x) = [x[2], -x[1]] +H = Lift(X) +H([1.0, 2.0], [3.0, 4.0]) +``` + +`H` is an `OptimalControl.LiftedHamiltonianFunction` — a callable, not a `Hamiltonian`: + +```@example main +typeof(H) +``` + +## From a typed vector field + +Lifting a typed `VectorField` instead gives back a real `Hamiltonian`: + +```@example main +XV = VectorField(x -> [x[2], -x[1]]) +HV = Lift(XV) +HV isa AbstractHamiltonian +``` + +```@example main +HV([1.0, 2.0], [3.0, 4.0]) +``` + +## Non-autonomous and variable forms + +```@example main +Xt(t, x) = [t * x[2], -x[1]] +Ht = Lift(Xt; is_autonomous=false) +Ht(2.0, [1.0, 2.0], [3.0, 4.0]) # H(t, x, p) +``` + +```@example main +Xv(x, v) = [x[2] + v, -x[1]] +Hv = Lift(Xv; is_variable=true) +Hv([1.0, 2.0], [3.0, 4.0], 1.0) # H(x, p, v) +``` + +## Which one do I get + +| You lift | You get | Signature | +| --- | --- | --- | +| `f::Function` | `OptimalControl.LiftedHamiltonianFunction` (`<: Function`) | `h(x,p)`, `h(t,x,p)`, `h(x,p,v)`, `h(t,x,p,v)` depending on the keywords | +| `X::AbstractVectorField` | `Hamiltonian` | same call signatures, inherited from `X`'s own traits | + +!!! warning "`Lift(f::Function)` is not an `AbstractHamiltonian`" + + `OptimalControl.LiftedHamiltonianFunction <: Function` only, **not** `<: AbstractHamiltonian` — a change + from v2.0, where the plain-function form and the typed-`VectorField` form both produced the + same kind of object. Any `isa`/`<:` test against the old hierarchy is now quietly wrong: + + ```@example main + H = Lift(X) # X::Function + H isa AbstractHamiltonian + ``` + + Only the plain-`Function` overload changed — `Lift(X::AbstractVectorField)` still returns a + `Hamiltonian`, confirmed above. + +## What you can do with it + +Feed a lift straight into [`Poisson`](@ref) (see +[The bridge identity](@ref geometry-overview)), or into [`Flow`](@ref) to integrate the +associated Hamiltonian system — see +[From Hamiltonians and vector fields](@ref flows-from-hamiltonians). + +## A trap to know about + +Lifting a `HamiltonianVectorField` doesn't work — it already lives on the cotangent space, so +there's nothing left to lift: + +```@example main +hvf = HamiltonianVectorField((x, p) -> (p, -x)) +try + Lift(hvf) +catch e + println(e) +end +``` + +The message talks about `ad`, not `Lift` — both operations share the same internal guard +against `HamiltonianVectorField` operands, so the wording doesn't adapt to which one triggered +it. Harmless, but don't be thrown by it: the operation that actually failed is `Lift`. + +## See also + +- [Overview](@ref geometry-overview) — the bridge identity this page is one half of. +- [Poisson bracket](@ref geometry-poisson) — what a lift is usually fed into. +- [From Hamiltonians and vector fields](@ref flows-from-hamiltonians) — turning a lift into a + flow. From 0d3a0f52eabbbd53c2b64bdecae67dcd653f8910 Mon Sep 17 00:00:00 2001 From: Olivier Cots Date: Sun, 23 Aug 2026 00:04:53 +0200 Subject: [PATCH 3/8] docs(geometry): write the ad page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ad(X, f)/ad(X, Y) — Lie derivative or Lie bracket depending on whether the second argument is scalar- or vector-valued. Covers typed nesting, ∂ₜ's always-NonAutonomous result, and every error the old ⋅/Lie/autonomous= spelling now triggers. Co-Authored-By: Claude Sonnet 5 --- docs/src/geometry/ad.md | 146 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 143 insertions(+), 3 deletions(-) diff --git a/docs/src/geometry/ad.md b/docs/src/geometry/ad.md index 66de7e3f5..e60374b6a 100644 --- a/docs/src/geometry/ad.md +++ b/docs/src/geometry/ad.md @@ -1,4 +1,144 @@ -# [ad](@id geometry-ad) +# [Lie derivative and Lie bracket](@id geometry-ad) -!!! 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 +``` + +`ad(X, foo)` is one function with two meanings, chosen by what `foo` returns. + +```@example main +using OptimalControl +``` + +## One function, two meanings + +- `foo` scalar-valued → the **Lie derivative** of `foo` along `X`. +- `foo` vector-valued → the **Lie bracket** of `X` and `foo`. + +## Lie derivative + +For a vector field $X$ and a scalar function $f$, + +```math +(\mathcal{L}_X f)(x) = f'(x) \cdot X(x). +``` + +```@example main +X(x) = [x[2], -x[1]] +f(x) = x[1]^2 + x[2]^2 # energy of the harmonic oscillator + +Xf = ad(X, f) +Xf([1.0, 2.0]) +``` + +Energy is conserved along $X$'s flow, so the Lie derivative vanishes everywhere. + +## Lie bracket + +For two vector fields $X, Y$, + +```math +[X, Y](x) = J_Y(x)\,X(x) - J_X(x)\,Y(x), +``` + +where $J$ denotes the Jacobian. + +```@example main +Y(x) = [x[1]^2, x[2]^2] +Z = ad(X, Y) +Z([1.0, 2.0]) +``` + +## Typed operands + +Wrapping the inputs as `VectorField`s makes `ad` return a `VectorField` too — so it **nests**: + +```@example main +XV = VectorField(x -> [x[2], -x[1]]) +YV = VectorField(x -> [x[1]^2, x[2]^2]) + +ZV = ad(XV, YV) +typeof(ZV) +``` + +```@example main +ZZV = ad(ZV, YV) # ad(ad(X, Y), Y) — a second-order bracket +ZZV([1.0, 2.0]) +``` + +## Non-autonomous and variable fields + +`is_autonomous=` and `is_variable=` work exactly as on [`Lift`](@ref); both operands must agree, +or you get a `PreconditionError` naming both traits: + +```@example main +Xa = VectorField(x -> [x[2], -x[1]]) +Xb = VectorField((t, x) -> [t + x[2], -x[1]]; is_autonomous=false) + +try + ad(Xa, Xb) +catch e + println(e) +end +``` + +## Partial time derivative + +`∂ₜ` computes $\partial f / \partial t$ for a non-autonomous `f`, `VectorField`, or +`Hamiltonian` — its result is **always** `NonAutonomous`, regardless of whether the input +already was: + +```@example main +g(t, x) = t^2 + x[1] * x[2] +dg = ∂ₜ(g) +dg(3.0, [1.0, 2.0]) # ∂g/∂t = 2t = 6 +``` + +```@example main +dXV = ∂ₜ(XV) # XV::VectorField, defined above — still comes back NonAutonomous +typeof(dXV) +``` + +## Errors you will meet + +| Situation | Exception | +| --- | --- | +| `ad` given an `AbstractHamiltonian` | `IncorrectArgument` — points at `Poisson` | +| time/variable dependence mismatch between operands | `PreconditionError` | +| an `InPlace` operand | `NotImplemented` | +| a `HamiltonianVectorField` passed to `ad` | `NotImplemented` | + +```@example main +H = Hamiltonian((x, p) -> x[1] + p[1]) +try + ad(H, x -> x) +catch e + println(e) +end +``` + +`ad` also refuses an `InPlace` operand: + +```@example main +Xip = VectorField((dx, x) -> (dx .= [x[2], -x[1]]); is_inplace=true) +try + ad(Xip, x -> x[1]^2) +catch e + println(e) +end +``` + +## Coming from v2.0 + +| v2.0 | v2.1 | +| --- | --- | +| `Lie(X, f)` | `ad(X, f)` | +| `X ⋅ f` | `ad(X, f)` — **no operator replacement**, `⋅` is gone | + +See [Migrating to v2.1](@ref migration) for the throwing shim on `Lie`. + +## See also + +- [Poisson bracket](@ref geometry-poisson) — the Hamiltonian-side counterpart, linked by + `Poisson(Lift(X), Lift(Y)) ≈ Lift(ad(X, Y))`. +- [The `@Lie` macro](@ref geometry-lie-macro) — `ad`/`Poisson` with bracket notation. From 9260bba331b92bc62316a7a9c629fd83aa455367 Mon Sep 17 00:00:00 2001 From: Olivier Cots Date: Sun, 23 Aug 2026 00:04:53 +0200 Subject: [PATCH 4/8] docs(geometry): write the poisson page Poisson(H, G), its bridge to Lie brackets, and the singular-control application (H01, H001, H101, u_sing = -H001/H101). Co-Authored-By: Claude Sonnet 5 --- docs/src/geometry/poisson.md | 120 ++++++++++++++++++++++++++++++++++- 1 file changed, 117 insertions(+), 3 deletions(-) diff --git a/docs/src/geometry/poisson.md b/docs/src/geometry/poisson.md index b4d5e02f0..48b01a44d 100644 --- a/docs/src/geometry/poisson.md +++ b/docs/src/geometry/poisson.md @@ -1,4 +1,118 @@ -# [Poisson](@id geometry-poisson) +# [Poisson bracket](@id geometry-poisson) -!!! 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 +``` + +For two Hamiltonians $H, G$, + +```math +\{H, G\}(x,p) = \nabla_p H \cdot \nabla_x G - \nabla_x H \cdot \nabla_p G. +``` + +```@example main +using OptimalControl +``` + +## On plain functions + +```@example main +H(x, p) = p[1] * x[2] + p[2] * x[1] +G(x, p) = x[1]^2 + p[2]^2 + +B = Poisson(H, G) +x, p = [1.0, 2.0], [3.0, 4.0] +B(x, p) +``` + +Antisymmetry, $\{H,G\} = -\{G,H\}$, checked directly: + +```@example main +Poisson(H, G)(x, p) + Poisson(G, H)(x, p) +``` + +## On typed Hamiltonians + +Wrapping the inputs as `Hamiltonian`s makes `Poisson` return a `Hamiltonian` too — it **nests**: + +```@example main +HT = Hamiltonian(H) +GT = Hamiltonian(G) + +BT = Poisson(HT, GT) +typeof(BT) +``` + +```@example main +BTT = Poisson(BT, GT) # {{H, G}, G} — a second-order bracket +BTT(x, p) +``` + +## The bridge to Lie brackets + +$\{H_X, H_Y\} = H_{[X,Y]}$ — lifting turns a Poisson bracket into a Lie bracket, and vice versa: + +```@example main +X(x) = [x[1]^2, x[2]^2] +Y(x) = [x[2], -x[1]] + +Poisson(Lift(X), Lift(Y))(x, p), Lift(ad(X, Y))(x, p) +``` + +## Non-autonomous and variable forms + +```@example main +Ht(t, x, p) = t + p[1] * x[2] + p[2] * x[1] +Gt(t, x, p) = t^2 + x[1]^2 + p[2]^2 + +Bt = Poisson(Ht, Gt; is_autonomous=false) +Bt(1.0, x, p) +``` + +## Application: singular controls + +For a pseudo-Hamiltonian $H = H_0 + u H_1$, if the switching function $H_1$ vanishes on an +interval (a singular arc), the control there is recovered from the iterated Poisson brackets + +```math +H_{01} = \{H_0, H_1\}, \qquad H_{001} = \{H_0, H_{01}\}, \qquad H_{101} = \{H_1, H_{01}\}, +``` + +giving + +```math +u_{\text{sing}} = -\frac{H_{001}}{H_{101}}, \qquad H_{101} \neq 0. +``` + +```@example main +H0(x, p) = p[1] * x[2] + p[2] * (-x[1]) +H1(x, p) = p[2] + +H01 = Poisson(H0, H1) +H001 = Poisson(H0, H01) +H101 = Poisson(H1, H01) + +H001(x, p), H101(x, p) +``` + +See the [example gallery](@ref examples-gallery) for a complete singular-control application on +a real problem. + +## A trap to know about + +`Poisson` is not defined on vector fields — lift them first: + +```@example main +XV = VectorField(x -> [x[2], -x[1]]) +try + Poisson(XV, x -> x[1]) +catch e + println(e) +end +``` + +## See also + +- [Lie derivative and Lie bracket](@ref geometry-ad) — the vector-field-side counterpart. +- [The `@Lie` macro](@ref geometry-lie-macro) — `{H, G}` notation for this bracket. +- [Example gallery](@ref examples-gallery) — the worked application. From b4e780848b312f3aa3d512a9b4474f69fe101cdf Mon Sep 17 00:00:00 2001 From: Olivier Cots Date: Sun, 23 Aug 2026 00:05:00 +0200 Subject: [PATCH 5/8] docs(geometry): write the @Lie macro page Bracket notation for ad/Poisson: [X, Y], {H, G}, nesting, arithmetic at an evaluation point, and the keyword-parenthesization gotcha. Verified the nested-brace examples in the actual rendered VitePress HTML, not just a clean Documenter exit code. Co-Authored-By: Claude Sonnet 5 --- docs/src/geometry/lie-macro.md | 112 ++++++++++++++++++++++++++++++++- 1 file changed, 109 insertions(+), 3 deletions(-) diff --git a/docs/src/geometry/lie-macro.md b/docs/src/geometry/lie-macro.md index b5c46a2a8..603657052 100644 --- a/docs/src/geometry/lie-macro.md +++ b/docs/src/geometry/lie-macro.md @@ -1,4 +1,110 @@ -# [@Lie](@id geometry-lie-macro) +# [The `@Lie` macro](@id geometry-lie-macro) -!!! 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 +``` + +`@Lie` lets you write brackets the way you'd write them on paper: square brackets for a Lie +bracket, curly braces for a Poisson bracket. + +```@example main +using OptimalControl +``` + +## Why a macro + +`@Lie [X, Y]` reads as $[X, Y]$; `@Lie {H, K}` reads as $\{H, K\}$ — both expand to a call to +[`ad`](@ref) or [`Poisson`](@ref) respectively. + +## Lie brackets + +```@example main +F1 = VectorField(x -> [0, -x[3], x[2]]) +F2 = VectorField(x -> [x[3], 0, -x[1]]) + +F12 = @Lie [F1, F2] +F12([1.0, 2.0, 3.0]) +``` + +## Poisson brackets + +```@example main +H0(x, p) = p[1] * x[2] + p[2] * (-x[1]) +H1(x, p) = p[2] + +H01 = @Lie {H0, H1} +H01([1.0, 2.0], [3.0, 4.0]) +``` + +## Nesting + +```@example main +F3 = VectorField(x -> [x[1], x[2], x[3]]) +F123 = @Lie [[F1, F2], F3] +F123([1.0, 2.0, 3.0]) +``` + +```@example main +H001 = @Lie {H0, {H0, H1}} +H001([1.0, 2.0], [3.0, 4.0]) +``` + +## Arithmetic and evaluation points + +`@Lie` brackets combine with ordinary arithmetic once evaluated: + +```@example main +x = [1.0, 2.0, 3.0] # F1, F2 are 3-D vector fields, defined above +@Lie [F1, F2](x) + 4 * [F1, F2](x) +``` + +**Parenthesise when you evaluate** — a trailing keyword binds to the macro, not to the call: +write `(@Lie [F, G](x))`, never `@Lie [F, G](x) atol=1e-6`, if you're combining the result with +anything else on the same line. + +## Keywords + +`is_autonomous=`, `is_variable=`, and `ad_backend=` all work exactly as on `ad`/`Poisson`, +inferred from typed `VectorField`/`Hamiltonian` operands or given explicitly for plain +functions: + +```@example main +X(t, x) = [t + x[2], -x[1]] +Y(t, x) = [x[1], t * x[2]] + +Z = @Lie [X, Y] is_autonomous=false +Z(1.0, [1.0, 2.0]) +``` + +## What it needs in scope + +The macro's expansion emits fully qualified `CTLie.*` and `CTBase.Traits.*` names, so both +modules must be resolvable at the call site. `using OptimalControl` re-exports both module +aliases: + +```@example main +CTLie, CTBase +``` + +which is why they appear in `names(OptimalControl)` at all — not because you're expected to use +them directly, but because `@Lie`'s expansion needs them in scope. + +## A trap to know about + +The old `autonomous=`/`variable=` keywords are rejected at macro-expansion time, not silently +accepted: + +```@example main +try + eval(:(@Lie [$F1, $F2] autonomous=false)) +catch e + println(e) +end +``` + +## See also + +- [Lie derivative and Lie bracket](@ref geometry-ad) +- [Poisson bracket](@ref geometry-poisson) +- [Example gallery](@ref examples-gallery) — where iterated `{...}` brackets are used in + practice. From a829f2baf4e96b1eee48414f9afeeb857219bcf9 Mon Sep 17 00:00:00 2001 From: Olivier Cots Date: Sun, 23 Aug 2026 00:05:00 +0200 Subject: [PATCH 6/8] docs(geometry): write the ad-backend page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New page: dg_ad_backend/dg_ad_backend!, the ad_backend= keyword shared by ad/Poisson/∂ₜ/@Lie, and the CPU/GPU DifferentiationInterface split. Co-Authored-By: Claude Sonnet 5 --- docs/src/geometry/ad-backend.md | 85 +++++++++++++++++++++++++++++++-- 1 file changed, 82 insertions(+), 3 deletions(-) diff --git a/docs/src/geometry/ad-backend.md b/docs/src/geometry/ad-backend.md index 0c49ad85e..384c6438c 100644 --- a/docs/src/geometry/ad-backend.md +++ b/docs/src/geometry/ad-backend.md @@ -1,4 +1,83 @@ -# [AD backend](@id geometry-ad-backend) +# [Choosing an AD backend](@id geometry-ad-backend) -!!! 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 +``` + +```@example main +using OptimalControl +``` + +## Everything here is AD-backed, except `Lift` + +[`ad`](@ref), [`Poisson`](@ref), [`∂ₜ`](@ref), and [`@Lie`](@ref) all differentiate under the +hood; [`Lift`](@ref) is purely algebraic and never touches a backend at all. + +## The default + +```@example main +dg_ad_backend() +``` + +`DifferentiationInterface{CPU}` over `ForwardDiff`, the same default used throughout +`CTBase`/`CTLie`. + +## Reading the current backend + +`dg_ad_backend()` (above) always returns the backend that will be used when no `ad_backend=` +keyword is given. + +## Changing it globally + +```@example main +import CTBase: Differentiation + +dg_ad_backend!(Differentiation.DifferentiationInterface()) +dg_ad_backend() +``` + +## Changing it for one call + +Every operation in this section accepts its own `ad_backend=`, overriding the global setting +just for that call: + +```@example main +X(x) = [x[2], -x[1]] +f(x) = x[1]^2 + x[2]^2 + +ad(X, f; ad_backend=Differentiation.DifferentiationInterface())([1.0, 2.0]) +``` + +## GPU + +A GPU-parameterized backend is constructed the same way, with the `GPU` strategy instead of +`CPU`: + +```julia +import CTBase: Differentiation, Strategies + +dg_ad_backend!(Differentiation.DifferentiationInterface{Strategies.GPU}()) +``` + +This block is not executed on this page — no CUDA-capable GPU is available in this development +environment or in CI, the same caveat as [GPU](@ref solve-gpu) on the solve side. + +## Introspection + +```@example main +describe(:di) +``` + +## If nothing works + +If `DifferentiationInterface` (and a concrete AD package, such as `ForwardDiff`) isn't loaded, +the extension that actually performs the differentiation never arms, and calling `ad`, +`Poisson`, `∂ₜ`, or `@Lie` fails. `OptimalControl` loads `DifferentiationInterface` and +`ForwardDiff` itself, so this only bites if you're using `CTLie` standalone. + +## See also + +- [Overview](@ref geometry-overview) — where each operation sits relative to AD. +- [GPU](@ref solve-gpu) — the same `CPU`/`GPU` strategy split on the solve side. +- [Flows overview](@ref flows-overview) — the `method=:cpu`/`:gpu` construction-time keyword on + `Flow`. From 7d55eb235942786a23faf8db365e52545d270a05 Mon Sep 17 00:00:00 2001 From: Olivier Cots Date: Sun, 23 Aug 2026 00:05:12 +0200 Subject: [PATCH 7/8] docs: verify PR 9 acceptance criteria against a real build, update work board MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Full julia --project=docs docs/make.jl rebuild and npx vitepress build, both clean except pre-existing unrelated warnings (missing sibling inventory files, and CTLie's own internal @ref targets in the auto-generated API reference — neither introduced by this PR). Ticked all 7 acceptance criteria in 06-geometry.md against that build, with an "Also found and fixed" section recording the BoundsError caught by the build, the non-existent examples-singular-control anchor, and the ad/Lift shared-guard message wart. Confirmed no src/ or test/ change was needed — src/imports/ctlie.jl already re-exports the full surface. Co-Authored-By: Claude Sonnet 5 --- docs/reports/06-geometry.md | 63 +++++++++++++++++++++++++++------ docs/reports/99-api-coverage.md | 4 +-- docs/reports/README.md | 2 +- docs/src/assets/Manifest.toml | 4 +-- 4 files changed, 57 insertions(+), 16 deletions(-) diff --git a/docs/reports/06-geometry.md b/docs/reports/06-geometry.md index 80a73e4ab..7ddb8d946 100644 --- a/docs/reports/06-geometry.md +++ b/docs/reports/06-geometry.md @@ -255,14 +255,55 @@ Errors, all worth showing once because they are good errors: ## Acceptance criteria -- [ ] `grep -rn 'Lie(\| ⋅ \|HamiltonianLift\|autonomous=\|OptimalControl\.VectorField' docs/src/geometry/` - returns nothing (`@Lie` and `is_autonomous=` excepted — refine the pattern). -- [ ] The bridge identity `Poisson(Lift(X), Lift(Y)) ≈ Lift(ad(X, Y))` executes on - `overview.md` and again on `poisson.md`. -- [ ] The `H isa AbstractHamiltonian` → `false` trap has a `!!! warning` on `lift.md`. -- [ ] `ad-backend.md` exists and `dg_ad_backend` / `dg_ad_backend!` appear nowhere else as - undocumented names. -- [ ] Nested Poisson notation renders correctly in the built VitePress site (check the HTML, - not the build log). -- [ ] Every error in the exceptions table is demonstrated at least once across the section. -- [ ] `LiftedHamiltonianFunction` is always written with the `OptimalControl.` prefix. +- [x] `grep -rn 'Lie(\| ⋅ \|HamiltonianLift\|autonomous=\|OptimalControl\.VectorField' docs/src/geometry/` + returns nothing (`@Lie` and `is_autonomous=` excepted — refine the pattern). Verified: the + only hits are the "Coming from v2.0" migration tables (`overview.md`, `ad.md`) and the + deliberate `@Lie [...] autonomous=false` rejection demo on `lie-macro.md` — all + intentional mentions of the *old* API, not live usage of it. +- [x] The bridge identity `Poisson(Lift(X), Lift(Y)) ≈ Lift(ad(X, Y))` executes on + `overview.md` and again on `poisson.md`. Both use the nonlinear pair + `X(x)=[x[1]^2,x[2]^2]`, `Y(x)=[x[2],-x[1]]` (the attic's own linear pair gives an + identically-zero bracket, too weak a check) — both sides evaluate to `12.0`. +- [x] The `H isa AbstractHamiltonian` → `false` trap has a `!!! warning` on `lift.md`. +- [x] `ad-backend.md` exists and `dg_ad_backend` / `dg_ad_backend!` appear nowhere else as + undocumented names (grepped across `docs/src/`). +- [x] Nested Poisson notation renders correctly in the built VitePress site — checked the + actual rendered HTML (`docs/build/1/geometry/lie-macro.html`), not just the build log; the + `{{`/`}}` escape plugin isn't even triggered since these examples live in fenced + ` ```@example ` blocks, not inline code spans. +- [x] Every error in the exceptions table is demonstrated at least once across the section, + including the `InPlace`-operand-to-`ad` case, added explicitly to `ad.md` once noticed it + was listed in the table but not actually shown anywhere. +- [x] `LiftedHamiltonianFunction` is always written with the `OptimalControl.` prefix. Caught and + fixed three bare occurrences on `lift.md` during review (the type itself is import-only, + not exported — a bare reference in an example would be a real footgun for a reader). + +## Also found and fixed + +- **A real bug in the first draft**, caught by the actual `make.jl` build, not by review: the + "Arithmetic and evaluation points" example on `lie-macro.md` reused `F1`/`F2` (3-D vector + fields, defined earlier on the page for the Lie-bracket examples) with a 2-D evaluation point + — a `BoundsError`. Fixed by evaluating at a 3-D point instead of introducing new fields. +- **`@ref examples-singular-control` doesn't exist.** The spec's own outgoing-links table + (`poisson.md`, `lie-macro.md`) points at this anchor, but PR 10 ("docs: examples") hasn't + started and `docs/src/examples/gallery.md` only carries the page-level stub anchor + `examples-gallery` — no sub-anchor for a specific worked example exists yet, unlike the + page-level stubs PR 2 pre-created for every *page* in the sitemap. Linking to the + not-yet-existing anchor would have been a genuine unresolved-`@ref` build warning, not a + harmless forward reference like the ones used elsewhere in this series. Pointed both links at + `@ref examples-gallery` instead; retarget to a more specific anchor once PR 10 creates one. +- **One nuance beyond the spec**: `Lift(::HamiltonianVectorField)` throws `NotImplemented`, as + specced, but the underlying guard is shared with `ad` and its message says "ad" even when + triggered through `Lift` (`ad_types.jl:59`'s `_check_not_hvf`, reused by both). Documented + honestly on `lift.md` rather than silently editing the shown output to match expectations — + it's a harmless upstream wart, not worth an issue against CTLie. +- **This PR needed no code change.** `src/imports/ctlie.jl` already re-exports the full surface + the spec calls for (`ad`, `Lift`, `Poisson`, `∂ₜ`, `@Lie`, `dg_ad_backend`, + `dg_ad_backend!`, plus the `CTLie`/`CTBase` module aliases), with `LiftedHamiltonianFunction` + correctly import-only. Confirmed live (`Base.isexported`) before writing a single page, so no + `src/`/`test/` changes and no re-export commit were needed for this PR — unlike PR 8. +- Verified against **CTLie 0.1.5-beta**, the version `docs/Manifest.toml` actually resolves + (`~/.julia/packages/CTLie/cHxPr/`) — not the newer `../CTLie` sibling checkout (`0.2.0`), same + situation as PR 8's CTFlows pin. Read the full source (`ad.jl`, `lift.jl`, `poisson.jl`, + `lie_macro.jl`, `ad_types.jl`, `default.jl`) at that exact version rather than trusting the + spec's own citations. diff --git a/docs/reports/99-api-coverage.md b/docs/reports/99-api-coverage.md index 526b08e0c..f62aa6bfe 100644 --- a/docs/reports/99-api-coverage.md +++ b/docs/reports/99-api-coverage.md @@ -173,7 +173,7 @@ for whoever next touches this file's top-line count. ## 9. Geometry — 7 -API theme **geometry**. +API theme **geometry**. Implemented in PR 9 (`docs: geometry`). | Symbol | Guide | | --- | --- | @@ -183,7 +183,7 @@ API theme **geometry**. | `∂ₜ` | `geometry-ad` §"Partial time derivative" | | `dg_ad_backend` `dg_ad_backend!` | `geometry-ad-backend` | -(`@Lie` counted in §2.) +(`@Lie` counted in §2, guide `geometry-lie-macro`.) ## 10. Type vocabulary — 27 diff --git a/docs/reports/README.md b/docs/reports/README.md index d858f0d05..619147ee9 100644 --- a/docs/reports/README.md +++ b/docs/reports/README.md @@ -56,7 +56,7 @@ Status legend: ⬜ not started · 🟡 in progress · ✅ merged | 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`](https://github.com/control-toolbox/OptimalControl.jl/pull/868) | Flows (indirect) | [`05`](05-flows-indirect.md) | 6 | 🟡 in review | -| 9 | `docs: geometry` | Geometry | [`06`](06-geometry.md) | 8 | ⬜ | +| 9 | `docs: geometry` | Geometry | [`06`](06-geometry.md) | 8 | 🟡 branch ready (`docs/geometry`, off `docs/flows`) | | 10 | `docs: examples` | Examples | [`08`](08-examples.md) | 8, 9 | ⬜ | | 11 | `docs: getting started` | Getting started + `index.md` | [`02`](02-getting-started.md) | 5–10 | ⬜ | | 12 | `docs: migration + cleanup` | Migration page, drop `docs/attic/` | [`10`](10-migration.md) §2 | all | ⬜ | diff --git a/docs/src/assets/Manifest.toml b/docs/src/assets/Manifest.toml index 72ec288bf..fa5b49cfb 100644 --- a/docs/src/assets/Manifest.toml +++ b/docs/src/assets/Manifest.toml @@ -543,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 = "3c6ceb2c59e132fd276ca16fdb0ecd043791d22d" +git-tree-sha1 = "5a4cc8259b903c537e8df3acf80075351d207a15" uuid = "2b5f629d-d688-5b77-993f-72d75c75574e" -version = "7.18.0" +version = "7.18.1" [deps.DiffEqBase.extensions] DiffEqBaseCUDAExt = "CUDA" From 8262f11dd6e41d1af28769cfc0d7375a5870e36b Mon Sep 17 00:00:00 2001 From: Olivier Cots Date: Sun, 23 Aug 2026 00:07:22 +0200 Subject: [PATCH 8/8] docs(reports): link PR 9 to #869 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 619147ee9..da01665ff 100644 --- a/docs/reports/README.md +++ b/docs/reports/README.md @@ -56,7 +56,7 @@ Status legend: ⬜ not started · 🟡 in progress · ✅ merged | 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`](https://github.com/control-toolbox/OptimalControl.jl/pull/868) | Flows (indirect) | [`05`](05-flows-indirect.md) | 6 | 🟡 in review | -| 9 | `docs: geometry` | Geometry | [`06`](06-geometry.md) | 8 | 🟡 branch ready (`docs/geometry`, off `docs/flows`) | +| 9 | [`docs: geometry`](https://github.com/control-toolbox/OptimalControl.jl/pull/869) | Geometry | [`06`](06-geometry.md) | 8 | 🟡 in review | | 10 | `docs: examples` | Examples | [`08`](08-examples.md) | 8, 9 | ⬜ | | 11 | `docs: getting started` | Getting started + `index.md` | [`02`](02-getting-started.md) | 5–10 | ⬜ | | 12 | `docs: migration + cleanup` | Migration page, drop `docs/attic/` | [`10`](10-migration.md) §2 | all | ⬜ |