Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 52 additions & 11 deletions docs/reports/06-geometry.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
4 changes: 2 additions & 2 deletions docs/reports/99-api-coverage.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
| --- | --- |
Expand All @@ -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

Expand Down
2 changes: 1 addition & 1 deletion docs/reports/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`](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 | ⬜ |
Expand Down
4 changes: 2 additions & 2 deletions docs/src/assets/Manifest.toml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

85 changes: 82 additions & 3 deletions docs/src/geometry/ad-backend.md
Original file line number Diff line number Diff line change
@@ -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`.
146 changes: 143 additions & 3 deletions docs/src/geometry/ad.md
Original file line number Diff line number Diff line change
@@ -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.
Loading