diff --git a/docs/reports/99-api-coverage.md b/docs/api-coverage.md similarity index 83% rename from docs/reports/99-api-coverage.md rename to docs/api-coverage.md index f62aa6bfe..a2dc426e1 100644 --- a/docs/reports/99-api-coverage.md +++ b/docs/api-coverage.md @@ -1,17 +1,24 @@ # API coverage matrix -**Status**: specification · **Generated from**: `names(OptimalControl)` on -OptimalControl 2.1.0-beta, resolved environment (CTBase 0.28.9-beta, CTModels 0.15.3-beta, -CTFlows 0.16.3-beta, CTLie 0.1.5-beta, CTSolvers 0.4.34-beta) +**Status**: living cross-reference · **Measured against**: `names(OptimalControl)` on +OptimalControl 2.2.0-beta (`[compat]` floors CTBase 0.30, CTModels 0.19, CTFlows 0.18, +CTLie 0.2, CTSolvers 0.5, CTDirect 1, CTParser 0.9) ## What this is The objective acceptance criterion for the whole rewrite: **every exported symbol appears in at least one guide page and in exactly one API-reference theme.** -`names(OptimalControl)` returns **193 symbols** (excluding `:OptimalControl` itself). They are -partitioned below. A symbol with an empty *Guide* cell is a hole — either a page must cover it -or it must be justified as reference-only. +The *theme* half is enforced by the docs build: `docs/api_reference.jl` (lines ~361–389) +`error`s the build if any exported symbol is absent from `API_THEMES`, or if a theme lists a +name that is neither exported nor `qualified`. This file is the **guide-side** cross-reference +the build cannot check — which page actually teaches each symbol. + +`names(OptimalControl)` returns **203 symbols** (excluding `:OptimalControl` itself). Sections +§§1–11 below partition **200** of them; the remaining **3** — `Lie`, `⋅`, `HamiltonianLift` +(§13, the deprecated shims re-introduced by PR 3, now real exported bindings) — bring the +total to 203. A symbol with an empty *Guide* cell is a hole — either a page must cover it or +it must be justified as reference-only. Regenerate the ground truth with: @@ -21,10 +28,11 @@ julia --project=. -e 'using OptimalControl; println(length(ns)); foreach(println, ns)' ``` -PR 4 wires the same computation into `docs/api_reference.jl` as a build-time check -([`09-api-reference.md`](09-api-reference.md) §"The completeness check"). +`docs/api_reference.jl` runs the same computation as a build-time completeness check (the +`let` block after `API_THEMES`). -Page ids refer to the sitemap in [`00-cahier-des-charges.md`](00-cahier-des-charges.md) §7. +Page ids are the `@id` anchors declared on the pages under `docs/src/` (e.g. +`modelling-abstract-syntax` on `modelling/abstract-syntax.md`). --- @@ -58,7 +66,7 @@ Re-exported so generated code and macro expansions can qualify. Not user-facing | `time!` `state!` `control!` `variable!` `dynamics!` `objective!` `constraint!` `time_dependence!` `build` | `modelling-functional-api` | modelling | | `build_initial_guess` | `solve-initial-guess` | modelling | -## 4. Problem introspection — 47 +## 4. Problem introspection — 58 All on `modelling-inspect` unless noted; API theme **problem**. @@ -79,7 +87,7 @@ All on `modelling-inspect` unless noted; API theme **problem**. `dynamics` `mayer` `lagrange` `has_mayer_cost` `has_lagrange_cost` `is_mayer_cost_defined` `is_lagrange_cost_defined` -**Constraints (11)** +**Constraints (12)** `constraint` `constraints` `path_constraints_nl` `boundary_constraints_nl` `state_constraints_box` `control_constraints_box` `variable_constraints_box` `dim_path_constraints_nl` `dim_boundary_constraints_nl` `dim_state_constraints_box` @@ -130,7 +138,7 @@ All on `results-solution` unless noted; API theme **solution**. `dim_dual_state_constraints_box` `dim_dual_control_constraints_box` `dim_dual_variable_constraints_box` -## 7. Options and strategies — 22 +## 7. Options and strategies — 25 All on `solve-options` or `solve-choosing-a-method`; API theme **options**. @@ -143,7 +151,7 @@ All on `solve-options` or `solve-choosing-a-method`; API theme **options**. | `parameter` `default_parameter` `available_parameters` | `solve-gpu` | | `CPU` `GPU` | `solve-gpu` | -## 8. Flows — 24 +## 8. Flows — 26 API theme **flows**. @@ -166,10 +174,9 @@ their siblings `control_law` and `pseudo_hamiltonian` were — PR 8 closed the g accessors — gradients and vocabulary" testset). `system` and `integrator` stay deliberately unexported — qualified as `CTFlows.Flows.system(f)`. -`length(names(OptimalControl)) - 1` measured **203** after this change, not the **200** this -report anticipated (193 + 7) — a 3-symbol drift accumulated somewhere between this report's -original count and PR 8, unrelated to this PR's own +7. Not audited/reconciled here; flagging -for whoever next touches this file's top-line count. +PR 8 brought the count from 193 to 200 (+7 here); the remaining 3 of the current 203 are the +§13 deprecated shims (`Lie`, `⋅`, `HamiltonianLift`), which PR 3 made real exported bindings. +Reconciled 2026-09 during E6-C. ## 9. Geometry — 7 @@ -272,13 +279,14 @@ Recorded so they are decided, not forgotten. | `SolverFailure` is exported by `CTBase.Exceptions` but imported nowhere in OptimalControl | §12 | open — PR 3: surface it or note the omission | | CTModels init helpers not re-exported: `initial_guess`, `pre_initial_guess`, `validate_initial_guess`, `initial_state`, `initial_control`, `initial_variable`, `PreInitialGuess` | §3 | open — PR 5/6 | | `@def_exa` exists in CTParser but is not re-exported | §2 | open — PR 5 | -| `time` and `success` are `Base` names with no OptimalControl binding | §13 | open — PR 3 | +| `time` and `success` are `Base` names with no OptimalControl binding | §13 | ✅ closed — PR 3 added throwing `Base.time`/`Base.success` methods (`src/deprecated.jl`) | ## 15. How to use this file -1. When a docs PR is written, tick its symbols off by filling the *Guide* column with the page - that actually covers them (not the one that was planned). -2. Before merging PR 12, re-run the `names(OptimalControl)` command above and diff against - §§1–11. A new symbol with no row is a missing docs change. -3. After PR 4, the build itself reports missing and stale symbols; this file becomes the - record of the *guide*-side coverage, which the build cannot check. +1. When a docs page changes what it covers, update the *Guide* column to the page that + actually teaches the symbol. +2. When the API surface changes, re-run the `names(OptimalControl)` command above and diff + against §§1–11 + §13. The docs build already fails on an uncovered *theme*; this file is + the *guide*-side record the build cannot check. +3. Keep the top-line count and the per-section subtotals in sync with the body — they are + documentation, not the enforced contract (that is `docs/api_reference.jl`). diff --git a/docs/api_reference.jl b/docs/api_reference.jl index 0ed7b514e..422092929 100644 --- a/docs/api_reference.jl +++ b/docs/api_reference.jl @@ -1,7 +1,7 @@ # Thematic API reference manifest. # # Each theme is a literal, hand-maintained list of symbols — no scraping of -# docs/reports/99-api-coverage.md. That file is prose meant for a human to +# docs/api-coverage.md. That file is prose meant for a human to # read and copy from, not a machine-parseable format: an earlier version of # this script tried to extract symbols from it automatically and silently # picked up names from sentences that explicitly said *not* to document them @@ -9,14 +9,14 @@ # module qualification some symbols need to resolve in `@docs` (a bare # `constraint` does not resolve; `CTModels.Components.constraint` does). # -# When 99-api-coverage.md changes, update the lists below by hand and rerun +# When docs/api-coverage.md changes, update the lists below by hand and rerun # `julia --project=docs docs/make.jl` to check the completeness error and the # build log for new "undefined binding" / "no docs found" warnings. const EXCLUDE_SYMBOLS = [:include, :eval, :OptimalControl] # Re-exported purely as escape hatches for generated code / cross-package -# qualification (see docs/reports/99-api-coverage.md §1 "Module aliases"). +# qualification (see docs/api-coverage.md §1 "Module aliases"). # They carry no docstring of their own (`@doc` on a bare module without one # resolves to Julia's generic "here are its exports" filler, identical across # all of them — not worth transcluding). Described in prose on qualified.md @@ -351,7 +351,7 @@ const API_THEMES = [ # ::Symbol...)"`): the key is what the coverage check sees, the value is the exact # signature written into the `@docs` block. Needed whenever a bare name would pull in # a foreign package's docstring for the same generic function (`:solve` collides with -# `CommonSolve.solve`, `:methods` with `Base.methods` — see docs/reports/99-api-coverage.md +# `CommonSolve.solve`, `:methods` with `Base.methods` — see docs/api-coverage.md # and control-toolbox/OptimalControl.jl's Phase D campaign report for how this was found). _bare_name(s::Symbol) = Symbol(split(String(s), ".")[end]) _bare_name(p::Pair) = _bare_name(first(p)) diff --git a/docs/reports/00-cahier-des-charges.md b/docs/reports/00-cahier-des-charges.md deleted file mode 100644 index c6278403a..000000000 --- a/docs/reports/00-cahier-des-charges.md +++ /dev/null @@ -1,446 +0,0 @@ -# Documentation rewrite — cahier des charges - -**Date**: 2026-08-09 · **Target**: OptimalControl.jl v2.1.0-beta · **Status**: specification -**Scope**: everything under `docs/`, plus `src/deprecated.jl` -**Out of scope**: the sibling packages' own documentation sites; the `Tutorials` repository - -> **How to read this.** Part I says why the current site fails and who we are writing for. -> Part II is the specification proper — the sitemap and the conventions every page must obey; -> that is the part to keep open while writing. Part III is the work order and the acceptance -> criteria. - ---- - -## Part I — State and audience - -### 1. Executive summary - -The site describes an API that no longer exists, and the doc environment cannot even resolve -against the current package. Three things happened in v2.1.0-beta and none of them reached -`docs/`: - -1. **Differential geometry moved to CTLie.** `Lie` → `ad`, `⋅` removed with no alias, - `HamiltonianLift` → `LiftedHamiltonianFunction` (and re-parented: `<: Function`, no longer - `<: AbstractHamiltonian`). `docs/src/manual-differential-geometry.md` — 634 lines, the most - API-dense page on the site — teaches the removed spellings throughout. -2. **The `Flow` API changed shape.** `Flow(f::Function)` is gone (wrap in a `Data` type); - constrained flows take `constraint=`/`multiplier=` keywords instead of positional arguments; - the variable is a mandatory keyword, not a 5th positional; `augment=` became - `variable_costate=`; and SciML is no longer a hard dependency, so **every flow example needs - `using OrdinaryDiffEqTsit5` in its preamble** or it fails with a bare `MethodError`. -3. **The type vocabulary moved to `CTBase.Data` and is now exported.** `VectorField`, - `Hamiltonian`, `HamiltonianVectorField`, `ControlLaw`, `OpenLoop`/`ClosedLoop`/ - `DynClosedLoop`, `PathConstraint`, `Multiplier` — all reachable unqualified. The docs still - write `OptimalControl.VectorField` and carry an admonition explaining why the qualification - is necessary. It is not. - -On top of that, two structural problems that predate the upgrade: - -4. **One "Manual" node holds thirteen pages** covering modelling, AI, solving, plotting, - geometry and flows (`docs/make.jl:231-252`). It is a junk drawer. -5. **The internal CTX split leaks into the user-facing site.** `docs/src/api/subpackages.md` - is a list of six sibling packages; `docs/src/api/public.md:11` opens with an admonition - explaining that `Lift` "is defined in CTFlows". A user of the OptimalControl DSL should not - have to know that CTFlows exists. - -### 2. The build is broken - -Not "stale" — **broken**. Verified: - -| Problem | Evidence | -| --- | --- | -| `docs/Project.toml` pins `CTBase = "=0.18.8"`, `CTModels = "=0.10.1"`, `CTFlows = "0.8"` | root `Project.toml` requires `0.28` / `0.15` / `0.16`. The doc environment cannot resolve. | -| `CTLie` absent from `docs/Project.toml` | it owns `ad`, `Lift`, `Poisson`, `∂ₜ`, `@Lie` | -| `DifferentiationInterface` and `ForwardDiff` absent | without them the whole geometry API is **inert** (extension-gated via `src/imports/ad.jl`) | -| `OrdinaryDiffEqTsit5` absent | `docs/Project.toml` has `OrdinaryDiffEq`, but the flow pages need an integrator armed | -| `docs/make.jl:108` fetches `Base.get_extension(CTFlows, :CTFlowsODE)` | that extension was deleted. Returns `nothing`, which then enters the `Modules` vector and the `setdocmeta!` loop at `docs/make.jl:126-129` | -| `make.jl` never does `using CTLie`, and `CTLie` is not in `Modules` | CTLie docstrings have no `DocTestSetup` and cannot be pulled by `@docs` | -| `docs/inventories/` does not exist | referenced 11 times in `make.jl:42-98`; `DocumenterInterLinks` silently falls back to the URLs | - -### 3. Who we are writing for - -**The reader is a user of the OptimalControl DSL, not a contributor to the ecosystem.** -Three profiles, in decreasing order of how much of the site they read: - -| Profile | Wants | Enters via | -| --- | --- | --- | -| **Applied** — engineer/researcher with a problem to solve | model it, solve it with a direct method, look at the solution | Getting started → Modelling → Solve → Results | -| **Geometric** — control theorist doing indirect methods | flows, shooting, singular controls, Lie/Poisson brackets | Flows → Geometry, usually via Examples | -| **Simulator** — has a controlled ODE, not an optimisation problem | integrate under an open-loop or feedback control, inspect the trajectory, estimate parameters | Flows § open/closed loop; Modelling § problems without control | - -The third profile is currently **invisible on the site** even though the code fully supports -it: `Flow(ControlledVectorField(f), OpenLoop(u))` returns a `StateFlowTrajectory` that -`state` / `control` / `objective` / `plot` all accept, exactly like an optimal control -solution. Same for control-free models used for ODE parameter estimation — supported and -tested (`test/problems/control_free.jl`) but only reachable through one example page. - -**Consequence for the writing**: the site is organised by what these people are doing, never -by which package implements it. The word "CTFlows" appears in the user-facing pages only in -the Ecosystem annex and the Migration page. - -### 4. What "done" means - -- A user can go from `] add OptimalControl` to a solved, plotted problem without leaving - Getting started. -- Every technical capability listed in §6 has a page that names it. -- Each of the **193** symbols in `names(OptimalControl)` appears in at least one guide page - **and** in exactly one API-reference theme ([`99-api-coverage.md`](99-api-coverage.md) is - the checklist, and PR 4 makes the build itself verify half of it). -- `julia --project=docs docs/make.jl` runs with `draft = false` and the log has no - `@ref`/`@extref` resolution errors. -- Every deprecated spelling from v2.0 fails with a message naming its replacement. - ---- - -## Part II — Specification - -### 5. Principles - -1. **Organised by capability.** Top-level sections are named after what the user is doing. - No "Manual", no "Miscellaneous". -2. **The ecosystem is an implementation detail.** No `OptimalControl.` qualification in - examples unless the symbol genuinely is not exported (only `LiftedHamiltonianFunction`, - `PreModel`, `Model`, `Solution`, `ADNLP`, `Ipopt`, `Collocation`, … — see - [`99-api-coverage.md`](99-api-coverage.md) §"Imported, not exported"). CTX package names - appear only in the Ecosystem annex and the Migration page. -3. **Every page runs.** Code is in `@example`/`@repl` blocks that execute, not in inert - ```` ```julia ```` fences. The exception is a page that shows a *deprecated* or - *erroring* spelling — those stay inert and say so. -4. **One page, one task.** If a page needs "and" in its title, it is two pages. -5. **Capability first, then the tools.** Each page opens with what you can do, then the - functions that do it, then a worked snippet. Not the reverse. -6. **Cross-link forward.** Guides link to the API reference for signatures and to Examples - for the full story. They do not inline a full docstring. - -### 6. Capability inventory the site must cover - -The user's own framing, turned into a checklist. Each line must be reachable from the -sitemap and appear in [`99-api-coverage.md`](99-api-coverage.md). - -**Modelling** -- define a problem with `@def` (abstract syntax) -- define the same problem with the functional API, macro-free -- define a problem **without a control** — parameter estimation in ODEs -- introspect a problem: dimensions, names, dynamics, costs, constraints, traits -- get an LLM to write the `@def` for you - -**Direct resolution** -- `solve` in one line; understand what it chose for you -- give an initial guess: `@init`, vectors, functions, warm start from a `Solution` -- choose a method: the 12 `(discretizer, modeler, solver, parameter)` combinations -- pass options; understand routing, `route_to`, `bypass` -- explicit mode with typed components -- solve on GPU - -**Results** -- introspect a solution: trajectories, costate, duals, status, iterations -- plot: layouts, groups, styles, control norm, constraints, normalised time -- save and load (JLD2, JSON3) - -**Indirect resolution — flows** -- build a flow from an OCP + a control law (the PMP path) -- build a flow from a Hamiltonian, a pseudo-Hamiltonian + a control law, a Hamiltonian - vector field, a pseudo-Hamiltonian vector field + a control law -- build a flow from a plain vector field -- build a flow from a **controlled** vector field + an **open-loop** control → particular - solutions of the controlled system -- build a flow from a controlled vector field + a **closed-loop** (feedback) control -- inspect the trajectory it returns exactly like an OCP solution (`state`, `control`, - `objective`, `plot`) -- **accessors**: recover the Hamiltonian, the Hamiltonian vector field, the - pseudo-Hamiltonian, the control law, the gradients — from a flow built from an OCP + a law -- concatenate arcs: switching times, jumps on state and costate, multi-phase accessors -- constrained arcs: `constraint=`/`multiplier=` pairs, boundary arcs -- write a shooting function and solve it - -**Geometry** (what makes the singular-control example possible) -- `Lift` a vector field to a Hamiltonian -- `ad` — Lie derivative and Lie bracket (the former `Lie`) -- `Poisson` bracket -- `@Lie` — the `[X, Y]` / `{H, K}` notation, nesting, evaluation points -- `∂ₜ` — partial time derivative -- choose the AD backend: `dg_ad_backend`, `dg_ad_backend!` - -### 7. Sitemap - -Page ids are the target filenames under `docs/src/`. `index.md` stays the VitePress root and -is not listed in `pages=`. - -``` -Home index.md - -Getting started getting-started/installation.md - getting-started/first-problem.md - getting-started/guided-tour.md ← Literate - -Modelling modelling/formulation.md - modelling/abstract-syntax.md - modelling/functional-api.md - modelling/without-control.md - modelling/inspect.md - modelling/with-ai.md - -Solve (direct) solve/overview.md - solve/initial-guess.md - solve/choosing-a-method.md - solve/options.md - solve/explicit-mode.md - solve/gpu.md - -Results results/solution.md - results/plot.md - results/save-load.md - -Flows (indirect) flows/overview.md - flows/from-ocp.md - flows/from-hamiltonians.md - flows/simulation.md - flows/accessors.md - flows/multi-phase.md - flows/constrained-arcs.md - flows/shooting.md - -Geometry geometry/overview.md - geometry/lift.md - geometry/ad.md - geometry/poisson.md - geometry/lie-macro.md - geometry/ad-backend.md - -Examples examples/*.md ← see 08 - -API reference api/.md ← generated, see 09 - api/internals.md ← generated - api/ecosystem.md - -Migrating to v2.1 migration.md -``` - -Ordering rationale: **Results comes before Flows**, contrary to the old site. A trajectory -returned by a flow is inspected and plotted with the same functions as a solution returned by -`solve`, so Results must already be on the table when Flows starts. - -### 8. Cross-cutting conventions - -Every page spec inherits these. Violations are review blockers. - -#### 8.1 Preamble - -- **Any page that calls `Flow`** must have `using OrdinaryDiffEqTsit5` in its `@setup` or - first `@example` block. SciML is no longer a hard dependency; without it `Flow` fails with - a bare `MethodError` (`BREAKING.md`, §"Start here: `Flow` needs an integrator"). -- **Any page that calls `solve`** needs a solver loaded — `using NLPModelsIpopt` for the - default path. -- **Any page that plots** needs `using Plots`; without it `plot` throws - `ExtensionError(:Plots)` (`CTModels.jl/src/Display/Display.jl:69`). -- **Any page that saves/loads** needs `using JLD2` and/or `using JSON3`. -- Geometry pages need nothing extra: `DifferentiationInterface` and `ForwardDiff` are armed - by `src/imports/ad.jl` — but `docs/Project.toml` must list them (see - [`01-infrastructure.md`](01-infrastructure.md)). - -#### 8.2 Spelling - -| Never write | Write instead | Why | -| --- | --- | --- | -| `OptimalControl.VectorField`, `OptimalControl.Hamiltonian`, … | `VectorField`, `Hamiltonian`, … | exported since v2.1.0-beta (`src/imports/ctbase.jl`) | -| `Lie(X, f)` | `ad(X, f)` | renamed | -| `X ⋅ f` | `ad(X, f)` | removed, no alias | -| `HamiltonianLift` | `Lift(f)` to build; `OptimalControl.LiftedHamiltonianFunction` to name the type | renamed and re-parented | -| `autonomous=`, `variable=`, `inplace=` | `is_autonomous=`, `is_variable=`, `is_inplace=` | prefixed | -| `Flow(f)` with `f::Function` | `Flow(VectorField(f))`, `Flow(Hamiltonian(f))`, … | the bare function no longer says what it is | -| `Flow(ocp, u, g, μ)` | `Flow(ocp, u; constraint=g, multiplier=μ)` | keywords, and they come as a pair | -| `f(t0, x0, p0, tf, λ)` | `f(t0, x0, p0, tf; variable=λ)` | mandatory keyword on `NonFixed` | -| `augment=true` | `variable_costate=true` | renamed; returns `(xf, pf, pvf)` | -| `CTSolvers.Modelers.ADNLP()`, `CTDirect.Collocation()` | `OptimalControl.ADNLP()`, `OptimalControl.Collocation()` | neither module name is re-exported | -| `time(ocp)`, `success(sol)` | `times(ocp)` / `time_grid(sol)`, `successful(sol)` | no longer re-exported | - -#### 8.3 Shape — "1-D is a scalar" - -The ecosystem rule, stated in full at -[`Handbook/philosophy/dimension-and-shape.md`](https://github.com/control-toolbox/Handbook/blob/main/philosophy/dimension-and-shape.md): - -> **A one-dimensional quantity is a scalar, never a length-1 vector.** - -It applies to **state, costate, control and variable**, end to end: the functions the user -writes, the values integrators pass around, and the trajectories a solution returns. - -**This is not aspirational — it is implemented and tested.** -`test/suite/shape/test_shape_contract.jl` pins it at OptimalControl's boundary: for `n=1, m=1` -the recorded callback arguments are `isa Number` on the **direct** path *and* on the -**indirect** path, and a third testset asserts the two paths present the *same* shape. That -last one is the seam nothing upstream can check. - -Four consequences the pages must respect: - -1. **Write 1-D scalar-style.** `lagrange(t, x, u, v) = 0.5u^2`, not `0.5u[1]^2`. - `x0 = 1.0`, not `[1.0]`. `xf::Real`, not `xf::Vector`. -2. **The in-place buffer is the exception, and stays a vector.** A scalar cannot be mutated, - so the derivative buffer is always an `n`-vector written by index, *even for `n = 1`*: - - ```julia - # 1-D dynamics: r is a length-1 vector; x, u, v are scalars - f!(r, t, x, u, v) = (r[1] = -x + u; nothing) - ``` - - Inputs follow "1-D = scalar"; the output buffer does not. State the asymmetry once, on - `modelling/functional-api.md`, and do not repeat it everywhere. -3. **`[1]`-indexed code still works** (`x[1] == x` for a scalar), so migrating a reader's - script is non-breaking. Say that once, on the migration page. But **do not teach it** — a - guide that writes `u[1]` for a scalar control is teaching the pre-rule mental model. -4. **Coercion is dimension-driven, not value-driven.** `only` when `dim == 1`, `identity` - otherwise. - -> ⚠️ **The single largest factual error in the old documentation.** -> `attic/manual-macro-free.md:252-279` is a section titled *"Scalar vs vector: a subtlety of -> the functional API"* whose entire content is now false. It states that callbacks -> *"always receive `x`, `u`, and `v` as vectors, regardless of their dimension"*, that -> *"dimension-1 components must always be indexed"*, and closes with a `!!! warning` calling -> the callback/solution asymmetry *"intentional"*. The rule was created to **remove** that -> asymmetry, and it did. The section must be deleted, not corrected — see -> [`03-modelling.md`](03-modelling.md). - -#### 8.4 Code cells - -- ≤ 75 characters per line. Long calls wrap with one argument per line and a trailing comma - (`Handbook/philosophy/documentation.md` §"Code cell line width"). -- Submodule imports bind the name only: `using CTBase: Data`, never `using CTBase.Data`. - In practice guides should not need either — everything is re-exported. -- Never `using LinearAlgebra` in a page that also demonstrates `⋅` deprecation. - -#### 8.5 Anchors and links - -- Every page that others link to declares an explicit anchor: - `# Title` followed by `{#id}` is not Documenter syntax — use - `# [Title](@id section-id)`, as the current pages do - (`manual-abstract.md` uses `@id manual-abstract-syntax`). -- Anchor ids follow the file path: `modelling/abstract-syntax.md` → `@id modelling-abstract-syntax`. -- Links to sibling-package docs use `@extref` and **must** have an `InterLinks` entry in - `make.jl`. CTLie has none today — [`01-infrastructure.md`](01-infrastructure.md) adds it. - Until it exists, do not write `[`ad`](@extref)`. - -#### 8.6 Admonitions - -Reserved meanings, so they stay scannable: - -- `!!! tip` — a shortcut or a better spelling. -- `!!! note` — a cross-reference or a scope statement. -- `!!! warning` — a v2.0 spelling that no longer works, or a silent-failure trap - (e.g. `H isa AbstractHamiltonian` is now `false`). -- `!!! danger` — unused. If it feels warranted, the page is wrong. - -#### 8.7 Page template - -```markdown -# [Title](@id section-page) - -One paragraph: what you can do on this page, and when you'd want to. - -## -...worked, executed example... - -## -... - -## See also -- [related guide](@ref other-page) -- [API reference](@ref api-theme) -``` - -No page ends without a "See also". No page opens with a function signature. - -### 9. Report template for sections 02–08 - -Each per-section report follows this shape: - -```markdown -#
— specification - -**PR**: #N · **Depends on**: ... · **Status**: ... - -## Objective -Which question the section answers, for which profile (§3). - -## Pages -| id | title | path | source | -(source = `attic/.md §
` or `new`) - -## Page details -### -- **Purpose** — one sentence -- **Outline** — the `##` headings -- **API covered** — explicit symbol list (feeds 99-api-coverage.md) -- **Executed examples** — what must actually run -- **Source** — harvest from the attic, or new -- **API traps** — what the old page says that is now false - -## Outgoing links -## Acceptance criteria -``` - ---- - -## Part III — Work order and acceptance - -### 10. PR sequence - -See the board in [`README.md`](README.md). Rationale for the order: - -- **Infrastructure first (PR 2)**: nothing can be verified until `docs/Project.toml` resolves. - This PR also archives the old pages and lands the full skeleton, so every later PR is - "fill in pages", never "and also change the nav". -- **API reference early (PR 4)**: later guide PRs link into it. If it lands last, every guide - accumulates broken `@ref`s that `warnonly=true` hides. -- **Modelling → Solve → Results → Flows → Geometry**: each reuses the previous one's examples. - The flow pages inspect trajectories with the accessors documented in Results. -- **Examples after Flows and Geometry**: the singular-control example depends on both. -- **Getting started last (PR 11)**: it is a synthesis and a curated path through everything - else. Writing it first means rewriting it. -- **PR 3 (deprecations) is independent** and can land at any point. - -### 11. Verification, per PR - -1. **Resolve** — `julia --project=docs -e 'using Pkg; Pkg.develop(path="."); Pkg.instantiate()'`. - This currently fails; PR 2 must make it pass. -2. **Draft build** — set `draft = true` in `docs/make.jl` and run - `julia --project=docs docs/make.jl`. Fast; validates structure and links only. -3. **Full build of the touched section** — `draft = false`, and confirm the section's - `@example` blocks execute. -4. **Read the log.** `makedocs` is called with `warnonly=true` (`docs/make.jl:215`), so a - broken `@ref` or an unresolved `@extref` **does not fail the build**. Grep the log for - `Error:`, `Warning:`, `cannot resolve` before declaring a PR green. -5. **VitePress** — `cd docs && npm install && npx vitepress build build/1`. -6. **Coverage** — tick the rows this PR closes in [`99-api-coverage.md`](99-api-coverage.md). - -For PR 3 additionally: run the test suite via `ct-dev-mcp` (`get_test_command` → run with -`tee` → `generate_report`) and confirm `test/suite/reexport/` and `test/suite/flows/` pass -after the three assertions in `test/suite/reexport/test_ctlie.jl:81-84` are rewritten. - -### 12. Global acceptance criteria - -- [ ] `docs/Project.toml` resolves against the root `Project.toml`. -- [ ] `docs/make.jl` runs with `draft = false`, log clean of resolution errors. -- [ ] No user-facing page contains `Lie(`, ` ⋅ `, `HamiltonianLift`, `OptimalControl.VectorField`, - the bare `autonomous=` keyword, or `Flow(ocp, u, g,` — except the Migration page, in inert - fences. The `is_autonomous=` spelling and CTModels' `time_dependence!(…; autonomous=…)` - (its real current signature) are the supported API and do not count; grep with - `grep -nE '(^|[^_])autonomous=' | grep -v time_dependence` to skip both. -- [ ] Every capability in §6 has a page. -- [ ] [`99-api-coverage.md`](99-api-coverage.md) has no uncovered re-exported symbol. -- [ ] `docs/attic/` is gone. -- [ ] Every deprecated v2.0 spelling in the table of §8.2 either throws a `PreconditionError` - naming its replacement, or is documented in the Migration page as unshimmable with the - reason (`augment=`, the `is_`-prefixed keywords). - -### 13. Known repository defects to fix along the way - -Found during the exploration; each belongs to the PR that touches its area. - -| Defect | Evidence | Fix in | -| --- | --- | --- | -| `methods()` docstring says 11 methods / "9 CPU"; real counts are 10 CPU + 2 GPU = 12, and `methods()[9]` is mislabelled | `src/helpers/methods.jl` | PR 6 | -| Module docstrings show `CTSolvers.Modelers.ADNLP()` / `CTDirect.Collocation()`, undefined under `using OptimalControl` | `src/OptimalControl.jl:33-36`, `src/solve/dispatch.jl:31-32`, `src/solve/canonical.jl:40-41` | PR 6 | -| `src/helpers/describe.jl` missing from the 14-file list, so `describe` appears in no generated API page although 4 guide pages document it | `docs/api_reference.jl` | PR 4 | -| `docs/src-literate/tutorial_pre.jl` is never referenced (the Literate loop is hardcoded to `["tutorial.jl"]`) | `docs/make.jl:189` | PR 2 | -| `CTDirect.DirectShooting` exists (`CTDirect.jl/src/direct_shooting.jl`, `id == :direct_shooting`) but is in neither `methods()` nor the registry — unreachable from OptimalControl | — | PR 6: wire it in, or document the limitation | -| `docs/inventories/` referenced 11 times but does not exist | `docs/make.jl:42-98` | PR 2 | -| `docs/src/assets/Manifest.toml` and the Literate outputs (`notebooks/`, `scripts/`) are committed build artifacts | — | PR 2: decide keep-or-gitignore | diff --git a/docs/reports/01-infrastructure.md b/docs/reports/01-infrastructure.md deleted file mode 100644 index a0b9b212a..000000000 --- a/docs/reports/01-infrastructure.md +++ /dev/null @@ -1,336 +0,0 @@ -# Infrastructure — specification - -**PR**: 2 · **Depends on**: 1 · **Status**: specification -**Scope**: `docs/Project.toml`, `docs/make.jl`, `docs/api_reference.jl`, `docs/inventories/`, -`docs/src/.vitepress/`, `docs/src-literate/`, archiving `docs/src/*.md` - -## Objective - -Make the documentation buildable again, land the final navigation, and move the old pages out -of the way — so every later PR is "write pages", never "and also fix the build". - -**This PR ships a green build with mostly empty pages.** That is intentional. A skeleton that -builds is the platform for the eleven PRs that follow. - ---- - -## 1. The versions the docs are written against - -Critical, and easy to get wrong: **the sibling checkouts in `../` are ahead of what -OptimalControl resolves.** The root `Manifest.toml` pulls registry versions: - -| Package | Resolved (root `Manifest.toml`) | Local checkout in `../` | Root `[compat]` | -| --- | --- | --- | --- | -| CTBase | **0.28.9-beta** | 0.29.3 | `"0.28"` | -| CTModels | **0.15.3-beta** | 0.16.1 | `"0.15"` | -| CTFlows | **0.16.3-beta** | 0.17.0 | `"0.16"` | -| CTLie | **0.1.5-beta** | 0.2.0 | `"0.1"` | -| CTSolvers | **0.4.34-beta** | 0.5.2 | `"0.4"` | -| CTDirect | 1.x | 1.0.12 | `"1"` | -| CTParser | 0.8.x | 0.8.17-beta | `"0.8"` | - -**Rule for every docs PR: verify each symbol against the resolved version in -`~/.julia/packages///`, not against `..//src/`.** The two agree on -everything this specification asserts — spot-checked on the points that matter most: - -- `CTLie 0.1.5-beta` (`cHxPr`) exports exactly `ad`, `Lift`, `LiftedHamiltonianFunction`, - `Poisson`, `∂ₜ`, `@Lie`, `dg_ad_backend`, `dg_ad_backend!` (`src/CTLie.jl:50-56`). - No `Lie`, no `⋅`. -- `CTFlows 0.16.3-beta` (`PsDmR`) has the same ten `Flow` methods as the checkout - (`src/Flows/building.jl:33,66,118,157,269,406,478,557,792,1019`), the same - `constraint=`/`multiplier=` keyword pair, and `variable_costate` on the call - (`src/Flows/calling.jl:93`). - -But the two are **not** identical everywhere. Any API detail this spec does not explicitly -cite must be re-checked before it goes in a page. - -> **Decision (settled with the maintainer, 2026-08-09): `docs/Project.toml` mirrors the root -> `Project.toml` `[compat]` exactly. Do not chase the newer releases.** -> -> The ecosystem has moved a full breaking unit past OptimalControl's `[compat]` (CTBase 0.29, -> CTModels 0.16, CTFlows 0.17, CTLie 0.2, CTSolvers 0.5) and those releases are stable — but -> **CTDirect and CTParser have not been updated and still require the older versions**, so -> the newer ones simply will not resolve. Bumping is a separate, source-side -> `upgrade-v2.1.0-beta`-sized PR that has to start upstream, and it is not a prerequisite for -> this work. -> -> Practical consequence for every docs PR: the root `Project.toml` is the single source of -> truth for `[compat]`. If a page needs an API that only exists in a newer sibling release, -> the page is wrong, not the pin. - ---- - -## 2. `docs/Project.toml` - -### 2.1 Missing dependencies — the reason nothing works - -| Add | Why | -| --- | --- | -| `CTLie` | owns `ad`, `Lift`, `Poisson`, `∂ₜ`, `@Lie`. The whole Geometry section is undocumentable without it, and `make.jl` cannot `setdocmeta!` its docstrings | -| `DifferentiationInterface` | arms `CTBaseDifferentiationInterface`. **Without it every `ad` / `Poisson` / `∂ₜ` / `@Lie` call is inert** and AD-backed `Flow`s do not build | -| `ForwardDiff` | the concrete backend behind the above | -| `OrdinaryDiffEqTsit5` | the integrator every flow page's preamble loads. `OrdinaryDiffEq` (already present) is the heavy meta-package; the docs should show the light one, as `BREAKING.md` §"Start here" does | - -### 2.2 Compat realignment - -Every CT pin is a breaking unit or more behind the root. Align to the root `[compat]`, and -apply the ecosystem granularity rule (`X == 0` → pin the minor; `X ≥ 1` → pin the major -alone; `julia` is the exception, it states a minimum): - -| Entry | Now | Target | Note | -| --- | --- | --- | --- | -| `CTBase` | `"=0.18.8"` | `"0.28"` | drop the exact pin | -| `CTModels` | `"=0.10.1"` | `"0.15"` | drop the exact pin | -| `CTFlows` | `"0.8"` | `"0.16"` | | -| `CTLie` | — | `"0.1"` | new | -| `CTDirect` | `"1"` | `"1"` | ok | -| `CTParser` | `"0.8"` | `"0.8"` | ok | -| `CTSolvers` | `"0.4"` | `"0.4"` | ok | -| `DifferentiationInterface` | — | `"0.7"` | new, matches root | -| `ForwardDiff` | — | `"0.10, 1"` | new, matches root | -| `OrdinaryDiffEqTsit5` | — | `"2"` | new, matches root | -| `ExaModels` | `"0.9"` | `"0.11"` | root says `"0.11"` | -| `CUDA` | `"5"` | `"5, 6"` | root says `"5, 6"` | -| `MadNLP` | `"0.9"` | `"0.9, 0.10"` | root says so | -| `MadNLPGPU` | `"0.8"` | `"0.8, 0.10"` | root says so | -| `OrdinaryDiffEq` | `"6"` | `"6, 7"` | root says so | - -> The exact pins `CTBase = "=0.18.8"` / `CTModels = "=0.10.1"` are a **different** constraint -> from the one that legitimately exists in `OptimalControl/.extras` (where they prevent a -> beta-version skew that breaks CTModels precompilation). Do not carry them over here. - -### 2.3 Deps that may be droppable - -Audit while you are in the file — each costs resolve time and CI minutes: - -| Dep | Used by | Verdict | -| --- | --- | --- | -| `BenchmarkTools` | grep `docs/src` — no hit found | drop unless a page plans to use it | -| `DataFrames` | no hit found | drop | -| `LiveServer` | developer convenience (`servedocs`) | keep, but it belongs in a comment | -| `CommonSolve`, `MarkdownAST` | `make.jl` `using` lines | keep | -| `CUDA`, `MadNLPGPU` | `solve/gpu.md` | keep | -| `NonlinearSolve` | `flows/shooting.md` and the indirect examples | keep — **required** | - -### 2.4 Acceptance - -```bash -julia --project=docs -e 'using Pkg; Pkg.develop(path="."); Pkg.instantiate()' -``` - -must succeed. It currently does not. Delete `docs/Manifest.toml` first — it is gitignored -(`docs/.gitignore`), so regenerating is free. - ---- - -## 3. `docs/make.jl` - -### 3.1 Dead extension handle — a latent crash - -`docs/make.jl:108`: - -```julia -const CTFlowsODE = Base.get_extension(CTFlows, :CTFlowsODE) -``` - -That extension no longer exists. CTFlows' extensions are now `CTFlowsPlots`, -`CTFlowsSciMLFlows`, `CTFlowsSciMLIntegrator`, `CTFlowsStaticArrays`. The call returns -`nothing`, `nothing` is pushed into `Modules` (`make.jl:124`), and the `setdocmeta!` loop at -`make.jl:126-129` interpolates it into `:(using $Module)`. - -**Fix**: drop the `CTFlowsODE` line, and make the whole extension-handle block defensive: - -```julia -_ext(pkg, sym) = Base.get_extension(pkg, sym) - -Modules = Any[CTBase, CTLie, CTFlows, CTDirect, CTModels, CTSolvers, - CTParser, OptimalControl] - -for (pkg, syms) in [ - CTModels => (:CTModelsJLD, :CTModelsJSON, :CTModelsPlots), - CTSolvers => (:CTSolversIpopt, :CTSolversKnitro, - :CTSolversMadNLP, :CTSolversMadNCL), - CTFlows => (:CTFlowsPlots, :CTFlowsSciMLFlows, - :CTFlowsSciMLIntegrator), -] - for s in syms - m = _ext(pkg, s) - isnothing(m) || push!(Modules, m) - end -end -``` - -An unarmed extension must skip, never crash. `api/public.md:34` also references -`CTFlowsODE.AbstractFlow`; that file disappears entirely in PR 4. - -### 3.2 CTLie is absent everywhere - -Three additions: - -1. `using CTLie` alongside the other CT packages (`make.jl:7-13`). -2. `CTLie` in `Modules`, so `setdocmeta!` gives its docstrings a `DocTestSetup`. -3. An `InterLinks` entry (§4). - -Without (1) and (2), `@docs ad` cannot resolve and no doctest in the Geometry section runs. - -### 3.3 `pages=` - -Replace the whole `pages=` block with the sitemap from -[`00-cahier-des-charges.md`](00-cahier-des-charges.md) §7. Keep the -`# index.md is the VitePress root — not listed here` comment; it is correct and non-obvious. - -The `"API Reference"` node keeps coming from the `with_api_reference` do-block, but its shape -changes in PR 4 — see [`09-api-reference.md`](09-api-reference.md). **PR 2 leaves -`api_reference.jl` alone** apart from §3.5. - -### 3.4 Literate - -`make.jl:189` hardcodes `for file in ["tutorial.jl"]`, and `tutorial_postprocess` is an -identity function with a commented-out body. Two cleanups: - -- Delete `docs/src-literate/tutorial_pre.jl` — never referenced. -- Either delete `tutorial_postprocess` or restore its intent. The commented line injects - `@meta Draft = false` so the tutorial executes even under a global `draft = true`; that is - useful, and the comment above it (`make.jl:191`) still claims it happens. Decide and make - the code and the comment agree. - -The guided tour keeps the Literate pipeline (markdown + notebook + script). Its output path -moves to `docs/src/getting-started/guided-tour.md` — see -[`02-getting-started.md`](02-getting-started.md). - -### 3.5 `describe` is invisible in the API - -`docs/api_reference.jl:29-44` lists 14 source files. `src/helpers/describe.jl` is **not** -among them, so `describe` — documented on four separate guide pages — appears in no generated -API page. Add it to the list. (Cheap, and independent of the PR 4 rework.) - ---- - -## 4. `InterLinks` and `docs/inventories/` - -`make.jl:42-98` declares 11 `InterLinks` entries, each with a third element -`joinpath(@__DIR__, "inventories", ".toml")`. **`docs/inventories/` does not exist.** -`DocumenterInterLinks` tries each location in order and uses the first reachable one, so this -silently degrades to "always fetch from the network" — every build depends on -control-toolbox.org being up, and `@extref` to an unreleased sibling cannot resolve at all. - -Three actions: - -1. **Add CTLie** to the `InterLinks` list. Until this lands, no page may write - `[`ad`](@extref)` — the reference will not resolve and `warnonly=true` will hide it. -2. **Create `docs/inventories/`** with the fallback inventories, or delete the third element - from every entry so the intent is honest. Prefer creating it. -3. **Point the CT entries at the sibling local builds first**, per - `Handbook/philosophy/documentation.md` §"Local fallback for sibling packages": - -```julia -"CTLie" => ( - "https://control-toolbox.org/CTLie/stable/", - joinpath(@__DIR__, "..", "..", "CTLie", - "docs", "build", "1", "objects.inv"), - "https://control-toolbox.org/CTLie/stable/objects.inv", -), -``` - -First reachable wins, so this resolves locally during cross-repo development and falls back to -the published inventory in CI. - -Note the URL: CTLie's repository has **no `.jl` suffix** (`../CTLie`, not `../CTLie.jl`), -unlike CTFlows.jl / CTModels.jl / CTDirect.jl / CTParser.jl. Same for CTBase and CTSolvers. -Check the deployed URLs before committing them. - ---- - -## 5. Archiving the old pages - -Decision (recorded in [`README.md`](README.md)): **archive, do not delete.** - -``` -docs/attic/ ← git mv every docs/src/*.md here -``` - -- `git mv` all 22 root-level `docs/src/*.md` **except `index.md`**, plus - `docs/src/api/public.md` and `docs/src/api/subpackages.md`. -- `index.md` stays put — it is the VitePress root and PR 11 rewrites it in place. -- `docs/attic/` is **not** under `docs/src/`, so Documenter never sees it and VitePress never - builds it. No `pages=` entry, no `.gitignore` entry needed. -- Add `docs/attic/README.md`: one line saying this is the pre-v2.1 documentation kept for - harvesting, and that PR 12 deletes the directory. -- Per-section reports cite harvest sources as `attic/.md §
`. - -Also move, since they are outputs of a page being rewritten: - -- `docs/src/notebooks/tutorial.ipynb` and `docs/src/scripts/tutorial.jl` — regenerated by - Literate on every build. **Decide whether they should be committed at all**; they are build - artifacts sitting in the source tree. Recommendation: gitignore them, keep only - `docs/src-literate/tutorial.jl` as the source of truth. Same question for - `docs/src/assets/Manifest.toml` (127 KB, copied by `make.jl:135`). - -Assets that **stay**: `docs/src/assets/{chariot.svg, chariot_q.svg, rocket-def.png, -custom.css}`, `docs/src/.vitepress/**`, `docs/src/components/*.vue`. - ---- - -## 6. Page skeleton - -Create every file in the §7 sitemap with a minimal stub so the build is green from day one: - -```markdown -# [Plot a solution](@id results-plot) - -!!! warning "Under construction" - This page is being written. See [the specification](https://github.com/...). -``` - -Rules for the stubs: - -- The `@id` anchor is **final** from PR 2 on. Later PRs fill content; they never rename - anchors, because other PRs will already be linking to them. -- Anchor naming: file path with `/` → `-`. `flows/from-ocp.md` → `@id flows-from-ocp`. -- The `!!! warning` block disappears when the page is written. - -This is what makes the PRs independent: PR 8 can link `[the solution object](@ref results-solution)` -before PR 7 has written a word of it. - ---- - -## 7. VitePress - -`docs/src/.vitepress/config.mts` needs **no structural change** — the sidebar is injected by -DocumenterVitepress (`sidebar: 'REPLACE_ME_DOCUMENTER_VITEPRESS'`, line 106) from `pages=`. - -Two things to preserve, both non-obvious: - -- **The `{{`/`}}` escaping plugin** (`config.mts:61-71`). It exists so - `` `@Lie {{H, K}, L}` `` is not parsed as a Vue template expression. The Geometry section - will use that notation heavily. Do not touch it. -- **`nav`** (`config.mts:20-23`) is just `Home` + the version picker. With nine top-level - sections in the sidebar that is fine; if the sidebar gets crowded, `sidebarDrawer` is already - on (`make.jl:218`). - -`docs/package.json` and the theme CSS need no change. - ---- - -## 8. Acceptance criteria - -- [ ] `julia --project=docs -e 'using Pkg; Pkg.develop(path="."); Pkg.instantiate()'` succeeds. -- [ ] `docs/Project.toml` has `CTLie`, `DifferentiationInterface`, `ForwardDiff`, - `OrdinaryDiffEqTsit5`; no `=` pin; every CT compat matches the root. -- [ ] `docs/make.jl` has `using CTLie`, `CTLie` in `Modules`, no `CTFlowsODE`, and the - extension loop skips `nothing`. -- [ ] `docs/inventories/` exists (or the third `InterLinks` element is gone), and CTLie has an - entry. -- [ ] `src/helpers/describe.jl` is in the `api_reference.jl` file list. -- [ ] `docs/src-literate/tutorial_pre.jl` deleted. -- [ ] Every page of the §7 sitemap exists as a stub with its final `@id`. -- [ ] `docs/attic/` holds the 24 old markdown files and a `README.md`. -- [ ] `julia --project=docs docs/make.jl` completes with `draft = true`, **and the log has no - `cannot resolve` / `Error:` lines** (`warnonly=true` will not fail the build for you). -- [ ] `cd docs && npm install && npx vitepress build build/1` completes. - -## Outgoing links - -- Sitemap and conventions: [`00-cahier-des-charges.md`](00-cahier-des-charges.md) §7, §8 -- API reference rework that lands next: [`09-api-reference.md`](09-api-reference.md) -- Guided tour output path: [`02-getting-started.md`](02-getting-started.md) diff --git a/docs/reports/02-getting-started.md b/docs/reports/02-getting-started.md deleted file mode 100644 index 6f475a19a..000000000 --- a/docs/reports/02-getting-started.md +++ /dev/null @@ -1,169 +0,0 @@ -# Getting started — specification - -**PR**: 11 · **Depends on**: PRs 5–10 · **Status**: specification -**Scope**: `docs/src/index.md`, `docs/src/getting-started/*`, `docs/src-literate/tutorial.jl` - -## Objective - -Take someone from `] add OptimalControl` to a solved, plotted problem without leaving the -section, then hand them a curated path into the rest of the site. Written **last** because it -is a synthesis: it links everywhere, so writing it before the targets exist means writing it -twice. - -Primary profile: **Applied** ([`00-cahier-des-charges.md`](00-cahier-des-charges.md) §3). -The Geometric and Simulator profiles get one signpost each at the end of the guided tour. - -## Pages - -| id | title | path | source | -| --- | --- | --- | --- | -| `index` | OptimalControl.jl | `index.md` | `docs/src/index.md` (in place, largely survives) | -| `getting-started-installation` | Installation | `getting-started/installation.md` | new (harvest `index.md` §Installation) | -| `getting-started-first-problem` | Your first problem | `getting-started/first-problem.md` | new | -| `getting-started-guided-tour` | Guided tour | `getting-started/guided-tour.md` | `attic/tutorial.md` + `src-literate/tutorial.jl` | - ---- - -## Page details - -### `index.md` — the VitePress root - -Not listed in `pages=` (`docs/make.jl:221`). Stays where it is; PR 11 edits it in place. - -- **Keep**: the scope paragraph, `## [Mathematical formulation](@id math-formulation)` — the - Bolza cost, dynamics, the four constraint families, free times and extra variables — the - `## Citing us` block with the Zenodo DOI, `## Contributing`, and the whole - `## Reproducibility` machinery (`_downloads_toml`, the three `@raw html` `
`). -- **Move out**: `## Installation` → `getting-started/installation.md`. Leave a one-line - pointer. -- **Rewrite**: `## Basic usage`. It is currently an inert ```` ```julia ```` fence. Make it - an executed `@example`, and make it identical to the opening of - `getting-started/first-problem.md` so the reader recognises it. -- **Add**: a short "Where to go next" table — one row per top-level section, one sentence - each. This is the only place on the site that surveys everything. - -**API traps**: the four cross-refs at the end of `## Basic usage` point at -`example-double-integrator-energy`, `manual-abstract-syntax`, `manual-solve`, `manual-plot`. -All four anchors change. New targets: `@ref examples-double-integrator-energy`, -`@ref modelling-abstract-syntax`, `@ref solve-overview`, `@ref results-plot`. - ---- - -### `getting-started/installation.md` - -- **Purpose** — get the package and the optional pieces installed, and explain *why* there are - optional pieces. -- **Outline** - - `## Install` — `] add OptimalControl` - - `## You will also need a solver` — `NLPModelsIpopt` for the default path; a table of the - alternatives (`MadNLP`, `MadNCL`, `Knitro`, `Uno`) and what each needs - - `## Optional: plotting` — `Plots`, and what `ExtensionError(:Plots)` looks like without it - - `## Optional: flows` — `OrdinaryDiffEqTsit5`, and the bare `MethodError` without it - - `## Optional: saving solutions` — `JLD2`, `JSON3` - - `## Optional: GPU` — `ExaModels`, `MadNLPGPU`, `CUDA` - - `## Checking your setup` — a snippet that loads everything and prints `methods()` -- **API covered** — none directly; `methods()` in the last section. -- **Executed examples** — the setup check only. Everything else is inert `] add` fences. -- **Source** — new. Harvest the `!!! tip` linking control-toolbox discussion #64 from - `index.md`. -- **API traps** — the current `index.md` implies `using OptimalControl, NLPModelsIpopt, Plots` - is the whole story. Since v2.1.0-beta the flow path needs an integrator too, and that is - the single most common first failure. Give it a `!!! warning`. - -**Why this page exists at all.** The optional-dependency structure is a deliberate design -decision (`BREAKING.md` §"Start here": keeping SciML's install cost off users who never write -a flow). Users hit it as a mysterious `MethodError`. One page, early, converts a trap into a -feature. - ---- - -### `getting-started/first-problem.md` - -- **Purpose** — the shortest complete story: define, solve, look at it. Fifteen lines of code, - no options, no theory. -- **Outline** - - `## The problem` — double integrator, energy minimisation, with the maths - - `## Define it` — `@def` - - `## Solve it` — bare `solve(ocp)` - - `## Look at it` — `plot(sol)`, then `objective(sol)`, `iterations(sol)`, `successful(sol)` - - `## What just happened` — `solve` picked `(:collocation, :adnlp, :ipopt, :cpu)`; point at - `@ref solve-choosing-a-method` - - `## Next` -- **API covered** — `@def`, `solve`, `plot`, `objective`, `iterations`, `successful`, - `state`, `control`, `time_grid`. -- **Executed examples** — all of it. This page must be `Draft = false`-clean. -- **Source** — new; the model problem is `attic/example-double-integrator-energy.md` - §"problem definition". -- **API traps** — none new, but respect **1-D is a scalar**: the control is scalar, so - `control(sol)(t)` returns a `Number`. - -The problem to use (same as `index.md` §Basic usage): - -```julia -@def ocp begin - t ∈ [0, 1], time - x ∈ R², state - u ∈ R, control - x(0) == [-1, 0] - x(1) == [0, 0] - ẋ(t) == [x₂(t), u(t)] - ∫(0.5u(t)^2) → min -end -``` - ---- - -### `getting-started/guided-tour.md` — Literate - -- **Purpose** — the one long-form narrative on the site. Double integrator end to end - (direct **and** indirect), then Goddard as a realistic problem. -- **Source** — `docs/src-literate/tutorial.jl` (503 lines). The structure survives; the code - needs a full v2.1 audit. -- **Pipeline** — `Literate.markdown` / `.notebook` / `.script`, as today - (`docs/make.jl:189-199`), but output to `docs/src/getting-started/`, `.../notebooks/`, - `.../scripts/`. Update the Binder badge path. -- **Outline** (keep the existing arc) - - The problem, and installing - - `@def` and the macro-free form side by side - - First solve; initial guess; the costate - - Goddard: direct method in depth, grid continuation - - GPU - - Indirect: PMP, the shooting function, the flow - - Going further — **three** signposts, one per profile of §3 -- **API traps to fix in `tutorial.jl`** - - Preamble must gain `using OrdinaryDiffEqTsit5` **before** the first `Flow`. - - The indirect section: check every flow call for a positional variable - (`f(t0,x0,p0,tf,λ)` → `variable=λ`) and for `augment=` (→ `variable_costate=`). - - `has_abstract_definition`, `definition`, `describe`, `methods` — all still valid. - - The `methods()` count is quoted in prose somewhere; the real number is **12** - (10 CPU + 2 GPU), and `src/helpers/methods.jl`'s own docstring is wrong about it. Fix - both in PR 6, then quote the right figure here. -- **Acceptance** — the notebook and the script regenerate; the markdown executes with - `Draft = false`. - ---- - -## Outgoing links - -| From | To | -| --- | --- | -| `index.md` "Where to go next" | every top-level section | -| `installation.md` | `@ref solve-choosing-a-method`, `@ref results-plot`, `@ref flows-overview`, `@ref solve-gpu` | -| `first-problem.md` | `@ref modelling-abstract-syntax`, `@ref solve-overview`, `@ref results-solution`, `@ref results-plot` | -| `guided-tour.md` | `@ref modelling-functional-api`, `@ref solve-initial-guess`, `@ref solve-gpu`, `@ref flows-shooting`, `@ref geometry-overview`, `@ref examples-singular-control` | - -> The Examples section has **no landing page** in the sitemap — it is a flat list of six -> problems. Nothing may link to a bare `@ref examples`; link to a specific example. If a -> gallery index turns out to be wanted, it is a PR 10 addition, not an assumption. - -## Acceptance criteria - -- [ ] A reader can install, define, solve and plot without leaving the section. -- [ ] `installation.md` names all four optional-dependency families and the error each one's - absence produces. -- [ ] `first-problem.md` executes end to end with `Draft = false`. -- [ ] `tutorial.jl` has no v2.0 spelling left (grep it against - [`00-cahier-des-charges.md`](00-cahier-des-charges.md) §8.2). -- [ ] The notebook and script outputs regenerate at the new paths and the Binder badge points - at the new location. -- [ ] `index.md`'s four cross-refs resolve to the new anchors. diff --git a/docs/reports/03-modelling.md b/docs/reports/03-modelling.md deleted file mode 100644 index e4bb18759..000000000 --- a/docs/reports/03-modelling.md +++ /dev/null @@ -1,250 +0,0 @@ -# Modelling — specification - -**PR**: 5 · **Depends on**: PR 2 · **Status**: specification -**Scope**: `docs/src/modelling/*` - -## Objective - -Everything about *describing* a problem, before anything is solved: the two front ends -(`@def` and the functional API), the control-free case, and how to read a model back. - -This is the first content PR because every later section's examples start with a model. - -## Pages - -| id | title | path | source | -| --- | --- | --- | --- | -| `modelling-formulation` | Mathematical formulation | `modelling/formulation.md` | `docs/src/index.md` §`math-formulation` (moved) | -| `modelling-abstract-syntax` | Abstract syntax (`@def`) | `modelling/abstract-syntax.md` | `attic/manual-abstract.md` (survives well) | -| `modelling-functional-api` | Functional API | `modelling/functional-api.md` | `attic/manual-macro-free.md` (trim heavily) | -| `modelling-without-control` | Problems without a control | `modelling/without-control.md` | **new** — harvest `attic/example-control-free.md` §intro | -| `modelling-inspect` | Inspect a problem | `modelling/inspect.md` | `attic/manual-model.md` (survives well) | -| `modelling-with-ai` | Write a problem with AI | `modelling/with-ai.md` | `attic/manual-ai-llm.md` | - ---- - -## Page details - -### `modelling/formulation.md` - -- **Purpose** — the mathematics the rest of the site refers back to. One page, no code. -- **Outline** - - `## Bolza problem` — $J(x,u) = g(x(t_0),x(t_f)) + \int_{t_0}^{t_f} f^0$ - - `## Dynamics` - - `## Constraints` — the four families: state box, control box, nonlinear path, boundary - - `## Mayer, Lagrange, Bolza` - - `## Free times and extra variables` — $J(x,u,t_0,t_f)$, then $v \in \mathbb{R}^k$ - - `## The control-free case` — degenerate $m = 0$; forward-link `@ref modelling-without-control` -- **API covered** — none. Deliberately. -- **Source** — lift `index.md` §`math-formulation` verbatim. **Keep the `@id math-formulation` - anchor alive** — `manual-macro-free.md` links to it today and other pages will. -- **Why it moves out of `index.md`**: the landing page should be an orientation, not a - mathematics section. `index.md` keeps a two-line summary and links here. - -### `modelling/abstract-syntax.md` - -- **Purpose** — the complete `@def` reference. The most-visited page on the site. -- **Outline** (the existing structure is good; keep it) - - `## variable` · `## time` · `## state` · `## control` · `## no control` - - `## dynamics` — including the coordinatewise form `∂(x)(t)` - - `## constraints` — box, nonlinear path, boundary, labelled - - `## cost` — Mayer, Lagrange, Bolza, `→ min` / `→ max` - - `## aliases` - - `## Known issues` -- **API covered** — `@def` (and `@def` with an explicit target). Note that `@def_exa` - (`CTParser.jl/src/onepass.jl:1361`) is **not** re-exported — either surface it or say - nothing; do not mention it as available. -- **Executed examples** — every syntax fragment should build a real model. -- **Source** — `attic/manual-abstract.md` (646 lines) is largely correct; it documents the - parser, which did not change. Audit rather than rewrite. -- **API traps** — the "no control" section must forward-link to - `@ref modelling-without-control`, and must state the rule explicitly: **a zero-dimensional - control is expressed by never declaring one.** `control!(pre, 0)` is *rejected* - (`CTModels.jl/src/Building/control.jl:77`). - -### `modelling/functional-api.md` - -- **Purpose** — build the same model without macros, for programmatic construction. -- **Outline** - - `## When you want this` — generated problems, loops over families, no parser - - `## The canvas` — the maths-to-builder mapping table - - `## A worked example` — the same double integrator as `@def`, side by side - - `## Shapes in callbacks` — **the new section that replaces the deleted one**, see below - - `## Order and preconditions` — what must be called before what - - `## Equivalence` — the two front ends produce equivalent models -- **API covered** — `PreModel`, `time!`, `state!`, `control!`, `variable!`, `dynamics!`, - `objective!`, `constraint!`, `time_dependence!`, `build`. -- **Source** — `attic/manual-macro-free.md` is 898 lines, the largest page on the site, and - it ends with an inline API-reference section of `@docs; canonical=false` blocks. - **Delete that inline section** — PR 4 generates it. Target ≈ 350 lines. - -- **⚠️ API trap #1 — the biggest factual error on the whole old site.** - - `attic/manual-macro-free.md:252-279`, *"Scalar vs vector: a subtlety of the functional - API"*, is **entirely false now**. Verbatim from the current page: - - > *"the functional API callbacks always receive `x`, `u`, and `v` as **vectors**, - > regardless of their dimension"* (line 256) - > - > *"inside callbacks, dimension-1 components must always be indexed"* (line 254) - > - > *"This asymmetry is intentional"* (line 278) - - The ecosystem's "1-D is a scalar" rule was created to **remove** exactly that asymmetry, - and it is now implemented and tested — `test/suite/shape/test_shape_contract.jl` asserts - `x isa Number` and `u isa Number` for `n=1, m=1` through the full `solve` stack, again - through `Flow`, and a third testset asserts the two paths agree. - - **Delete the section. Do not correct it.** Replace it with `## Shapes in callbacks`: - - - 1-D state, control and variable arrive as **scalars**; dimension ≥ 2 as vectors. - - The **in-place buffer is the exception**: `r` (and `dx`, `val`, `b`) is always a - length-`n` vector written by index, *even for `n = 1`*, because a scalar cannot be - mutated. `f!(r, t, x, u, v) = (r[1] = -x + u; nothing)`. - - This is the one place on the site where that asymmetry is explained. Every other page - just follows it. - - Consequence for the rest of the page: **rewrite every callback that indexes a - dimension-1 quantity into scalar style.** `lagrange_energy(t, x, u, v) = 0.5 * u[1]^2` - (line 213) becomes `0.5 * u^2`; `dx[2] = u[1]` (line 194) becomes `dx[2] = u`; - `v[1]` for a scalar variable becomes `v` (lines 330-332, 367, 449-474, 558-568). - `dx[1] = …` and `b[1] = …` stay — those are buffers. - - `x[1]` still evaluates to the scalar, so nothing *breaks*; but a guide that writes `u[1]` - for a scalar control is teaching the pre-rule mental model. Mention the compatibility - once on `@ref migration`, not here. - -- **API trap #2** — `PreModel` and `Model` are **imported, not exported**: write - `OptimalControl.PreModel()`. -- **API trap #3** — the equivalence claim is a real, tested contract: - `test/suite/problems/test_forms_equivalent.jl` asserts it for every library problem. Say - so; it is the reason to trust the page. - -### `modelling/without-control.md` — **new page, currently invisible on the site** - -- **Purpose** — model an ODE with parameters and no control: parameter estimation, fitting, - and the "optimise a constant" family. Serves the **Simulator** profile. -- **Outline** - - `## What this is for` — fitting a dynamical system to data; optimising a constant - - `## How to declare it` — declare a `variable`, a time, a state, dynamics, a cost; **omit - the control line entirely** - - `## Worked example: exponential growth` — fit $\dot x = p\,x$, $x(0)=2$ to - $2e^{t/2}$ by minimising $\int_0^{10} (x - x_{\text{obs}})^2$; expect $p = 0.5$ - - `## Worked example: harmonic oscillator` — $\ddot q = -\omega^2 q$, analytic - $\omega = \pi/2$ - - `## How the package knows` — `is_control_free(ocp)` / `has_control(ocp)`; the trait - `_control_dependence(::EmptyControlModel) = Traits.ControlFree` - (`CTModels.jl/src/Models/model.jl:160`) - - `## Solving it directly` — plain `solve` - - `## Solving it indirectly` — `Flow(ocp)` takes **no** control law in this case; forward-link - `@ref flows-from-ocp` - - `## Adding a control back` — one paragraph + link to - `@ref examples-control-and-variable` -- **API covered** — `@def` without a control, `is_control_free`, `has_control`, `variable`, - `Flow(ocp)`, `solve`. -- **Source** — new page. Harvest the intro and both problems from - `attic/example-control-free.md`; that file becomes the *example* (see - [`08-examples.md`](08-examples.md)) while this one is the *guide*. -- **API traps** — three, all sharp: - 1. `control!(ocp, 0)` is an error, not a way to say "no control". - 2. `Flow(ocp)` with **no** law works only in the control-free case - (`CTFlows.jl/src/Flows/building.jl:157` → `_flow_from_ocp(::Type{Traits.ControlFree}, …)`). - With a control it throws a `PreconditionError` telling you to use `Flow(ocp, law)`. - 3. `constraint=` / `multiplier=` on a control-free `Flow(ocp)` are **rejected** — there is - no pseudo-Hamiltonian to carry $\mu \cdot g$. - -Fixtures already exist and are tested: `test/problems/control_free.jl` (`ExponentialGrowth`, -`HarmonicOscillator`), both built in the abstract *and* functional forms. - -### `modelling/inspect.md` - -- **Purpose** — read a model back: dimensions, names, dynamics, costs, constraints, traits. - Also the "is this model what I think it is" workflow. -- **Outline** - - `## Times` · `## State` · `## Control` · `## Variable` - - `## Dynamics` · `## Cost` · `## Constraints` - - `## Traits` — autonomous? variable? control-free? abstractly defined? - - `## The original definition` — `definition`, `has_abstract_definition` -- **API covered** — the full getter surface: - `initial_time`, `final_time`, `times`, `time_name`, `initial_time_name`, `final_time_name`, - `has_fixed_initial_time`, `has_free_initial_time`, `has_fixed_final_time`, - `has_free_final_time`, `is_initial_time_fixed`, `is_initial_time_free`, - `is_final_time_fixed`, `is_final_time_free`; - `state_dimension`, `state_name`, `state_components`, `control_dimension`, `control_name`, - `control_components`, `variable_dimension`, `variable_name`, `variable_components`; - `components`, `dimension`, `name`, `index`, `expression`; - `dynamics`, `objective`, `mayer`, `lagrange`, `criterion`, `has_mayer_cost`, - `has_lagrange_cost`, `is_mayer_cost_defined`, `is_lagrange_cost_defined`; - `constraint`, `constraints`, `path_constraints_nl`, `boundary_constraints_nl`, - `state_constraints_box`, `control_constraints_box`, `variable_constraints_box`, - `dim_path_constraints_nl`, `dim_boundary_constraints_nl`, `dim_state_constraints_box`, - `dim_control_constraints_box`, `dim_variable_constraints_box`; - `is_autonomous`, `is_nonautonomous`, `is_variable`, `is_nonvariable`, `has_variable`, - `has_control`, `is_control_free`; - `definition`, `has_abstract_definition`, `is_abstractly_defined`. -- **Source** — `attic/manual-model.md` (721 lines) is accurate and well organised. Audit, - reorganise into the outline above, drop the "model struct" internals section — that is - API-reference material now. -- **API traps** — `time(ocp)` is gone (it is `Base.time`); the accessor is `times(ocp)`. - PR 3 makes the old spelling throw with that message. - -### `modelling/with-ai.md` - -- **Purpose** — get an LLM to write the `@def` for you, including from a photograph of a - problem statement. -- **Outline** — unchanged: the prompt template, the `@raw html` provider buttons, three - transcripts (double integrator, coordinatewise/ExaModels-compatible form, Goddard from - `assets/rocket-def.png`). -- **Source** — `attic/manual-ai-llm.md`, near-verbatim. -- **API traps** — the prompt template points the model at the DSL page. **Update the URL** to - `modelling/abstract-syntax`. Everything else is inert ```` ```julia ```` fences and needs no - execution. -- **Note** — this page is the only one whose code blocks are deliberately *not* executed - (they are model output, quoted). Say so at the top so a reviewer does not "fix" it. - ---- - -## Outgoing links - -| From | To | -| --- | --- | -| `formulation.md` | `@ref modelling-abstract-syntax`, `@ref modelling-without-control` | -| `abstract-syntax.md` | `@ref modelling-functional-api`, `@ref modelling-without-control`, `@ref solve-overview`, `@ref modelling-with-ai` | -| `functional-api.md` | `@ref math-formulation`, `@ref modelling-abstract-syntax` | -| `without-control.md` | `@ref flows-from-ocp`, `@ref examples-control-free`, `@ref examples-control-and-variable` | -| `inspect.md` | `@ref results-solution` (the symmetric page), API reference | -| `with-ai.md` | `@ref modelling-abstract-syntax` | - -## Acceptance criteria - -- [x] Every symbol in the `inspect.md` list appears on the page and executes. -- [x] `without-control.md` exists and both fixtures run, reproducing $p = 0.5$ and - $\omega = \pi/2$. -- [x] `functional-api.md` no longer contains an inline `@docs` block, and is under ~400 lines - (267). -- [x] No page writes `OptimalControl.VectorField`-style qualification for an exported symbol; - `PreModel` and `Model` *are* qualified, correctly. -- [ ] ~~The `@id math-formulation` anchor still resolves after the move.~~ **Dropped by - decision, not implemented**: Documenter only recognises `@id` when the link is the sole - child of a *heading* node (`namedheader`, `expander_pipeline.jl`) — a standalone - `[](@id math-formulation)` anchor is not real Documenter syntax and was silently treated - as a broken link, caught by a full rebuild. No currently-built page references - `@ref math-formulation` (only `docs/attic/manual-macro-free.md`, not part of the live - build) so nothing breaks; kept only `@id modelling-formulation`, matching the sitemap - convention. `docs/reports/02-getting-started.md`'s mention of keeping this section on - `index.md` is a latent inconsistency with this PR's move — left for PR 11 to reconcile. -- [x] `with-ai.md`'s prompt template points at the new DSL page URL, confirmed against the - actual built route (`/modelling/abstract-syntax`, no `.html` — DocumenterVitepress - clean URLs) via a full `docs/build/1` inspection, not just the source text. - -Verified by an independent full rebuild (`julia --project=docs docs/make.jl` + -`npx vitepress build build/1`), not just re-checked against the diff: zero "undefined -binding"/"no docs found"/"duplicate docs found" (regression check on PR 4), zero build -errors, zero failed or un-expanded `@example` blocks on any of the six pages, zero unresolved -`@ref`s originating from these pages. One genuine upstream bug found and filed in the -process, unrelated to this PR's own content but triggered by it: `plot(sol)` throws -`IncorrectArgument("a VBox needs at least one child")` on the *default* call for any plain -direct-solved solution once `layout=:split`'s default description includes `:costate` -([`CTModels.jl#392`](https://github.com/control-toolbox/CTModels.jl/issues/392)) — worked -around in both affected pages with an explicit `plot(sol, :state, :control)`/`plot(sol, -:state)` description. diff --git a/docs/reports/04-solve-direct.md b/docs/reports/04-solve-direct.md deleted file mode 100644 index aa2b084d6..000000000 --- a/docs/reports/04-solve-direct.md +++ /dev/null @@ -1,260 +0,0 @@ -# Solve (direct) — specification - -**PR**: 6 · **Depends on**: PR 5 · **Status**: specification -**Scope**: `docs/src/solve/*`, plus three docstring fixes in `src/` - -## Objective - -Make `solve` fully understood — not just "call it", but *what it chose for you and how to -choose differently*. The user's own framing: "résoudre avec les méthodes directes via la -méthode solve, donc bien comprendre les possibilités de cette méthode." - -Primary profile: **Applied**. - -## Pages - -| id | title | path | source | -| --- | --- | --- | --- | -| `solve-overview` | Solve a problem | `solve/overview.md` | `attic/manual-solve.md` | -| `solve-initial-guess` | Set an initial guess | `solve/initial-guess.md` | `attic/manual-initial-guess.md` | -| `solve-choosing-a-method` | Choosing a method | `solve/choosing-a-method.md` | **new** — split out of `attic/manual-solve.md` | -| `solve-options` | Options and routing | `solve/options.md` | `attic/manual-solve-advanced.md` | -| `solve-explicit-mode` | Explicit mode | `solve/explicit-mode.md` | `attic/manual-solve-explicit.md` | -| `solve-gpu` | Solving on GPU | `solve/gpu.md` | `attic/manual-solve-gpu.md` | - -**Why the split.** `attic/manual-solve.md` currently mixes "call `solve`", "available -methods", "choosing a method", "solver requirements" and "passing options to strategies" in -304 lines. Method selection is the single richest thing about this API — 12 combinations -across 4 families — and deserves its own page. - ---- - -## The mental model the section must convey - -Three layers, all spelled `solve`, all `CommonSolve.solve`: - -| Layer | File | What it does | -| --- | --- | --- | -| Dispatch | `src/solve/dispatch.jl` | `solve(ocp, description::Symbol...; kwargs...)`; detects the mode, resolves the registry | -| Descriptive | `src/solve/descriptive.jl` | completes the symbol tuple, routes flat kwargs to families, builds the strategies | -| Explicit | `src/solve/explicit.jl` | takes typed components, completes what is missing | -| Canonical | `src/solve/canonical.jl` | `solve(ocp, guess, discretizer, modeler, solver; display)` — no defaults, no completion | - -**The one thing every user must know**: the mode is detected **by argument type, not by -keyword name** (`src/solve/mode_detection.jl`, `src/helpers/kwarg_extraction.jl:_extract_kwarg`). -A kwarg whose *value* is an `AbstractDiscretizer`/`AbstractNLPModeler`/`AbstractNLPSolver` -switches you into explicit mode. Mixing typed components with symbol tokens throws -`IncorrectArgument`. This is surprising and belongs on `solve/overview.md`, not buried in the -advanced page. - ---- - -## Page details - -### `solve/overview.md` - -- **Purpose** — call `solve`, read what it printed, know what it decided. -- **Outline** - - `## Quick start` — `sol = solve(ocp)` - - `## Reading the display` — the configuration table, and where each value came from - (`:user` / `:default` / `:computed`) - - `## Turning the display off` — `display=false` - - `## The defaults` — `(:collocation, :adnlp, :ipopt, :cpu)`, and *why*: completion picks - the first match top-to-bottom in `methods()` - - `## Two ways to steer it` — descriptive (symbols) vs explicit (typed); **detection is by - type**; mixing is an error. Links to both pages. - - `## When it fails` — `successful(sol)`, `status(sol)`, `message(sol)`, - `constraints_violation(sol)` -- **API covered** — `solve`, `display` kwarg, `successful`, `status`, `message`, - `constraints_violation`, `iterations`. -- **Source** — `attic/manual-solve.md` §"Quick start", §"Display", §"Solver requirements". -- **API traps** — none in the prose; the code is current. - -### `solve/initial-guess.md` - -- **Purpose** — every way to hand `solve` a starting point. -- **Outline** - - `## The default guess` — what you get if you say nothing - - `## The `@init` macro` — `q(t) := sin(t)`, `x(T) := X`, `u := 0.1`, the `log = true` - trailing argument - - `## Constants, vectors, functions` — and how vectors are interpolated onto the grid - - `## Mixing them` - - `## Warm start from a solution` — pass a `Solution` directly - - `## Costate and multipliers` - - `## `init` or `initial_guess`` — aliases; passing both is an `IncorrectArgument` -- **API covered** — `@init`, `build_initial_guess`, the `init=` / `initial_guess=` kwargs. -- **Source** — `attic/manual-initial-guess.md` (627 lines). Drop the final "legacy NamedTuple - construction" section, or move it to `migration.md`. -- **API traps** - - `_INITIAL_GUESS_ALIASES` (`src/helpers/descriptive_routing.jl:81`) is what makes both - spellings work; supplying both throws (`_extract_action_kwarg`). - - Several CTModels init helpers exist but are **not** re-exported — - `initial_guess`, `pre_initial_guess`, `validate_initial_guess`, `initial_state`, - `initial_control`, `initial_variable`, `PreInitialGuess`. Do not document them as - available. Flag as an open question whether they should be surfaced. - -### `solve/choosing-a-method.md` — **new page** - -- **Purpose** — the map of what can be combined with what, and how to ask for it. -- **Outline** - - `## The four families` — discretizer, NLP modeler, NLP solver, parameter (device) - - `## What is available` — `methods()`, printed live. **12 combinations**: 10 CPU - (`{:adnlp, :exa} × {:ipopt, :madnlp, :uno, :madncl, :knitro}`) + 2 GPU - (`:exa × {:madnlp, :madncl}`) - - `## Partial descriptions` — `solve(ocp, :madnlp)` completes the rest; completion takes the - first match top-to-bottom, which is why the default is `(:collocation, :adnlp, :ipopt, :cpu)` - - `## Ambiguity` — what `AmbiguousDescription` looks like and how to resolve it - - `## What each solver needs installed` — a table: `:ipopt` → `NLPModelsIpopt`, `:madnlp` → - `MadNLP`, `:madncl` → `MadNCL`, `:knitro` → `NLPModelsKnitro` (+ licence), `:uno` → - `UnoSolver` - - `## Inspecting a strategy` — `describe(:adnlp)`, `describe(:ipopt)`, `describe(:collocation)`; - note that `describe` also covers the **indirect** side (`:di`, `:sciml`) and the parameters - (`:cpu`, `:gpu`) - - `## Discretization schemes` — `scheme=` (alias `disc_method=`): `:trapeze`, `:midpoint` - (default), `:euler` / `:euler_explicit` / `:euler_forward`, `:euler_implicit` / - `:euler_backward`, `:gauss_legendre_2`, `:gauss_legendre_3`, `:variable`; plus `grid_size` - (default 250) and an explicit non-uniform `time_grid` -- **API covered** — `methods`, `describe`, `id`, `metadata`, `option_names`, `option_type`, - `option_default`, `option_defaults`, `option_description`, `has_option`, `strategy_ids`, - `type_from_id`, `parameter`, `default_parameter`, `available_parameters`, `create_registry`. -- **Source** — `attic/manual-solve.md` §"Available methods", §"Choosing a method", - §"Solver requirements"; discretizer options from `CTDirect.jl/src/collocation.jl`. -- **API traps / repo defects to fix in this PR** - 1. **`src/helpers/methods.jl`'s docstring is wrong**: it claims `length(m) == 11` and - "CPU methods (9 total)", and says `methods()[9] == (:collocation, :exa, :madnlp, :gpu)`. - The truth is 10 + 2 = 12, and `methods()[9]` is `(:collocation, :exa, :madncl, :cpu)`. - Fix the docstring, then let the page print `methods()` live rather than quoting a number. - 2. **`CTDirect.DirectShooting` — out of scope, decided.** It exists - (`CTDirect.jl/src/direct_shooting.jl`, `id == :direct_shooting`) but is not functional - yet, which is why it is in neither `src/imports/ctdirect.jl` nor `methods()`/the - registry. **Do not wire it in and do not mention it on the page.** The section - documents `:collocation` as the only discretizer, without qualification. - 3. `describe` is missing from `docs/api_reference.jl`'s file list — fixed in PR 2, verify. - -### `solve/options.md` - -- **Purpose** — how a flat keyword finds its way to the right strategy, and the two escape - hatches. -- **Outline** - - `## Option routing` — flat kwargs are routed by name in **`:strict` mode**: an unknown - option is an `IncorrectArgument`, not a silent no-op - - `## Ambiguous options` — when two strategies declare the same name; - `route_to(adnlp=:sparse, ipopt=:cpu)` - - `## Undeclared solver options` — `bypass(v)` (alias `force(v)`) skips validation - - `## Where a value came from` — `option_value`, `option_source`, `is_user`, `is_default`, - `is_computed` - - `## Action options vs strategy options` — `init`/`initial_guess` and `display` are handled - before routing and win over a same-named strategy option unless you `route_to` -- **API covered** — `route_to`, `bypass`, `force`, `options`, `option_value`, `option_source`, - `is_user`, `is_default`, `is_computed`, `has_option`. -- **Source** — `attic/manual-solve-advanced.md` (208 lines), accurate. Add the - action-vs-strategy precedence rule, which is currently undocumented. -- **API traps** — `RoutedOption` and `BypassValue` are imported, not exported; do not name - the types in examples, only the functions. - -### `solve/explicit-mode.md` - -- **Purpose** — hand `solve` typed components instead of symbols. -- **Outline** - - `## When you want this` — programmatic construction, reusing a configured strategy - - `## Basic usage` - - `## Partial components` — give one, the rest is completed - - `## Per-component options` - - `## Mixing modes is forbidden` - - `## Inspecting the components you built` -- **API covered** — `solve` with `discretizer=`/`modeler=`/`solver=`, `options`, `methods`, - `bypass`, `is_user`, `is_default`, plus the constructors - `OptimalControl.Collocation()`, `OptimalControl.ADNLP()`, `OptimalControl.Exa()`, - `OptimalControl.Ipopt()`, `OptimalControl.MadNLP()`, `OptimalControl.MadNCL()`, - `OptimalControl.Knitro()`, `OptimalControl.Uno()`. -- **Source** — `attic/manual-solve-explicit.md` (252 lines). It already uses the correct - `OptimalControl.` spelling — it is the only page that does. -- **API traps / repo defect to fix in this PR** - - **Three module docstrings show a spelling that does not work**: - `src/OptimalControl.jl:33-36`, `src/solve/dispatch.jl:31-32`, - `src/solve/canonical.jl:40-41` all write `CTSolvers.Modelers.ADNLP()` / - `CTSolvers.Solvers.Ipopt()` / `CTDirect.Collocation()`. Neither `CTSolvers` nor `CTDirect` - is re-exported, so those names are undefined under `using OptimalControl`. Fix all three - to `OptimalControl.ADNLP()` etc. - - The component types (`Collocation`, `ADNLP`, `Exa`, `Ipopt`, `MadNLP`, `MadNCL`, - `Knitro`, `Uno`) are **imported, not exported** — the `OptimalControl.` prefix is - mandatory, and this page must say why in one sentence. - -### `solve/gpu.md` - -- **Purpose** — run the same problem on a GPU. -- **Outline** - - `## Prerequisites` — `ExaModels`, `MadNLPGPU`, `CUDA` - - `## The problem must be coordinatewise` — link back to - `@ref modelling-abstract-syntax` §dynamics - - `## Descriptive mode` — the `:gpu` token - - `## Explicit mode` — parameterised strategy types - - `## What combinations work` — `:exa` × {`:madnlp`, `:madncl`} only - - `## Performance notes` -- **API covered** — `solve` with `:gpu`, `CPU`, `GPU`, `available_parameters`, - `default_parameter`, `parameter`, `describe(:gpu)`. -- **Source** — `attic/manual-solve-gpu.md` (175 lines), accurate. -- **API traps** — none known. Note that `:cpu`/`:gpu` mean the same thing on the flow side - (`Flow(...; method=:gpu)`), tested by `test/suite/flows/test_gpu_routing.jl` — cross-link - from `@ref flows-overview`. -- **Build caveat** — this page cannot execute in CI without a GPU. Guard it with - ```` ```@meta\nDraft = true\n``` ```` or keep its blocks inert, and say so at the top. - ---- - -## Outgoing links - -| From | To | -| --- | --- | -| `overview.md` | `@ref solve-choosing-a-method`, `@ref solve-explicit-mode`, `@ref solve-initial-guess`, `@ref results-solution` | -| `initial-guess.md` | `@ref results-solution` (warm start), `@ref modelling-abstract-syntax` | -| `choosing-a-method.md` | `@ref solve-options`, `@ref solve-gpu`, API reference | -| `options.md` | `@ref solve-choosing-a-method`, `@ref solve-explicit-mode` | -| `explicit-mode.md` | `@ref solve-options`, `@ref solve-choosing-a-method` | -| `gpu.md` | `@ref modelling-abstract-syntax`, `@ref flows-overview` | - -## Acceptance criteria - -- [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/05-flows-indirect.md b/docs/reports/05-flows-indirect.md deleted file mode 100644 index d9d92fe74..000000000 --- a/docs/reports/05-flows-indirect.md +++ /dev/null @@ -1,381 +0,0 @@ -# Flows (indirect) — specification - -**PR**: 8 · **Depends on**: PR 6 (and reads from PR 7) · **Status**: specification -**Scope**: `docs/src/flows/*`, plus one re-export decision in `src/imports/ctflows.jl` - -## Objective - -The largest and most-changed section. It covers three distinct jobs that share one -constructor: - -1. **Indirect optimal control** — build the Hamiltonian flow of the PMP, write a shooting - function, solve it. -2. **Simulation** — integrate a controlled system under an open-loop or feedback control, and - inspect the result exactly like an optimal control solution. -3. **Inspection** — pull the Hamiltonian, the Hamiltonian vector field, the pseudo-Hamiltonian - or the control law back out of a flow you built. - -Profiles: **Geometric** (1, 3) and **Simulator** (2). Job 2 is currently invisible on the -site; job 3 is documented nowhere. - -> **Every page in this section opens with `using OrdinaryDiffEqTsit5`.** SciML is not a hard -> dependency; without an integrator, `Flow` fails with a bare `MethodError`. No exceptions. - -## Pages - -| id | title | path | source | -| --- | --- | --- | --- | -| `flows-overview` | Flows and indirect methods | `flows/overview.md` | **new** | -| `flows-from-ocp` | Flows from an optimal control problem | `flows/from-ocp.md` | `attic/manual-flow-ocp.md` (heavy rework) | -| `flows-from-hamiltonians` | Flows from Hamiltonians and vector fields | `flows/from-hamiltonians.md` | `attic/manual-flow-others.md` (108 lines → full page) | -| `flows-simulation` | Simulating a controlled system | `flows/simulation.md` | **new** | -| `flows-accessors` | What you can get back from a flow | `flows/accessors.md` | **new** | -| `flows-multi-phase` | Multi-phase flows | `flows/multi-phase.md` | **new** — split out of `attic/manual-flow-ocp.md` §concatenation | -| `flows-constrained-arcs` | Constrained arcs | `flows/constrained-arcs.md` | `attic/manual-flow-ocp.md` §state constraints | -| `flows-shooting` | Writing a shooting function | `flows/shooting.md` | **new** — harvest `attic/example-double-integrator-time.md` | - ---- - -## Reference: the constructor catalogue - -Every page in this section must agree with this table. Verified against **CTFlows 0.16.3-beta** -(`~/.julia/packages/CTFlows/PsDmR/src/Flows/building.jl`), which is what OptimalControl -resolves — not the `../CTFlows.jl` checkout. - -| # | Call | Line | Returns | -| --- | --- | --- | --- | -| 1 | `Flow(VectorField(f))` | 33 | `StateFlow` | -| 2 | `Flow(HamiltonianVectorField(hvf))` | 66 | `HamiltonianFlow` | -| 3 | `Flow(Hamiltonian(h))` | 118 | `HamiltonianFlow` (AD) | -| 4 | `Flow(ocp)` — **control-free only** | 157 | `OptimalControlFlow` | -| 5 | `Flow(PseudoHamiltonian(h̃), law)` | 269 | `HamiltonianFlow` | -| 6 | `Flow(PseudoHamiltonianVectorField(h̃vf), law)` | 406 | `HamiltonianFlow` | -| 7 | `Flow(ControlledVectorField(fc), law)` | 478 | `ControlledFlow` | -| 8 | `Flow(ocp, law::ControlLaw)` | 557 | `OptimalControlFlow` or `ControlledFlow` | -| 9 | `Flow(ocp, u::Function)` | 792 | `OptimalControlFlow` (auto-wrapped `DynClosedLoop`) | -| — | `Flow(ocp, ::Any, args...)` | 1019 | **always throws** `PreconditionError` | -| 10 | `Flow(::SciMLBase.AbstractODEFunction)` / `(::AbstractODEProblem)` | SciML ext | `StateFlow` / `SciMLProblemFlow` | - -**There is no `Flow(f::Function)`.** A bare function must be wrapped in the `CTBase.Data` type -that says what it is. PR 3 makes the old spelling throw with that message. - -Which law type selects which behaviour: - -| Law | With an OCP | With a `ControlledVectorField` | With a `PseudoHamiltonian` | -| --- | --- | --- | --- | -| `DynClosedLoop((x,p) -> …)` | `OptimalControlFlow` (Hamiltonian) | `PreconditionError` | ✅ the only accepted law | -| `ClosedLoop(x -> …)` | `ControlledFlow` (state) | ✅ | `PreconditionError` | -| `OpenLoop(t -> …)` | `ControlledFlow` (state) | ✅ | `PreconditionError` | - -Call signatures: - -```julia -(f::AbstractStateFlow)(t0, x0, tf; variable, unsafe) -(f::AbstractStateFlow)(tspan, x0; variable, unsafe) -(f::AbstractHamiltonianFlow)(t0, x0, p0, tf; variable, unsafe, variable_costate) -(f::AbstractHamiltonianFlow)(tspan, x0, p0; variable, unsafe, variable_costate) -``` - -Three keywords that must be explained somewhere in the section: - -- **`variable=v`** — mandatory on a `NonFixed` problem. Omitting it raises a - `PreconditionError` whose suggestion is literally *"Pass `variable=v` when calling the - flow"*. There is **no positional slot** any more. -- **`variable_costate=true`** — integrates $\dot p_v = -\partial H/\partial v$ and returns - `(xf, pf, pvf)`. This replaced `augment=true`. -- **`unsafe=true`** — do not check the ODE retcode, do not throw on failure. **Essential - inside a shooting loop**, where an intermediate failure should surface through the residual - rather than abort the solve. Document it on `flows/shooting.md`. - ---- - -## Page details - -### `flows/overview.md` — new - -- **Purpose** — the map of the section, and the PMP recap that makes the rest make sense. -- **Outline** - - `## Why flows` — direct discretises, indirect integrates; when each wins - - `## The Pontryagin Maximum Principle, briefly` — pseudo-Hamiltonian - $H(t,x,p,u,v) = p \cdot f + p^0 f^0$, the maximisation condition, the control law - $u^*(x,p)$, the boundary/transversality conditions - - `## From the PMP to a flow` — you supply $u^*$, `Flow` gives you $\exp(t\vec H)$ - - `## Three things this section does` — indirect solving, simulation, inspection - - `## Before you start` — `using OrdinaryDiffEqTsit5`, and what happens without it - - `## Choosing an integrator and its options` — `describe(:sciml)`, `describe(:di)`; - `alg=`, `reltol=`, `abstol=`, `saveat=`, `dense=`; the AD family routes to `:di`, - the integrator family to `:sciml` - - `## CPU and GPU` — `method=:cpu` / `:gpu`, the same tokens as `solve` - - `## Where to go` -- **API covered** — `Flow` (named, not detailed), `describe(:sciml)`, `describe(:di)`, - `SciML`, `AbstractIntegrator`. -- **API traps** — `describe` covering the *indirect* strategies (`:di`, `:sciml`) is a real - capability nobody documents. `OptimalControl.get_full_strategy_registry()` merges the solve - registry with `CTFlows.Flows.flow_registry()`; mention it as the introspection entry point. - -### `flows/from-ocp.md` - -- **Purpose** — the main path: an OCP plus a control law gives you the PMP flow. -- **Outline** - - `## The idea` — you did the PMP by hand, you have $u^*(x,p)$ - - `## The simplest form` — `Flow(ocp, (x, p) -> p[2])` - - `## Passing a typed law` — `Flow(ocp, DynClosedLoop((x,p) -> …))`, and why you would - - `## Non-autonomous problems` — `u(t, x, p)` - - `## Problems with a variable` — `u(x, p, v)` / `u(t, x, p, v)`; **`variable=` is - mandatory at call time** - - `## Free final time and the augmented costate` — `variable_costate=true` - - `## Control-free problems` — `Flow(ocp)` with no law; link `@ref modelling-without-control` - - `## Total or partial Hamiltonian` — `hamiltonian_type=:total` (default; composes a - `ComposedHamiltonian`, differentiates through the law) vs `:partial` - (`PseudoHamiltonianSystem`, partials at frozen feedback). Tested by - `test/suite/problems/test_hamiltonian_type.jl` - - `## What comes back` — an `OptimalControlFlow`; a trajectory call returns a - `CTModels.Solution`, so everything in `@ref results-solution` applies -- **API covered** — `Flow`, `DynClosedLoop`, `ControlLaw`, `variable=`, `variable_costate=`, - `unsafe=`, `hamiltonian_type=`, `method=`. -- **Source** — `attic/manual-flow-ocp.md` (685 lines, 23 `Flow` calls). Split: concatenation - → `multi-phase.md`, state constraints → `constrained-arcs.md`, bare-vector-field → - `from-hamiltonians.md`. What is left is this page. -- **API traps** — the whole page. Specifically: - - `Flow(x -> x)` at `attic/manual-flow-ocp.md:504-505` no longer exists. - - `Flow(ocp, u, g, μ)` at `attic/manual-flow-ocp.md:594` is now keywords. - - `augment=true` → `variable_costate=true`. - - `OptimalControl.Hamiltonian(...)` → bare `Hamiltonian(...)`. - - `u`'s arity is **validated** against the OCP's time/variable dependence; a mismatch is an - `IncorrectArgument`. Show the error once — it is a good error. - -### `flows/from-hamiltonians.md` - -- **Purpose** — build a flow when you do *not* start from an OCP. Currently 108 lines; this is - a full page. -- **Outline** — one section per constructor, each with a runnable snippet: - - `## From a vector field` — `Flow(VectorField(f))`; the plain ODE case - - `## From a Hamiltonian` — `Flow(Hamiltonian(h))`; $\vec H$ obtained by AD - - `## From a Hamiltonian vector field` — `Flow(HamiltonianVectorField(hvf))`; no AD - - `## From a pseudo-Hamiltonian and a control law` — - `Flow(PseudoHamiltonian(h̃), DynClosedLoop(u))`; `hamiltonian_type=` - - `## From a pseudo-Hamiltonian vector field and a control law` — - `Flow(PseudoHamiltonianVectorField(h̃vf), DynClosedLoop(u))`; no `hamiltonian_type` here - - `## From a SciML problem` — `Flow(ODEFunction(...))`, `Flow(ODEProblem(...))` - - `## Non-autonomous and variable-dependent forms` — `is_autonomous=`, `is_variable=` - - `## Summary table` — which constructor uses AD, which returns what -- **API covered** — `Flow`, `VectorField`, `Hamiltonian`, `HamiltonianVectorField`, - `PseudoHamiltonian`, `PseudoHamiltonianVectorField`, `ControlledVectorField`, - `ComposedHamiltonian`, `ComposedVectorField`, `DynClosedLoop`, `is_autonomous=`, - `is_variable=`, `is_inplace=`. -- **Source** — `attic/manual-flow-others.md` gives the skeleton; everything else is new. -- **API traps** — `attic/manual-flow-others.md:98` writes - `Flow((t,x) -> …; autonomous=false)`. Both halves are wrong now: no `Flow(::Function)`, and - the keyword is `is_autonomous`. Correct: `Flow(VectorField(f; is_autonomous=false))`. - -### `flows/simulation.md` — new, serves the Simulator profile - -- **Purpose** — *"construire des flots à partir de contrôles en boucle ouverte pour calculer - des solutions particulières du système contrôlé"*, and the closed-loop counterpart. -- **Outline** - - `## The idea` — you have a controlled system and a control; you want the trajectory, not - an optimum - - `## Open loop` — `Flow(ControlledVectorField(fc), OpenLoop(t -> …))` - - `## Closed loop (feedback)` — `Flow(ControlledVectorField(fc), ClosedLoop(x -> …))` - - `## From an optimal control problem` — `Flow(ocp, OpenLoop(u))` returns a `ControlledFlow` - that also carries the **objective**, so you can evaluate a candidate control's cost - - `## Inspecting the trajectory` — `state`, `control`, `objective`, `time_grid` — the same - accessors as a solution; link `@ref results-solution` - - `## Plotting it` — `plot(traj)`; link `@ref results-plot` - - `## Point vs trajectory` — `f(t0, x0, tf)` returns the endpoint; - `f((t0, tf), x0)` returns a trajectory -- **API covered** — `Flow`, `ControlledVectorField`, `OpenLoop`, `ClosedLoop`, `state`, - `control`, `objective`, `time_grid`, `plot`. -- **API traps** - - **`OpenLoop` is now unconditionally non-autonomous**: the law is `u(t)` or `u(t, v)`. - `OpenLoop(() -> 1.0)` is gone. This is a semantic change that will not announce itself as - an import error — give it a `!!! warning`. - - `DynClosedLoop` with a `ControlledVectorField` is a `PreconditionError`. - - `Flow(ControlledVectorField, law)` returns a trajectory with **no objective** (there is no - OCP); `Flow(ocp, OpenLoop(u))` does have one. Say which is which. - - The trajectory type is `StateFlowTrajectory` (renamed from `ControlledTrajectory`, no - alias) — but it is **not re-exported**, so never name it in an example. - -### `flows/accessors.md` — new; the user asked for this explicitly - -- **Purpose** — *"quand il construit un flot il a accès à des accesseurs. Par exemple depuis - un ocp et une loi de commande, il peut récupérer le hamiltonien, ou le champ de vecteurs - hamiltoniens."* -- **Outline** - - `## What a flow remembers` - - `## The Hamiltonian` — `hamiltonian(f)` → callable `H(t,x,p,v)` - - `## The Hamiltonian vector field` — `hamiltonian_vector_field(f)` → - `HamiltonianVectorField` $= (\partial_p H, -\partial_x H)$ - - `## The pseudo-Hamiltonian and the control law` — `pseudo_hamiltonian(f)` → - $\tilde H(t,x,p,u,v)$, `control_law(f)` → the law you passed - - `## Gradients` — `get_hamiltonian_gradient(f)`, `get_variable_gradient(f)`, - `get_pseudo_hamiltonian_gradient(f)`, `get_pseudo_variable_gradient(f)` - - `## Building $\vec H$ from a Hamiltonian without a flow` — - `hamiltonian_vector_field(h::AbstractHamiltonian)` - - `## What is available on which flow` — a table - - `## The underlying system and integrator` — `system(f)`, `integrator(f)`; escape hatch -- **Availability table** (must be in the page, it is the whole point): - -| Built from | `hamiltonian` | `hamiltonian_vector_field` | `pseudo_hamiltonian` | `control_law` | -| --- | --- | --- | --- | --- | -| `Hamiltonian(h)` | ✅ | ✅ | ✗ | ✗ | -| `HamiltonianVectorField(hvf)` | ✗ | ✅ | ✗ | ✗ | -| `PseudoHamiltonian(h̃), law` | ✅ | ✅ | ✅ | ✅ | -| `PseudoHamiltonianVectorField(h̃vf), law` | ✗ | ✅ | ✗ | ✗ | -| `ocp, law` | ✅ | ✅ | ✅ | ✅ | -| `VectorField(f)` | ✗ | `vector_field` | ✗ | ✗ | - -Asking for an unavailable accessor raises `IncorrectArgument`. - -- **✅ Re-export gap — decided 2026-08-09: close it.** - - `src/imports/ctflows.jl:16` re-exports `control_law` and `pseudo_hamiltonian` but **not** - their siblings `hamiltonian`, `hamiltonian_vector_field`, `vector_field`, `system`, - `integrator`, or the four `get_*_gradient` functions — even though all are exported by - `CTFlows.Flows` / `CTFlows.Systems`. As it stands the page would write - `CTFlows.Systems.hamiltonian(f)` for one accessor and bare `control_law(f)` for its - sibling, which is indefensible. - - **PR 8 adds to `src/imports/ctflows.jl`:** - - ```julia - @reexport import CTFlows.Systems: - hamiltonian, hamiltonian_vector_field, vector_field, - get_hamiltonian_gradient, get_variable_gradient, - get_pseudo_hamiltonian_gradient, get_pseudo_variable_gradient - ``` - - Verified against the resolved environment: none of these names collides with anything - CTBase, CTModels, CTSolvers or CTDirect exports. `system` and `integrator` stay - **unexported** — too generic for a DSL surface — and the page qualifies them as - `CTFlows.Flows.system(f)` in the escape-hatch section. - - `test/suite/reexport/test_ctflows.jl` must gain the matching `reexports(...)` assertions in - the same PR, and [`99-api-coverage.md`](99-api-coverage.md) §8 must move these rows into - the exported table. - -### `flows/multi-phase.md` — new (split out) - -- **Purpose** — concatenate arcs: bang–bang switchings, jumps, boundary arcs. -- **Outline** - - `## Concatenating two flows` — `f1 * (t1, f2)` - - `## Jumps` — `f1 * (t1, jump, f2)`; on a Hamiltonian flow, - `h1 * (t, jump_x, jump_p, h2)` - - `## Jumps as callables` — `x -> x'`, `(x,p) -> (x',p')`, or `nothing` - - `## Calling a multi-phase flow` - - `## Inspecting one` — `n_phases`, `get_flow`, `get_flows`, `get_switching_time`, - `get_switching_times`, `get_jump`, `get_jumps` - - `## Worked example` — the bang–bang time-optimal double integrator -- **API covered** — `Base.:*` on flows, `MultiPhaseFlow`, `MultiPhaseStateFlow`, - `MultiPhaseHamiltonianFlow`, `AnyMultiPhaseFlow`, `n_phases`, `get_flow`, `get_flows`, - `get_switching_time`, `get_switching_times`, `get_jump`, `get_jumps`. -- **Source** — `attic/manual-flow-ocp.md` §concatenation + - `attic/example-double-integrator-time.md`. -- **API traps** — the multi-phase accessors are **all newly re-exported** and appear on no - current page. This page is their only home. - -### `flows/constrained-arcs.md` - -- **Purpose** — flows on a boundary arc, with a path constraint and its multiplier. -- **Outline** - - `## The setting` — a state constraint active on a sub-interval - - `## Building the constrained flow` — `Flow(ocp, u; constraint=g, multiplier=μ)` - - `## Three ways to give the constraint` — a plain `Function`, a `Data.StateConstraint` / - `ControlConstraint` / `MixedConstraint` / `PathConstraint`, or **a `Symbol` naming a - `:path` constraint already declared in the OCP** - - `## Several constraints at once` — matched tuples - - `## Assembling the arcs` — unconstrained → boundary → unconstrained, with the costate - jump; link `@ref flows-multi-phase` - - `## Partial Hamiltonian on a constrained arc` — now supported -- **API covered** — `Flow` with `constraint=`/`multiplier=`, `StateConstraint`, - `ControlConstraint`, `MixedConstraint`, `PathConstraint`, `Multiplier`. -- **Source** — `attic/manual-flow-ocp.md` §state constraints, §jump on the costate; - `attic/example-state-constraint.md` (518 lines) for the worked material. -- **API traps** - - Positional `Flow(ocp, u, g, μ)` is gone; the keywords come **as a pair** — one without the - other is an `IncorrectArgument`. - - The `Symbol` spelling (`constraint=:vmax`) is a **capability gain**, not a rename. Lead - with it; it is the nicest thing in this API. - - `constraint=`/`multiplier=` on a **control-free** `Flow(ocp)` are rejected. - -### `flows/shooting.md` — new - -- **Purpose** — the payoff: turn a flow into a shooting function and solve it. -- **Outline** - - `## The shooting equation` — unknown $p_0$ (and switching times, and $t_f$) - - `## A simple shooting function` — out-of-place - - `## In-place, for the solver` - - `## Solving it` — `NonlinearSolve` - - `## `unsafe=true` inside the loop` — why an intermediate integration failure should - become a residual, not an exception - - `## Free final time` — the extra transversality equation, and `variable_costate=true` - - `## Multiple shooting and switching times` — unknowns include the $t_i$; link - `@ref flows-multi-phase` - - `## Getting a starting point from a direct solve` — take `costate(sol)(t0)` from a - direct solution; this is the standard workflow and deserves a section of its own - - `## Checking against the direct solution` -- **API covered** — `Flow`, `costate`, `state`, `control`, `variable`, `time_grid`, - `unsafe=`, `variable_costate=`, plus `NonlinearSolve`. -- **Source** — new page assembled from `attic/example-double-integrator-time.md` - (in-place shooting, switching-time detection, NLE resolution) and - `attic/tutorial.md` §indirect. -- **API traps** — every flow call in the harvested material needs the positional variable and - `augment=` audit. - ---- - -## Outgoing links - -| From | To | -| --- | --- | -| `overview.md` | every page in the section, `@ref solve-overview`, `@ref geometry-overview` | -| `from-ocp.md` | `@ref results-solution`, `@ref modelling-without-control`, `@ref flows-accessors`, `@ref flows-shooting` | -| `from-hamiltonians.md` | `@ref geometry-lift`, `@ref flows-accessors` | -| `simulation.md` | `@ref results-solution`, `@ref results-plot`, `@ref modelling-without-control` | -| `accessors.md` | `@ref flows-from-ocp`, `@ref geometry-overview` | -| `multi-phase.md` | `@ref flows-constrained-arcs`, `@ref examples-double-integrator-time` | -| `constrained-arcs.md` | `@ref flows-multi-phase`, `@ref examples-state-constraint` | -| `shooting.md` | `@ref solve-overview`, `@ref geometry-poisson`, `@ref examples-singular-control` | - -## Acceptance criteria - -- [x] Every page's preamble loads `OrdinaryDiffEqTsit5`. -- [x] All ten constructor forms of the catalogue appear, each with a runnable snippet — - confirmed live across `from-hamiltonians.md` (6) and `from-ocp.md`/`simulation.md` - (the 4 OCP/law forms). -- [x] The law/constructor compatibility table and the accessor availability table are on the - site (`overview.md`/`from-ocp.md` state the law table piecewise; the accessor table is - `accessors.md`'s own centrepiece, verified cell-by-cell, with one spec cell corrected — - `hamiltonian(f)` on a `HamiltonianVectorField`-built flow genuinely throws - `IncorrectArgument`). -- [x] `simulation.md` and `accessors.md` exist — neither has any predecessor. -- [x] The re-export decision on `hamiltonian` / `hamiltonian_vector_field` / `vector_field` / - `get_*_gradient` is taken and implemented (`src/imports/ctflows.jl`), and - `test/suite/reexport/test_ctflows.jl` has the matching testset (73/73 passing). -- [x] `OpenLoop`'s unconditional non-autonomy carries a `!!! warning` (`simulation.md`) — - worded precisely after live verification: a zero-arg closure builds without error and - only fails on first call, not at construction as one might assume. -- [x] No page contains a positional variable, `augment=`, `Flow(f::Function)`, - `Flow(ocp,u,g,μ)`, `autonomous=`, or `OptimalControl.Hamiltonian` **as working syntax**. - Three surviving matches on a grep, all deliberate: `from-hamiltonians.md` shows the - stale `VectorField(g; autonomous=false)` failing (the point of that section), and - `from-ocp.md` mentions `augment=true` once in prose, explaining it was renamed to - `variable_costate=true` — neither presents the old spelling as something that works. -- [x] `unsafe=true` is explained where it matters — `shooting.md`, demonstrated with a - synthetic finite-time-blowup ODE since the worked double-integrator example itself is - too well-behaved to blow up even under a wild guess. - -**Also found and fixed, beyond the checklist above:** -- A reproducible Documenter build quirk, isolated but not fully root-caused: `using - OptimalControl` inside Documenter's `@example` sandbox (a `baremodule`) fails to bind - `hamiltonian`/`hamiltonian_vector_field`/`vector_field`/the four `get_*_gradient` functions - specifically, while `Flow`/`control_law`/`pseudo_hamiltonian` (re-exported the exact same way - from the same `CTFlows.Systems` module) bind fine. Confirmed via extensive bisection that - real, normal usage (`using OptimalControl` in an ordinary session or module) is unaffected — - this is specific to the sandboxed `baremodule` Documenter's `@example`/`@repl` blocks run in. - Worked around with a hidden (`# hide`), redundant direct import in `accessors.md`'s setup - block; does not change what a reader sees or what a real user needs to do. -- The spec's own claim that a flow without an integrator loaded fails with a bare `MethodError` - — corrected on `overview.md`: verified live it's actually a clean `ExtensionError`. -- `method=:cpu`/`:gpu` corrected to be a construction-time keyword (`Flow(...; method=:gpu)`), - not a call-time one as the spec's `overview.md` outline could be read to imply. diff --git a/docs/reports/06-geometry.md b/docs/reports/06-geometry.md deleted file mode 100644 index 7ddb8d946..000000000 --- a/docs/reports/06-geometry.md +++ /dev/null @@ -1,309 +0,0 @@ -# Geometry — specification - -**PR**: 9 · **Depends on**: PR 8 · **Status**: specification -**Scope**: `docs/src/geometry/*` - -## Objective - -The differential-geometry toolkit, which now lives in **CTLie**. The user's framing: *"dans -certains cas, pour définir le flot, comme dans un des exemples avec la singulière, il a besoin -d'outils de la géométrie différentielle pour calculer le contrôle singulier — il faut donc lui -expliquer les outils qu'il a à disposition, sachant que les choses ont bien changé, maintenant -c'est dans CTLie (et `ad` remplace `Lie` par exemple)."* - -**This is the most broken section of the old site.** `attic/manual-differential-geometry.md` -is 634 lines built on `Lie`, `⋅` and `HamiltonianLift` — none of which exist. It is a rewrite, -not an audit. - -Profile: **Geometric**. - -## Pages - -| id | title | path | source | -| --- | --- | --- | --- | -| `geometry-overview` | Differential geometry tools | `geometry/overview.md` | **new** | -| `geometry-lift` | Lifting a vector field | `geometry/lift.md` | `attic/manual-differential-geometry.md` §"Hamiltonian lift" | -| `geometry-ad` | Lie derivative and Lie bracket | `geometry/ad.md` | **rewrite** of §"Lie derivative" + §"Lie bracket" | -| `geometry-poisson` | Poisson bracket | `geometry/poisson.md` | `attic/…` §"Poisson bracket" | -| `geometry-lie-macro` | The `@Lie` macro | `geometry/lie-macro.md` | `attic/…` §"The `@Lie` macro" | -| `geometry-ad-backend` | Choosing an AD backend | `geometry/ad-backend.md` | **new** | - -`∂ₜ` (partial time derivative) folds into `geometry/ad.md` §"Non-autonomous fields" rather -than getting its own page — it is one function with one job. - ---- - -## Reference: the whole surface - -Verified against **CTLie 0.1.5-beta** (`~/.julia/packages/CTLie/cHxPr/`), the version -OptimalControl resolves. `src/CTLie.jl:50-56` exports exactly: - -``` -ad · Lift · LiftedHamiltonianFunction · Poisson · ∂ₜ · @Lie -dg_ad_backend · dg_ad_backend! -``` - -OptimalControl re-exports all of them **except `LiftedHamiltonianFunction`**, which is -imported only (`src/imports/ctlie.jl`) — so it must be written -`OptimalControl.LiftedHamiltonianFunction`. - -Signatures (from the resolved source): - -```julia -ad(X::Function, foo::Function; - ad_backend = dg_ad_backend(), - is_autonomous::Bool = true, - is_variable::Bool = false) # src/ad.jl:40 -ad(X::Function, foo::Function, ::Type{TD}, ::Type{VD}; ad_backend) # src/ad.jl:92 -ad(X::AbstractVectorField, Y::AbstractVectorField; ad_backend) → VectorField -ad(X::AbstractVectorField, f::Function; ad_backend) → Function - -Lift(f::Function; is_autonomous, is_variable) → LiftedHamiltonianFunction # src/lift.jl:68 -Lift(f::Function, ::Type{TD}, ::Type{VD}) → LiftedHamiltonianFunction # :107 -Lift(X::AbstractVectorField) → Hamiltonian # :144 - -Poisson(H::Function, G::Function; ad_backend, is_autonomous, is_variable) # src/poisson.jl:37 -Poisson(H::Function, G::Function, ::Type{TD}, ::Type{VD}; ad_backend) # :82 -Poisson(H::AbstractHamiltonian, G::AbstractHamiltonian; ad_backend) → Hamiltonian - -∂ₜ(f::Function; ad_backend) -∂ₜ(X::AbstractVectorField; ad_backend) → VectorField{NonAutonomous,…} -∂ₜ(X::AbstractHamiltonianVectorField; ad_backend) → HamiltonianVectorField{NonAutonomous,…} -∂ₜ(H::AbstractHamiltonian; ad_backend) → Hamiltonian{NonAutonomous,…} - -@Lie expr [is_autonomous=…] [is_variable=…] [ad_backend=…] # src/lie_macro.jl:510 - -dg_ad_backend() → Differentiation.AbstractADBackend -dg_ad_backend!(backend) → nothing -``` - -Conventions to state once, on `geometry/overview.md`: - -- Lie bracket: $[X, Y](x) = J_Y(x)\,X(x) - J_X(x)\,Y(x)$. -- Poisson bracket: $\{H, G\} = \nabla_p H \cdot \nabla_x G - \nabla_x H \cdot \nabla_p G$. -- The identity that ties them: $\{H_X, H_Y\} = H_{[X,Y]}$ — i.e. - `Poisson(Lift(X), Lift(Y)) ≈ Lift(ad(X, Y))`. Show it as an executed check; it is the best - single demonstration that the two halves of the toolkit agree. - -Errors, all worth showing once because they are good errors: - -| Situation | Exception | -| --- | --- | -| `ad` given an `AbstractHamiltonian` | `IncorrectArgument` — "use `Poisson`" | -| `Poisson` given an `AbstractVectorField` | `IncorrectArgument` — "use `ad`" | -| time/variable dependence mismatch between operands | `PreconditionError` | -| an `InPlace` operand, or a `HamiltonianVectorField` to `ad` | `NotImplemented` | -| `Lift(::HamiltonianVectorField)` | `NotImplemented` | -| `@Lie … autonomous=false` (old keyword) | `IncorrectArgument` at macro-expansion, listing `is_autonomous, is_variable, ad_backend` | - ---- - -## Page details - -### `geometry/overview.md` — new - -- **Purpose** — what the toolkit is for, and the map. -- **Outline** - - `## What this is for` — computing a singular control, checking controllability, building - Hamiltonians from vector fields - - `## The four operations` — `Lift`, `ad`, `Poisson`, `∂ₜ`, one line each - - `## Two vocabularies` — vector fields on the state space vs Hamiltonians on the cotangent - space; `Lift` is the bridge - - `## The bridge identity` — `Poisson(Lift(X), Lift(Y)) ≈ Lift(ad(X, Y))`, executed - - `## Autonomous, non-autonomous, variable` — the `is_autonomous` / `is_variable` keywords - and why operands must agree - - `## Automatic differentiation` — everything except `Lift` is AD-backed; link - `@ref geometry-ad-backend` - - `## Coming from v2.0` — a short table: `Lie` → `ad`, `⋅` → `ad`, - `HamiltonianLift` → `LiftedHamiltonianFunction`; link `@ref migration` -- **API covered** — all of it, by name. -- **API traps** — `attic/manual-differential-geometry.md:7` claims `Hamiltonian`, - `VectorField`, `HamiltonianVectorField` are **not exported** and must be qualified. They are - exported. Delete the admonition and every `OptimalControl.` prefix on them. - -### `geometry/lift.md` - -- **Purpose** — turn a vector field into a Hamiltonian: $H(x,p) = p \cdot X(x)$. -- **Outline** - - `## The lift` — the definition and the one-liner - - `## From a plain function` — `Lift(f)` → a `LiftedHamiltonianFunction` - - `## From a typed vector field` — `Lift(VectorField(f))` → a `Hamiltonian` - - `## Non-autonomous and variable forms` — `is_autonomous=`, `is_variable=`; the call - signatures `h(x,p)`, `h(t,x,p)`, `h(x,p,v)`, `h(t,x,p,v)` - - `## Which one do I get` — a two-row table - - `## What you can do with it` — feed it to `Poisson`, or to `Flow`; link - `@ref flows-from-hamiltonians` -- **API covered** — `Lift`, `OptimalControl.LiftedHamiltonianFunction`, `VectorField`, - `Hamiltonian`. -- **Source** — `attic/manual-differential-geometry.md` §"Hamiltonian lift" is structurally - fine; `Lift` did not change. Only the surrounding vocabulary did. -- **API traps** — one, and it is a **silent** failure, so it gets a `!!! warning`: - - ```julia - H = Lift(F) # F::Function - H isa AbstractHamiltonian # false in v2.1, true in v2.0 - ``` - - `LiftedHamiltonianFunction <: Function`, no longer `<: AbstractHamiltonian`. Any `isa` or - `<:` test against the old hierarchy is now quietly wrong. Note also that `Lift` is - **overloaded on its input**: `Lift(X::AbstractVectorField)` still returns a `Hamiltonian`. - Only the plain-`Function` overload changed. - -### `geometry/ad.md` — rewrite - -- **Purpose** — `ad`: the Lie derivative when the second argument is a scalar function, the - Lie bracket when it is a vector field. -- **Outline** - - `## One function, two meanings` - - `## Lie derivative` — `ad(X, f)` where `f` is scalar-valued; $(\mathcal{L}_X f)(x)$ - - `## Lie bracket` — `ad(X, Y)` where `Y` is a vector field; the convention - - `## Typed operands` — `ad(VectorField(X), VectorField(Y))` returns a `VectorField`, so it - **nests**: `ad(ad(X, Y), Y)` - - `## Non-autonomous and variable fields` — `is_autonomous=`, `is_variable=`; operands must - agree or you get a `PreconditionError` - - `## Partial time derivative` — `∂ₜ`, and the rule that its result is **always** - `NonAutonomous` - - `## Errors you will meet` — the table above - - `## Coming from v2.0` — `Lie(X, f)` → `ad(X, f)`; `X ⋅ f` → `ad(X, f)`, **removed with no - operator replacement** -- **API covered** — `ad`, `∂ₜ`, `VectorField`, `is_autonomous=`, `is_variable=`. -- **Source** — the structure of `attic/manual-differential-geometry.md` §"Lie derivative" - (lines 84–192) and §"Lie bracket" (325–377) survives; **every code block is dead** and must - be rewritten. The `⋅` sections and the summary table (622–628) are deleted outright. -- **API traps** — this page *is* the trap. Grep the finished page for `Lie(`, ` ⋅ `, - `autonomous=` and `HamiltonianLift` before merging. - -### `geometry/poisson.md` - -- **Purpose** — the Poisson bracket of two Hamiltonians. -- **Outline** - - `## Definition` — $\{H, G\} = \nabla_p H \cdot \nabla_x G - \nabla_x H \cdot \nabla_p G$ - - `## On plain functions` — `Poisson(H, G)` - - `## On typed Hamiltonians` — returns a `Hamiltonian`, so it **nests** - - `## The bridge to Lie brackets` — `Poisson(Lift(X), Lift(Y)) ≈ Lift(ad(X, Y))`, executed - - `## Non-autonomous and variable forms` - - `## Application: singular controls` — the standard chain $H_{01}$, $H_{001}$, $H_{101}$, - and $u_{\text{sing}} = -H_{001}/H_{101}$; forward-link - `@ref examples-singular-control` -- **API covered** — `Poisson`, `Hamiltonian`, `Lift`, `ad`. -- **Source** — `attic/manual-differential-geometry.md` §"Poisson bracket"; the code survives - because `Poisson` did not change. -- **API traps** — passing an `AbstractVectorField` gives `IncorrectArgument` pointing at `ad`. - Show it. - -### `geometry/lie-macro.md` - -- **Purpose** — `@Lie`, the bracket notation that makes the singular-control computation - readable. -- **Outline** - - `## Why a macro` — `@Lie [X, Y]` and `@Lie {H, K}` read like the mathematics - - `## Lie brackets` — `[a, b]` expands to `ad` - - `## Poisson brackets` — `{c, d}` expands to `Poisson` - - `## Nesting` — `@Lie [[X, Y], Y]`, `@Lie {{H, K}, L}` - - `## Arithmetic and evaluation points` — `@Lie [F0, F1](x) + 4 * [F1, F2](x)` - - `## Keywords` — `is_autonomous=`, `is_variable=`, `ad_backend=`; **parenthesise** when you - evaluate, because trailing keywords bind to `@Lie`: - `(@Lie [F, G](x))`, not `@Lie [F, G](x) atol=1e-6` - - `## What it needs in scope` — the expansion emits fully qualified `CTLie._lie_mac` / - `CTLie._poisson_mac` and `CTBase.Traits.*`, so **both `CTLie` and `CTBase` must be - resolvable at the call site**. `using OptimalControl` re-exports both module names, so - this is automatic — but it explains why they are re-exported at all -- **API covered** — `@Lie`, `CTLie`, `CTBase`. -- **Source** — `attic/manual-differential-geometry.md` §"The `@Lie` macro`". -- **API traps** - - `@Lie [X, Y] autonomous=false` → `IncorrectArgument` at expansion time, naming the three - accepted keywords. Show it; it is a well-designed error. - - **Do not break the VitePress escaping.** The `{{`/`}}` plugin - (`docs/src/.vitepress/config.mts:61-71`) exists so `` `@Lie {{H, K}, L}` `` in inline code - is not parsed as a Vue template expression. Nested Poisson brackets are this page's bread - and butter — verify the rendered output, not just the build exit code. - -### `geometry/ad-backend.md` — new - -- **Purpose** — choose how the derivatives are computed. Currently undocumented. -- **Outline** - - `## Everything here is AD-backed` — except `Lift` - - `## The default` — `DifferentiationInterface` over ForwardDiff - - `## Reading the current backend` — `dg_ad_backend()` - - `## Changing it globally` — `dg_ad_backend!(...)` - - `## Changing it for one call` — the `ad_backend=` keyword on `ad`, `Poisson`, `∂ₜ`, `@Lie` - - `## GPU` — `Differentiation.DifferentiationInterface{CTBase.Strategies.GPU}()` - - `## Introspection` — `describe(:di)` - - `## If nothing works` — the symptom when `DifferentiationInterface` is not loaded -- **API covered** — `dg_ad_backend`, `dg_ad_backend!`, `ad_backend=`, `describe(:di)`, - `CPU`, `GPU`. -- **API traps** - - The backend is a **`CTBase.Differentiation` object**, not a raw `ADTypes` backend. - `dg_ad_backend!(AutoForwardDiff())` is the v2.0 spelling and is wrong; the current one is - `dg_ad_backend!(CTBase.Differentiation.DifferentiationInterface())`. `ADTypes` is no - longer even a CTLie dependency. - - `CTBase.Differentiation` is reachable (the `CTBase` module name is re-exported) but - `Differentiation` itself is not bound — write the full path. - ---- - -## Outgoing links - -| From | To | -| --- | --- | -| `overview.md` | every page in the section, `@ref flows-overview`, `@ref migration` | -| `lift.md` | `@ref flows-from-hamiltonians`, `@ref geometry-poisson` | -| `ad.md` | `@ref geometry-poisson`, `@ref geometry-lie-macro`, `@ref migration` | -| `poisson.md` | `@ref geometry-lie-macro`, `@ref examples-singular-control` | -| `lie-macro.md` | `@ref geometry-ad`, `@ref geometry-poisson`, `@ref examples-singular-control` | -| `ad-backend.md` | `@ref solve-gpu`, `@ref flows-overview` | - -## Acceptance criteria - -- [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/07-results.md b/docs/reports/07-results.md deleted file mode 100644 index a3db7e529..000000000 --- a/docs/reports/07-results.md +++ /dev/null @@ -1,165 +0,0 @@ -# Results — specification - -**PR**: 7 · **Depends on**: PR 6 · **Status**: specification -**Scope**: `docs/src/results/*` - -## Objective - -Everything you do *after* something has been computed: read it, draw it, store it. - -**This section is deliberately placed before Flows.** A trajectory returned by a flow is -inspected and plotted with exactly the same functions as a solution returned by `solve` — -`state`, `control`, `costate`, `objective`, `time_grid`, `plot`. Flows can then say "same as -Results" instead of repeating it, which is precisely the point the user made: *"il peut les -afficher, introspecter la solution, comme quand c'est une solution d'un problème de contrôle -optimal."* - -## Pages - -| id | title | path | source | -| --- | --- | --- | --- | -| `results-solution` | The solution object | `results/solution.md` | `attic/manual-solution.md` | -| `results-plot` | Plot a solution | `results/plot.md` | `attic/manual-plot.md` | -| `results-save-load` | Save and load | `results/save-load.md` | **new** — currently undocumented | - ---- - -## Page details - -### `results/solution.md` - -- **Purpose** — read a computed result: trajectories, costate, duals, metadata. -- **Outline** - - `## What you get back` — `solve` returns a `Solution`; a flow returns a trajectory; the - accessors are the same generics - - `## Trajectories` — `state`, `control`, `variable`, `costate`; **they are functions of - time**, and `time_grid` gives the nodes - - `## The objective` - - `## Did it work` — `successful`, `status`, `message`, `iterations`, - `constraints_violation`, `infos` - - `## Dual variables` — `dual`, and the eight constraint-specific accessors - - `## Back to the model` — `model(sol)` - - `## Empty solutions` — `is_empty`, `is_empty_time_grid` -- **API covered** — `state`, `control`, `variable`, `costate`, `objective`, `time_grid`, - `times`, `iterations`, `status`, `message`, `successful`, `constraints_violation`, `infos`, - `model`, `is_empty`, `is_empty_time_grid`, `dual`, - `path_constraints_dual`, `boundary_constraints_dual`, - `state_constraints_lb_dual`, `state_constraints_ub_dual`, - `control_constraints_lb_dual`, `control_constraints_ub_dual`, - `variable_constraints_lb_dual`, `variable_constraints_ub_dual`, - `dim_dual_state_constraints_box`, `dim_dual_control_constraints_box`, - `dim_dual_variable_constraints_box`. -- **Source** — `attic/manual-solution.md` (437 lines). Drop the "solution struct" internals - section; that is API-reference material. -- **API traps** - - `success(sol)` never existed as a CTModels method and `success` is `Base.success`. The - accessor is `successful(sol)`. PR 3 makes the old spelling throw with that message — - cross-link `@ref migration`. - - **1-D is a scalar**: for a scalar control, `control(sol)(t)` is a `Number`, not a - length-1 vector. Show it explicitly; this is the most common shape surprise. - - `Solution` and `AbstractSolution` are imported, not exported — do not write bare - `Solution` in a type annotation in an example. -- **Symmetry** — this page mirrors `@ref modelling-inspect`. Say so and link both ways. - -### `results/plot.md` - -- **Purpose** — draw a solution or a trajectory, and control the layout. -- **Outline** - - `## Getting started` — `using Plots`, then `plot(sol)` - - `## What gets drawn by default` - - `## Choosing what to draw` — positional `:state`, `:control`, `:costate` - - `## Layout` — `layout=:split` vs `layout=:group` - - `## The control` — `control=:components` / `:norm` / `:all` - - `## Styling` — `state_style`, `costate_style`, `control_style`, `time_style` - (NamedTuple, or `:none` to hide) - - `## Normalised time` - - `## Constraints` - - `## Adding to an existing plot` — `plot!` - - `## Custom subplots` - - `## Plotting a flow trajectory` — the same call works; forward-link - `@ref flows-simulation` -- **API covered** — `plot`, `plot!`, and every keyword above. -- **Source** — `attic/manual-plot.md` (454 lines) is thorough and current. Audit, do not - rewrite. Its §"plotting from `Flow`" (`@id manual-plot-flow`) becomes the last section here - and must gain `using OrdinaryDiffEqTsit5` in the preamble. -- **API traps** - - `plot` on a solution without `using Plots` throws `ExtensionError(:Plots)` - (`CTModels.jl/src/Display/Display.jl:69`). Show the error once; it is the most common - "why is nothing happening". - - The flow section's examples must be re-checked against the current `Flow` API - ([`05-flows-indirect.md`](05-flows-indirect.md) §"Constructor catalogue"). - -### `results/save-load.md` — **new page** - -- **Purpose** — persist a solution and read it back. Currently documented nowhere on the site - even though the API is exported. -- **Outline** - - `## Why` — expensive solves, warm starts, sharing results - - `## JLD2` — `using JLD2`; `export_ocp_solution(sol; format=:JLD, filename="solution")`, - `import_ocp_solution(ocp; format=:JLD, filename="solution")` - - `## JSON3` — `using JSON3`; same calls with `format=:JSON` - - `## Which format` — JLD2 round-trips Julia values exactly; JSON3 is portable and readable - - `## Reloading as an initial guess` — feed the imported solution straight to `solve` as a - warm start; link `@ref solve-initial-guess` - - `## Without the extension` — the `ExtensionError` you get -- **API covered** — `export_ocp_solution`, `import_ocp_solution`. -- **Source** — new. The backends are weak-dependency extensions `CTModelsJLD` / `CTModelsJSON`. -- **The user-facing signatures** (verified in the resolved CTModels 0.15.3-beta, - `src/Serialization/export_import.jl:49,91`): - - ```julia - export_ocp_solution(sol; format::Symbol=:JLD, filename::String="solution") - import_ocp_solution(ocp; format::Symbol=:JLD, filename::String="solution") - ``` - - `format` is a **`Symbol`**, `:JLD` or `:JSON`; anything else is an `IncorrectArgument` - that names both valid values. `filename` is a **base name — the extension is added - automatically**, which is why the same name works for both formats. - -- **API traps** - - The tag types `JLD2Tag`, `JSON3Tag`, `AbstractTag` - (`CTModels.jl/src/Serialization/types.jl`) are an **internal dispatch mechanism**. They - are not re-exported and the user never touches them — the `format` symbol is the API. - Do not mention them on the page. - - `import_ocp_solution` takes the **model**, not a filename, as its positional argument: - the model is what the solution is reconstructed against. - - `filename` has no extension. Writing `filename="sol.jld2"` produces `sol.jld2.jld2`. - ---- - -## Outgoing links - -| From | To | -| --- | --- | -| `solution.md` | `@ref modelling-inspect`, `@ref results-plot`, `@ref results-save-load`, `@ref migration` | -| `plot.md` | `@ref results-solution`, `@ref flows-simulation` | -| `save-load.md` | `@ref solve-initial-guess`, `@ref results-solution` | - -## Acceptance criteria - -- [x] Every accessor in the `solution.md` list appears and executes — including - `is_empty_time_grid(sol)` in place of the spec's own `is_empty(sol)`, which does not - exist (`is_empty` only accepts a `TimeGridModel`, confirmed live via `MethodError`); the - page uses the real accessor instead of the spec's literal wording. -- [x] The scalar-control shape is demonstrated explicitly (`typeof(u(0.25))`), not merely - asserted. -- [x] `save-load.md` exists, runs a real round-trip in both formats (JLD2 exact, JSON3 - bit-exact in the run but phrased as "not guaranteed" in general), and states how the - format is chosen. -- [x] `plot.md`'s flow section has `using OrdinaryDiffEqTsit5` and current `Flow` calls — - including the fine-grid subsection, rewritten around `Flow(...; saveat=..., dense=false)` - at construction time since the old call-time `saveat=` no longer exists. -- [x] The `success` → `successful` note is present, shows the real thrown error live, and links - to the migration page. -- [x] Nothing in the section writes bare `Solution` as a type name. - -**Also found and fixed, beyond the checklist above:** -- CTModels.jl#392 (the VBox-plotting bug already filed during PR 5, and hit once more during - PR 6) turned out to affect nearly every bare `plot(sol)` call in `plot.md`'s attic source - specifically — any call with an empty/default description, independent of styles or layout - kwargs, under this docs environment's pinned CTModels (`0.15.3-beta`). Fixed throughout with - explicit selectors or `layout=:group`, with one clear note near the top of the page instead - of repeating the caveat at every occurrence. -- The spec's cited line for the `plot` `ExtensionError` (`CTModels.jl/src/Display/Display.jl:69`) - was off by 3 — the actual `throw` is at line 72; not worth a citation in the page itself, but - noted here for anyone cross-checking against source later. diff --git a/docs/reports/08-examples.md b/docs/reports/08-examples.md deleted file mode 100644 index 1a9388c1d..000000000 --- a/docs/reports/08-examples.md +++ /dev/null @@ -1,284 +0,0 @@ -# Examples — specification - -**PR**: 10 · **Depends on**: PRs 8, 9 · **Status**: specification -**Scope**: `docs/src/examples/*` - -## Objective - -Complete, self-contained problems worked end to end. A guide answers *"how do I do X"*; an -example answers *"what does a real problem look like"*. Each one is a story with a result, -not a feature demonstration. - -Also the section's second job: **be the entry point for the Geometric profile**, who arrives -looking for a singular arc or a state constraint, not for a tutorial. - -## Pages - -Six existing, plus one new. Ordered by difficulty — the sidebar order is the reading order. - -| id | title | path | source | -| --- | --- | --- | --- | -| `examples-double-integrator-energy` | Energy minimisation | `examples/double-integrator-energy.md` | `attic/example-double-integrator-energy.md` | -| `examples-double-integrator-time` | Time minimisation (bang–bang) | `examples/double-integrator-time.md` | `attic/example-double-integrator-time.md` | -| `examples-control-free` | Parameter estimation without a control | `examples/control-free.md` | `attic/example-control-free.md` | -| `examples-control-and-variable` | Control and variable together | `examples/control-and-variable.md` | `attic/example-control-and-variable.md` | -| `examples-singular-control` | Singular control | `examples/singular-control.md` | `attic/example-singular-control.md` | -| `examples-state-constraint` | State constraint | `examples/state-constraint.md` | `attic/example-state-constraint.md` | -| `examples-simulation` | Simulating a controlled system | `examples/simulation.md` | **new** (optional — see §"Scope decision") | - -### Scope decision - -`examples-simulation` is the only genuinely new example, and it exists because the Simulator -profile has no story of its own: every current example is an optimisation. **Recommendation**: -write it if `flows/simulation.md` turns out to need more than a snippet to be convincing; -otherwise drop it and let the guide carry the load. Decide when PR 8 is done, not before. - ---- - -## Common structure - -Every example page follows the same skeleton. Consistency is what makes a gallery usable. - -```markdown -# [Title](@id examples-) - -## The problem -Physical setting, then the mathematics. - -## Definition -`@def`, executed. - -## Direct solution -`solve`, `plot`. - -## Indirect solution ← where applicable -PMP by hand, the flow, the shooting function, the check against the direct solution. - -## Comparison ← where both exist -Same picture, both methods. - -## See also -Guides this example exercises. -``` - -The "See also" is not decoration: it is how a reader who arrived from a search engine finds -the guide that explains what they just read. - ---- - -## Page details - -### `examples/double-integrator-energy.md` - -- **Story** — the canonical first problem: $\ddot q = u$, minimise $\int \tfrac12 u^2$. -- **Covers** — `@def`, `solve`, `plot`, `time_grid`, `costate`, `Flow`. -- **Source** — `attic/example-double-integrator-energy.md` (169 lines). -- **API traps** — the indirect section builds a Hamiltonian flow; audit the preamble - (`using OrdinaryDiffEqTsit5`) and every flow call. -- **Note** — this problem also appears in `index.md` and - `getting-started/first-problem.md`. That repetition is deliberate; keep the three - definitions **character-identical** so the reader recognises it. - -### `examples/double-integrator-time.md` - -- **Story** — time-optimal double integrator; bang–bang control with one switching. -- **Covers** — `@def`, `solve`, `plot`, `plot!`, `time_grid`, `costate`, `variable`, `Flow`, - flow concatenation `*`, in-place shooting, switching-time detection, NLE resolution. -- **Source** — `attic/example-double-integrator-time.md` (204 lines). -- **Role** — this is the **reference example for `flows/multi-phase.md` and - `flows/shooting.md`**. Those two guides harvest from it; keep it the canonical version and - have the guides link here rather than duplicate. -- **API traps** — free final time means a `NonFixed` problem: `variable=` is mandatory on - every flow call, and the transversality condition may want `variable_costate=true`. Audit - every call. - -### `examples/control-free.md` - -- **Story** — two parameter-estimation problems with no control: exponential growth rate - ($p = 0.5$) and harmonic-oscillator pulsation ($\omega = \pi/2$). Solved directly, then - indirectly with a Hamiltonian flow and the augmented costate. -- **Covers** — `@def` without a control, `solve`, `plot`, `plot!`, `time_grid`, `costate`, - `variable`, `model`, `objective`, `Flow(ocp)`. -- **Source** — `attic/example-control-free.md` (378 lines). -- **Relation to the guide** — `modelling/without-control.md` (PR 5) is the *guide*; this is - the *example*. The guide harvests the intro and both fixtures; this page keeps the full - worked story including the indirect part. Cross-link both ways. -- **API traps** — the augmented-costate section used `augment=true`; it is now - `variable_costate=true`. `Flow(ocp)` with no law is correct **here and only here** (the - control-free case). - -### `examples/control-and-variable.md` - -- **Story** — the same two systems, now with a control input and a quadratic control cost: - simultaneous parameter *and* control estimation. -- **Covers** — same list as `control-free.md`. -- **Source** — `attic/example-control-and-variable.md` (381 lines). -- **Note** — this pair (`control-free` → `control-and-variable`) is the site's clearest - illustration of what adding a control does to a problem. Keep them adjacent in the sidebar - and cross-link them explicitly. - -### `examples/singular-control.md` — the Geometry payoff - -- **Story** — a problem whose optimal control has a singular arc; the singular control is - computed by hand, then again with Poisson brackets, then the whole thing is solved by - shooting. -- **Covers** — `@def`, `solve`, `plot`, `plot!`, `state`, `costate`, `variable`, `time_grid`, - `Flow`, **`Lift`**, **`@Lie`** in its `{}` Poisson form. -- **The computation** — this is the passage the whole Geometry section exists to support: - - ```julia - H0 = Lift(F0) - H1 = Lift(F1) - H01 = @Lie {H0, H1} - H001 = @Lie {H0, H01} - H101 = @Lie {H1, H01} - us(x, p) = -H001(x, p) / H101(x, p) - ``` - -- **Source** — `attic/example-singular-control.md` (351 lines). -- **API traps** — good news: `Lift` and the `@Lie {}` form both survive unchanged, so the - geometry code is **already correct**. What needs auditing is the flow half (preamble, - positional variable, `augment=`) and any `OptimalControl.` qualification on now-exported - types. -- **Role** — `geometry/poisson.md` and `geometry/lie-macro.md` both forward-link here as the - worked application. Do not duplicate the derivation in the guides. - -### `examples/state-constraint.md` - -- **Story** — first-order and second-order state constraints: boundary arcs, the multiplier - $\mu$, costate jumps, multi-arc concatenation. -- **Covers** — `@def`, `solve`, `plot`, `plot!`, `state`, `costate`, `time_grid`, `Flow` - (five of them, including the constrained-arc form), flow concatenation with jumps. -- **Source** — `attic/example-state-constraint.md` (518 lines — the longest page on the site). -- **API traps** — the biggest concentration on the site: - - `Flow(ocp, u, c, μ)` → `Flow(ocp, u; constraint=c, multiplier=μ)`. - - Consider showing the `Symbol` spelling (`constraint=:label`) at least once — the problem - already declares its constraints in the `@def`, so this is exactly the case the feature - was built for. - - The concatenation syntax `f1 * (t1, f2) * (t2, f3)` and the jump forms need re-checking - against the current `MultiPhase` API. -- **Role** — reference example for `flows/constrained-arcs.md`. - -### `examples/simulation.md` — new, conditional - -- **Story** — a controlled system, an open-loop control, a trajectory; then the same system - under feedback. No optimisation anywhere. -- **Covers** — `Flow(ControlledVectorField(f), OpenLoop(u))`, - `Flow(ControlledVectorField(f), ClosedLoop(k))`, `state`, `control`, `objective`, - `time_grid`, `plot`. -- **Why it might be worth it** — it is the only page on a hypothetical site that would tell - the Simulator profile "yes, this package is for you too". Everything else here is an - optimisation. -- **Decide after PR 8.** - ---- - -## Cross-cutting work for this PR - -1. **Preamble audit** — every example gains `using OrdinaryDiffEqTsit5` before its first - `Flow`. Six pages, six preambles. -2. **Flow-call audit** — grep all six for a positional variable (`, tf, ` followed by a fifth - argument), `augment=`, `Flow(ocp, u, `, `Flow(` on a bare function, and - `OptimalControl.Hamiltonian`. -3. **Shape audit** — 1-D states and controls must be scalars. -4. **Anchor rename** — all six ids change (`example-*` → `examples-*` and the path moves - under `examples/`). `index.md` links to the first one; the guides will link to several. -5. **Sidebar order** — energy → time → control-free → control-and-variable → singular → - state-constraint. Increasing difficulty, and each one uses something the previous - introduced. - -## Outgoing links - -| From | To | -| --- | --- | -| every example | the guides it exercises | -| `double-integrator-time.md` | `@ref flows-multi-phase`, `@ref flows-shooting` | -| `control-free.md` | `@ref modelling-without-control`, `@ref examples-control-and-variable` | -| `control-and-variable.md` | `@ref examples-control-free` | -| `singular-control.md` | `@ref geometry-poisson`, `@ref geometry-lie-macro`, `@ref geometry-lift`, `@ref flows-shooting` | -| `state-constraint.md` | `@ref flows-constrained-arcs`, `@ref flows-multi-phase` | - -## Acceptance criteria - -- [x] All six pages execute with `Draft = false`. Confirmed via a full - `julia --project=docs docs/make.jl` build (0 execution failures) plus a standalone - block-by-block re-run of each page's extracted `@example` code. -- [x] Every page follows the common skeleton and ends with a "See also". -- [x] No positional variable, no `augment=`, no `Flow(f::Function)`, no - `Flow(ocp, u, g, μ)`, no `OptimalControl.`-qualified exported type. Grepped precisely - (not just for the substrings) — zero hits; see "Also found and fixed" for the three - positional-`variable` bugs this caught that the spec's own file notes didn't flag. -- [x] Every page that calls `Flow` loads `OrdinaryDiffEqTsit5`. All six do. -- [x] The double-integrator definition is character-identical across `index.md` and - `examples/double-integrator-energy.md` — confirmed, reused verbatim - (`t0=0,tf=1,x0=[-1,0],xf=[0,0]`, dynamics `[v(t),u(t)]`, cost `0.5∫(u(t)^2)`). - `getting-started/first-problem.md` is still a PR-11 stub (not yet written) — flagged for - PR 11 to reuse this exact block when it lands; nothing further to do here. -- [x] `singular-control.md` reproduces the same numerical result as before the rewrite: the - free final time from the indirect (shooting) solve, `tf_sol ≈ 1.149730885756096`, matches - the direct solve's own `tf ≈ 1.149732813941868` to 6 significant figures, and the - `Lift`/`@Lie` bracket formula `us_bracket(q,p)` agrees with the hand-derived - `u_indirect(x)=sin(x[3])^2` along the extremal to ~1e-9 — both confirm the untouched - geometry code and the fixed flow half agree. -- [x] The `examples-simulation` decision is recorded: **dropped**. `flows/simulation.md` (PR 8) - already carries 10 executed `@example` blocks — open loop, closed loop, an - OCP-with-`OpenLoop` trajectory carrying a real objective, plotting, and every relevant - error path — and reads as a complete story without a dedicated example page. - -## Also found and fixed - -- **Three positional-`variable` bugs, not flagged by the spec's own file-by-file notes**: - `double-integrator-time.md`, `control-free.md`/`control-and-variable.md`, and - `singular-control.md` all called a `NonFixed` flow with the variable value as a bare - positional argument (or omitted it) instead of the mandatory `variable=` keyword — inherited - unchanged from the attic. Confirmed against `test/problems/double_integrator.jl`'s - `_di_time_shoot_builder`, which threads `variable=τf` correctly; fixed all three pages to - match, re-verified live (residuals ~1e-13 to ~1e-15 throughout). -- **A real bug caught only by live execution**: `state-constraint.md`'s boundary-arc case built - `fs_arc` and `fc_bd` from two *separate* calls to `make_ocp(a_arc)` — each call constructs a - fresh OCP instance, and multi-phase concatenation requires every phase to share the exact same - one. Threw `IncorrectArgument` ("cannot reconstruct a multi-phase solution from flows of - different OCPs") at the final `f_arc = fs_arc * (...) * fc_bd * (...)` step. Fixed by building - `ocp_arc = make_ocp(a_arc)` once and reusing it for `solve`, `fs_arc`, and `fc_bd`. -- **A broken `[Home](@ref)` link**: `index.md`'s top heading has no `@id`, so there is no `Home` - anchor to reference — caught by the full `make.jl` build (`Cannot resolve @ref`), fixed by - rewording to plain prose. -- **Two `plot!` calls with the wrong first argument**: wrote `plot!(sol1, sol2, ...)` (two - solutions) instead of `plot!(plt, sol2, ...)` (a stored plot handle, then the solution to - overlay) in `state-constraint.md`'s two comparison plots — a `RecipesPipeline` error, fixed by - storing the first `plot(...)` call's return value. -- **The recurring CTModels.jl#392 VBox bug** hits every bare `plot(sol)` call under this - environment's pinned CTModels, same as every prior PR in this series — every plot on this page - uses the `plot(sol, :state, :control)` selector workaround. -- **`v(t) ≤ v_max` first-order constraint reuses `flows/constrained-arcs.md`'s exact fixture** - (`VMAX=1.2`, `t0=0.0,tf=1.0,x0=[-1.0,0.0]`, labeled `(vmax)`) rather than re-deriving new - numbers, and demonstrates the `constraint=:vmax` Symbol form per the spec's own suggestion — - the multiplier signature is `μ(x, p) = p[1]` (2-arg), not the attic's 1-arg `μ(p) = p[1]`. -- **The `model` naming collision**: `control-free.md`/`control-and-variable.md`'s local - synthetic-data function was renamed from `model(t)` to `x_true(t)` to stop shadowing the real, - exported `model(sol)` accessor; added one real `model(direct_sol) === ocp_growth` call so the - accessor the spec's own "Covers" list asks for is actually demonstrated, not just avoided. -- **`control-and-variable.md`'s harmonic oscillator no longer converges to `ω=π/2`** — confirmed - correct, not a bug: with a control now sharing the work of steering `q` to `0`, the - cost-minimising `|ω|` genuinely changes (confirmed direct and indirect solves agree with each - other, `ω≈-0.654` both ways); `ω=π/2` was only ever forced by the control-free version's rigid - boundary conditions. Added a note in the page so this isn't read as a discrepancy. -- **`docs/src/examples/gallery.md` isn't in the spec's own page table** — kept as the section's - landing/index page (the "Examples" analogue of every other section's "Overview"), since PR 2 - already built it as a stub with a live anchor (`examples-gallery`) that 8 already-written - guide pages forward-link to. `docs/make.jl`'s "Examples" nav entry converted from a bare - string to a nested vector (`"Gallery"` first, then the six story pages), matching every other - section's shape. -- **Sharpened 4 pre-existing forward links** that only ever pointed at the generic - `examples-gallery` stub for lack of anything better, now that the specific anchors exist: - `flows/multi-phase.md` and `flows/shooting.md` gained a real "See also" link to - `examples-double-integrator-time` (PR 8 predates this section, had none before); - `geometry/poisson.md` and `geometry/lie-macro.md` retargeted to `examples-singular-control` - (both already flagged as a TODO in PR 9's own report); `modelling/without-control.md` - retargeted to `examples-control-free`; `docs/src-literate/tutorial.jl`'s three `#md`-only - mentions retargeted to the three specific example anchors (its `#nb` notebook-export variants - left untouched — those are literal URLs into the stable, deployed old site, out of scope - here). `modelling/functional-api.md`'s "worked both ways" mention was deliberately **not** - retargeted — nothing in `examples/` actually shows the same problem worked in both `@def` and - functional-API form side by side, so forcing a specific anchor there would misrepresent the - content; left pointing at the generic gallery. diff --git a/docs/reports/09-api-reference.md b/docs/reports/09-api-reference.md deleted file mode 100644 index 2a8388038..000000000 --- a/docs/reports/09-api-reference.md +++ /dev/null @@ -1,223 +0,0 @@ -# API reference — specification - -**PR**: 4 · **Depends on**: PR 2 · **Status**: specification -**Scope**: `docs/api_reference.jl`, `docs/src/api/*`, `docs/make.jl` (the `pages=` API node) - -## Objective - -Replace a hand-maintained, alphabetical, partly-dead list of ~125 symbols with **thematic -pages generated from a Julia manifest**, so the reference cannot drift from the code again. - -The user's framing: *"l'api par méthode"* — grouped so you can find a function by what it -does, not by its initial. - -## The problem with what exists - -`docs/src/api/public.md` is 160 lines: an `@autodocs` block for the module docstring, then one -`@docs; canonical=true` block listing ~125 symbols alphabetically, by hand. It has drifted: - -| Entry | Status | -| --- | --- | -| `*(::CTFlowsODE.AbstractFlow)` | `CTFlowsODE` **no longer exists** | -| `Lie`, `⋅` | removed from the ecosystem | -| `success` | never had a method; `success` is `Base.success` | -| `time` | no longer re-exported | -| `CTModels.OCP.constraint` / `.objective` / `.variable` | submodule reorganised into `Components` / `Models` / `Solutions` | -| `CTDirect.discretize`, `CTSolvers.Optimization.*` in the `solve` signatures | paths changed | - -And it is missing everything new: `ad`, `dg_ad_backend`, `dg_ad_backend!`, the whole -`MultiPhase` group (`n_phases`, `get_flow(s)`, `get_jump(s)`, `get_switching_time(s)`), -`CPU`/`GPU`, `force`, `evaluate_at`, `final_state`, `SciML`, `control_law`, -`pseudo_hamiltonian`, and the entire `CTBase.Data` type vocabulary. - -For scale: `names(OptimalControl)` returns **193 symbols** today (measured, see -[`99-api-coverage.md`](99-api-coverage.md)). The hand-written page lists ~125, several of -which no longer exist. - -The Handbook is explicit: *"never hand-write API pages"* -(`Handbook/philosophy/documentation.md` §Principles). This PR brings OptimalControl back in -line — with one adaptation, because OptimalControl's public surface is almost entirely -**re-exported**, and `automatic_reference_documentation` selects symbols by *defining source -file*. A file-based scan of `src/` would find only `solve`, `methods` and the private helpers. - -## The design: a thematic manifest - -Keep `with_api_reference` / `_cleanup_pages` (they work). Add a manifest to -`docs/api_reference.jl` mapping a theme to a symbol list, and generate one `.md` per theme. - -```julia -# docs/api_reference.jl - -const API_THEMES = [ - (id="modelling", title="Modelling", symbols=[ - Symbol("@def"), Symbol("@init"), - :time!, :state!, :control!, :variable!, :dynamics!, - :objective!, :constraint!, :time_dependence!, :build, - :build_initial_guess, - ]), - (id="problem", title="Problem", symbols=[...]), - (id="solving", title="Solving", symbols=[...]), - (id="options", title="Options and strategies", symbols=[...]), - (id="solution", title="Solution", symbols=[...]), - (id="flows", title="Flows", symbols=[...]), - (id="geometry", title="Geometry", symbols=[...]), - (id="types", title="Types", symbols=[...]), - (id="io", title="Plotting and I/O", symbols=[...]), -] -``` - -Each theme becomes `docs/src/api/.md`, written at build time and removed by -`_cleanup_pages` afterwards — same lifecycle as the existing `api/private.md`. The page body -is a `@docs; canonical=true` block over the theme's symbols, plus a one-paragraph lead. - -Themes mirror the sitemap ([`00-cahier-des-charges.md`](00-cahier-des-charges.md) §7) so a -reader moving from a guide to the reference lands somewhere recognisable. - -### The completeness check — the part that stops the drift - -Generate, in the same script, the set of symbols OptimalControl actually exports: - -```julia -exported = setdiff(names(OptimalControl), (:OptimalControl,)) -covered = union(Set.(t.symbols for t in API_THEMES)...) - -missing_syms = setdiff(exported, covered) # exported but in no theme -stale_syms = setdiff(covered, exported) # in a theme but not exported -``` - -Emit an `@warn` (or fail the build under a flag) when either set is non-empty. **This is the -whole point of the rework**: `Lie` and `⋅` sat in `public.md` for a full release cycle because -nothing checked. Wire the same two sets into -[`99-api-coverage.md`](99-api-coverage.md). - -### Imported-but-not-exported symbols - -Some names are legitimately reachable only as `OptimalControl.X` — `PreModel`, `Model`, -`Solution`, `AbstractSolution`, `Collocation`, `ADNLP`, `Exa`, `Ipopt`, `MadNLP`, `MadNCL`, -`Knitro`, `Uno`, `LiftedHamiltonianFunction`, the `CTException` family. They belong in the -reference (guides use them) but are **not** in `names(OptimalControl)`. - -Give them their own theme, `api/qualified.md` — "Reachable as `OptimalControl.X`" — and -exclude it from the `exported` check. A user who has to type the prefix deserves to be told -which names those are, in one place. - -## Pages produced - -``` -API reference - Modelling api/modelling.md generated - Problem api/problem.md generated - Solving api/solving.md generated - Options and strategies api/options.md generated - Solution api/solution.md generated - Flows api/flows.md generated - Geometry api/geometry.md generated - Types api/types.md generated - Plotting and I/O api/io.md generated - Qualified access api/qualified.md generated - Internals api/internals.md generated (was "Private") - Ecosystem api/ecosystem.md hand-written, 1 page -``` - -Two renames worth doing while the file is open: - -- **"Private" → "Internals"**, matching the Handbook's wording - (`title="Internals"`, `filename="internals"`). -- **"Subpackages" → "Ecosystem"**. The current `api/subpackages.md` is ten lines of `@extref` - links to six sibling packages and it is the main place the CTX split leaks into the - user-facing site. Rewrite it as a short *"this package is assembled from these; you do not - need to know that, but here is the map"* page — and **add CTLie**, which is missing today. - -## Themes: the symbol assignment - -Full lists live in [`99-api-coverage.md`](99-api-coverage.md); this is the partition rule. - -| Theme | Contains | -| --- | --- | -| Modelling | `@def`, `@init`, the `PreModel` builders, `build`, `build_initial_guess` | -| Problem | every model getter and trait: dimensions, names, components, times, dynamics, cost, constraints, `definition`, `is_autonomous`, `has_control`, … | -| Solving | `solve`, `methods`, `discretize`, `ocp_model`, `nlp_model`, `ocp_solution`, `get_build_examodel` | -| Options and strategies | `describe`, `id`, `metadata`, `options`, `option_*`, `has_option`, `route_to`, `bypass`, `force`, `is_user`/`is_default`/`is_computed`, `create_registry`, `strategy_ids`, `type_from_id`, `parameter`, `default_parameter`, `available_parameters`, `CPU`, `GPU` | -| Solution | `state`, `control`, `costate`, `variable`, `objective`, `time_grid`, `times`, `status`, `message`, `successful`, `iterations`, `constraints_violation`, `infos`, `model`, `is_empty`, `dual` + the constraint duals | -| Flows | `Flow`, `control_law`, `pseudo_hamiltonian`, the `MultiPhase` group, `SciML`, `AbstractIntegrator`, `AbstractIntegrationResult`, `final_state`, `evaluate_at` (+ the accessor re-exports decided in PR 8) | -| Geometry | `ad`, `Lift`, `Poisson`, `∂ₜ`, `@Lie`, `dg_ad_backend`, `dg_ad_backend!` | -| Types | the `CTBase.Data` vocabulary: `VectorField`, `Hamiltonian`, `HamiltonianVectorField`, `PseudoHamiltonian`, `PseudoHamiltonianVectorField`, `ControlledVectorField`, `ComposedHamiltonian`, `ComposedVectorField`, `ControlLaw`, `OpenLoop`, `ClosedLoop`, `DynClosedLoop`, `PathConstraint`, `StateConstraint`, `ControlConstraint`, `MixedConstraint`, `Multiplier`, and their `Abstract*` supertypes | -| Plotting and I/O | `plot`, `plot!`, `export_ocp_solution`, `import_ocp_solution` | -| Qualified access | `PreModel`, `Model`, `Solution`, the strategy component types, `LiftedHamiltonianFunction`, the `CTException` family | - -Judgement calls, recorded so they are not re-argued: - -- **`variable` is in Solution, not Problem.** It is one generic over both; put it where it is - most used and cross-reference. -- **`describe` is in Options**, not Solving — it introspects strategies, including the - indirect ones (`:di`, `:sciml`). -- **`CPU`/`GPU` are in Options**, not Solving — they are strategy parameters. -- **The `CTException` family goes in Qualified access.** It is not exported, and a reader - looking for it is debugging. - -## Also in this PR - -- **Delete `docs/src/api/public.md`** and `docs/src/api/subpackages.md` (the latter is - replaced by `api/ecosystem.md`). -- **Keep the `!!! tip` about re-exports** in spirit but rewrite it: the current text - (`api/public.md:9-21`) explains that `CTFlows.Lift` works from OptimalControl — a - double error (it is CTLie's now, and the prefix is no longer shown anywhere). The - replacement belongs on `api/ecosystem.md`, phrased as *"everything documented here is - reachable from `using OptimalControl`"*. -- **Verify `src/helpers/describe.jl` is in the Internals file list** (added in PR 2). -- **Add `docs/api_reference.jl`'s new manifest to the review checklist** for every future - source PR that adds an export. - -## Acceptance criteria - -- [x] `docs/src/api/public.md` and `api/subpackages.md` are deleted. -- [x] Every theme page is generated at build time and removed by `_cleanup_pages`. -- [x] The completeness check runs and reports **zero** missing and **zero** stale symbols. -- [x] `api/ecosystem.md` lists all seven packages including **CTLie**, and every link - resolves through `InterLinks`. -- [x] The Internals page is titled "Internals" and includes `describe`. -- [x] No `@docs` block anywhere under `docs/src/` outside `api/` — the guides link to the - reference, they do not inline it (this deletes the inline block currently at the end of - `attic/manual-macro-free.md`). -- [x] `docs/make.jl`'s API node is built from the manifest, not from a literal list. - -Verified by an independent full rebuild (`julia --project=docs docs/make.jl` + -`npx vitepress build build/1`), not just re-checked against the diff: all seven criteria hold, -and this rework also fixes the 10 unresolved self-`@ref`s for `solve`/`methods`/`describe` on -the old `api/private.md` found while auditing PR 2's build. - -The manifest itself was rewritten as a literal, hand-maintained `API_THEMES` array (per this -report's own example) rather than parsed from `99-api-coverage.md` at build time — an earlier -draft scraped that file's prose and picked up 46 symbols that don't actually resolve in -`@docs` (bare `constraint`/`objective`/`variable`/`parameter` etc. need module qualification; -six others — `solve_explicit`, `solve_descriptive`, `SolveMode`, `display_ocp_configuration`, -`get_full_strategy_registry`, `will_solver_print` — are explicitly listed in -`99-api-coverage.md` §12 as **not** documented as available, but the scraper picked them up -from the sentence saying so anyway, since it does not understand negation in prose). All of -these are fixed now: zero "undefined binding" / "no docs found" / "duplicate docs found" -warnings on a full rebuild, one genuine upstream gap found and filed -([`CTDirect.jl#623`](https://github.com/control-toolbox/CTDirect.jl/issues/623): -`Collocation` has no docstring at all), and the six module aliases (`CTBase`, `CTLie`, -`CTFlows`, `CTModels`, `ADNLPModels`, `ExaModels`) moved from `@docs` to prose on -`api/qualified.md`, since `@doc` on a bare module without its own docstring only returns -Julia's generic "here are its exports" filler. - -Not fixed here, and out of scope for this PR: a large number of "cannot resolve `@ref`" -warnings on the generated theme pages, from sibling-package docstrings whose own "See also" -cross-references use bare `@ref` (correct only within their own doc build, not when the -docstring is reused here). This is **not** permanent upstream debt: CTBase 0.29, CTModels -0.16, CTFlows 0.17, CTLie and CTSolvers 0.5 have already migrated these to self-referencing -`@extref` (verified — zero bare `@ref` left in CTBase/CTModels/CTLie/CTSolvers `src/`, 15 -residual in CTFlows). The warnings persist only because `docs/Project.toml` is pinned to -older versions (`CTBase = "0.28"`, `CTModels = "0.15"`, …) — itself blocked on CTDirect and -CTParser not yet accepting the newer sibling releases, the same blocker tracked in -`.reports/upgrade-v2.1.0-beta.md`. Once that unblocks and the compat pins move, most of these -warnings should disappear on their own. `warnonly=true` tolerates them in the meantime. - -## Outgoing links - -- Symbol partition and the coverage checklist: [`99-api-coverage.md`](99-api-coverage.md) -- Sitemap the themes mirror: [`00-cahier-des-charges.md`](00-cahier-des-charges.md) §7 -- The `describe.jl` file-list fix: [`01-infrastructure.md`](01-infrastructure.md) §3.5 -- The flow-accessor re-export decision that changes the Flows theme: - [`05-flows-indirect.md`](05-flows-indirect.md) §`flows/accessors.md` diff --git a/docs/reports/10-migration.md b/docs/reports/10-migration.md deleted file mode 100644 index 55718d46b..000000000 --- a/docs/reports/10-migration.md +++ /dev/null @@ -1,267 +0,0 @@ -# Migration and deprecations — specification - -**PRs**: 3 (the shims, code), 12 (the page, docs), 13 (drop `docs/attic/`) · **Status**: specification -**Scope**: `src/deprecated.jl`, `test/suite/reexport/test_deprecated.jl`, -`test/suite/reexport/test_ctlie.jl`, `test/suite/flows/test_flow_api.jl`, `BREAKING.md`, -`docs/src/migration.md`, `docs/attic/` (PR 13) - -## Objective - -Today a v2.0 script fails with `UndefVarError: Lie not defined` or a bare `MethodError`. -Nothing tells you what replaced it. Three deliverables: - -1. **Part 1 (PR 3, code)** — a shim layer so every removed spelling throws a - `CTBase.PreconditionError` naming its replacement. -2. **Part 2 (PR 12, docs)** — one page that is the old→new table, and the honest list of what - could *not* be shimmed. -3. **Part 3 (PR 13, docs)** — delete `docs/attic/` now that every page in it has either been - harvested into `docs/src/` (PRs 2–12) or superseded; nothing in `docs/src/` may reference it - afterward. - -PR 3 is independent of every docs PR and can land at any point. PR 13 depends on PR 12 (the -migration page is the last consumer of attic content — the "Legacy initial guesses" section -harvests from `attic/manual-initial-guess.md`). - ---- - -# Part 1 — `src/deprecated.jl` (PR 3) - -## Design - -One file, `src/deprecated.jl`, included from `src/OptimalControl.jl` **after** the -`imports/` block (the shims reference `AbstractVectorField`, `Model`, `AbstractSolution`, -`Flow` — all installed there) and before `helpers/`. Concretely, between the commented-out -`redefine.jl` line and the helpers block. - -Every shim throws: - -```julia -CTBase.PreconditionError(msg; reason, suggestion, context) -``` - -verified signature at `CTBase/src/Exceptions/types.jl:126-140` — positional `msg::String`, -keyword-only `reason` / `suggestion` / `context`, all `Union{String,Nothing}`. - -**Why `PreconditionError` and not `IncorrectArgument`**: the arguments are individually fine; -it is the *spelling* the current API no longer accepts. That is a contract failure, which is -exactly what `PreconditionError` is for (`Handbook/philosophy/exceptions.md`). - -**Piracy is deliberate and must be banner-commented.** These methods extend functions and -types owned by CTFlows, CTModels, Base and LinearAlgebra. OptimalControl sits at the top of -the stack and is the only place a *user-facing* migration message belongs. The precedent is -already in the repo: `src/helpers/describe.jl:51-55` pirates -`CTBase.Strategies.describe(::Symbol)` under a `# NOTE:` banner. Follow that style. - -## Verdict table - -| Target | Shim from OptimalControl? | Already upstream? | Decision | -| --- | --- | --- | --- | -| `Lie(X, f)` / `Lie(X, Y)` → `ad` | yes — free name, catch-all method, no piracy | no | **do it**, `export Lie` | -| `X ⋅ f` → `ad(X, f)` | yes — **only** as `import LinearAlgebra: ⋅` + methods on `dot` | no | **do it**, that form only | -| `HamiltonianLift` → `LiftedHamiltonianFunction` | yes — throwing *function* | no | **do it** | -| `success(sol)` → `successful(sol)` | yes — `Base.success(::AbstractSolution)` | no | **do it**, do **not** export | -| `time(ocp)` → `times(ocp)`; `time(sol)` → `time_grid(sol)` | yes — `Base.time(::Model)` / `(::AbstractSolution)` | no | **do it**, do **not** export | -| `Flow(f::Function)` | yes — piracy on `CTFlows.Flows.Flow` | no | **do it** | -| flow call `f(t0,x0,p0,tf,λ)` | yes — one method on the `AbstractHamiltonianFlow` alias | no | **do it** — best value/cost of the set | -| flow call `f(t0,x0,tf,λ)` (state flow) | yes — same pattern on `AbstractStateFlow` | no | **do it**, for symmetry | -| `Flow(ocp, u, g, μ)` | only as a 4-arity specialisation; the exact signature would **overwrite** upstream | **yes** — `PreconditionError` at `CTFlows/…/src/Flows/building.jl:1019` | **skip**; filed [CTFlows#401](https://github.com/control-toolbox/CTFlows.jl/issues/401) (its `suggestion` string is wrong for this case) | -| flow call `augment=true` | **no** — same positional signature as the still-valid call, so a shim would overwrite CTFlows' own `OptimalControlFlow` method; confirmed this breaks precompilation (`ERROR: Method overwriting is not permitted during Module precompilation`) | no | **skip**; filed [CTFlows#402](https://github.com/control-toolbox/CTFlows.jl/issues/402) | -| flow call `f(...; saveat=, abstol=, reltol=, alg=, ...)` (per-call integrator option override) | **no** — same reason as `Flow(ocp, u, g, μ)`: the call signature is closed (`variable`/`unsafe`/`variable_costate` only), so a shim would overwrite CTFlows' own call method | no — bare `MethodError`, not caught anywhere | **skip**; document in `BREAKING.md` (§"Flow call convention" point 4). Construction-time options (`Flow(ocp, u; abstol=...)`) are unaffected and still work. Not filed upstream: this looks like a deliberate CTFlows design choice (options are baked into the flow's type at construction), not a bug — unlike the two rows above. | -| `@Lie … autonomous=false` | n/a | **yes** — `IncorrectArgument` at `CTLie/src/lie_macro.jl:381` | **skip** | -| `autonomous=` / `variable=` / `inplace=` on the `Data` constructors | **no** — 14 entry points, and the workaround duplicates CTBase's trait detection | no | **skip**; optional CTBase issue | - -## The three items that need care - -### `⋅` — must re-export LinearAlgebra's binding, not define a new one - -`⋅` is **not reachable today**: `src/imports/examodels.jl` does `using LinearAlgebra: -LinearAlgebra`, which binds only the module name, and `test/suite/reexport/test_ctlie.jl:82` -asserts `!is_exported(OptimalControl, :⋅)`. So `X ⋅ f` is currently an `UndefVarError`. - -If OptimalControl defined a **new** generic `⋅` and exported it, a user writing -`using OptimalControl, LinearAlgebra` would get an export-conflict warning and every bare `⋅` -— including the idiomatic `p ⋅ f(x, u)` in their own Hamiltonians — would become an -`UndefVarError`. That is a worse regression than the one being fixed. - -The correct form: - -```julia -import LinearAlgebra: ⋅ # the same binding, not a new function -export ⋅ -LinearAlgebra.dot(X::AbstractVectorField, f::Function) = throw(...) -``` - -Because `⋅ === dot` and OptimalControl's `⋅` is an import alias, both `using` statements -resolve to the same binding and Julia stays silent. This is also exactly the v2.0 topology -(old CTBase did `using LinearAlgebra`, added `⋅` methods, and exported the name). - -Two costs to state in the file header: `using OptimalControl` re-introduces -`LinearAlgebra.dot` into user scope, and `dot(::AbstractVectorField, ::Function)` is a method -on a very hot generic. Ship the `AbstractVectorField` method; a `dot(::Function, ::Function)` -overload is optional and higher-risk. - -### `HamiltonianLift` — a function, not a type - -`HamiltonianLift` was a *type* in v2.0, so `H isa HamiltonianLift` was legal. A throwing -**function** makes that a `TypeError` — ugly but loud. An `abstract type` stand-in with a -throwing constructor would make the same test return `false` **silently**, which is precisely -the failure mode `BREAKING.md` warns about for `H isa AbstractHamiltonian`. Choose the loud -one. - -### The 5-positional flow call — one method covers everything - -`AbstractHamiltonianFlow` is a type alias over the third parameter -(`AbstractFlow{TD,VD,HamiltonianDynamics}`), so a single method catches `OptimalControlFlow`, -`HamiltonianFlow` and `MultiPhaseHamiltonianFlow`: - -```julia -function (f::CTFlows.Flows.AbstractHamiltonianFlow)( - t0::Real, x0, p0, tf::Real, variable -) -``` - -No 5-positional method exists anywhere upstream, so this **adds** a method rather than -overwriting one. The optional state-flow twin on `AbstractStateFlow` is disjoint -(`StateDynamics` vs `HamiltonianDynamics`) — no ambiguity. - -**One hazard that cannot be fixed here**: on an `OptimalControlFlow`, the old state-call -`f(t0, x0, tf, λ)` with a real `λ` silently matches the 4-positional Hamiltonian method and -misreads the arguments. Note it on the migration page; it belongs upstream. - -## Tests - -**New file**: `test/suite/reexport/test_deprecated.jl`, defining `test_deprecated()`. -Discovery is automatic (`test/runtests.jl` scans `suite/*/test_*` and builds -`Symbol(:test_, name)`); nothing to register. - -The two **flow-shaped** shims go in `test/suite/flows/test_flow_api.jl` instead — it already -loads the integrator and has the OCP fixtures. - -Assertion style, matching the neighbours -(`test/suite/flows/test_flow_api.jl:255,269`, `test/suite/reexport/test_ctflows.jl:153`): - -```julia -Test.@test_throws OptimalControl.PreconditionError Lie(X, Y) -``` - -and, because the message *is* the feature, assert it: - -```julia -err = try Lie(X, Y) catch e; e end -Test.@test err isa OptimalControl.PreconditionError -Test.@test occursin("deprecated", err.msg) -Test.@test occursin("ad(X, f)", err.suggestion) -``` - -### Existing assertions that become false — must be rewritten in the same PR - -| Location | Current | Why it breaks | -| --- | --- | --- | -| `test/suite/reexport/test_ctlie.jl:81` | `@test !is_exported(OptimalControl, :Lie)` | we now export `Lie` | -| `test/suite/reexport/test_ctlie.jl:82` | `@test !is_exported(OptimalControl, :⋅)` | we now export `⋅` | -| `test/suite/reexport/test_ctlie.jl:84` | `@test !isdefined(OptimalControl, :HamiltonianLift)` | we now define it | - -Replace that "Removed API" testset with "Removed API is now shimmed", asserting the new -contract. For `⋅`, add the binding-identity check — it is what prevents the export conflict: - -```julia -Test.@test getfield(OptimalControl, :⋅) === LinearAlgebra.dot -``` - -### Assertions that keep passing (verified, no change needed) - -- `test_ctmodels.jl:224-228` — `!is_exported(:success)` ✓ (never exported); - `getfield(OptimalControl, :success) === Base.success` ✓ (writing `Base.success(...)` - qualified creates no OptimalControl binding); the `parentmodule` check ✓ (our method's - parent is `OptimalControl`, not `CTModels.Solutions`). -- `test_ctmodels.jl:249-256` — same reasoning for `time`. -- `test_ctflows.jl:43` — `Flow isa UnionAll` ✓. -- `test_flow_api.jl:133,134,153,170` — 3- and 4-positional `MethodError` expectations; - our shims are 5-positional. Disjoint ✓. - -## Also in PR 3 - -- **`BREAKING.md`**: both removal tables currently say the names are simply gone. Add that - they now fail with a `PreconditionError` naming the replacement — the migration document is - where that contract belongs. -- **Two upstream issues**, filed not fixed: - - CTFlows: the `PreconditionError` at `src/Flows/building.jl:1019` has a `suggestion` that - is wrong for the `Flow(ocp, u, g, μ)` case — it says the OCP flow takes no positional - argument beyond the model (false: `Flow(ocp, law)` is supported) and never mentions - `constraint=`/`multiplier=`. - - CTFlows: add `augment=nothing` to the three call methods and throw when it is non-`nothing`. -- **No `docs/` change.** `docs/make.jl` has `warnonly=true` and the DocumenterReference - extension scans `src/`, so `deprecated.jl` gets an Internals page for free. - -## Acceptance criteria (PR 3) - -- [x] `src/deprecated.jl` exists, is included after `imports/`, and has the piracy banner - plus the "not shimmed, and why" list. -- [x] `Lie`, `⋅`, `HamiltonianLift` are exported; `success`, `time`, `Flow` are not - re-exported by this file. -- [x] `using OptimalControl, LinearAlgebra` produces **no** export-conflict warning. -- [x] Every shim's message names its replacement, verified by an `occursin` assertion. -- [x] The three `test_ctlie.jl` assertions are rewritten; the full suite is green via - `ct-dev-mcp` (`get_test_command` → run + `tee` → `generate_report`). -- [x] `BREAKING.md` records the new contract. -- [x] The two CTFlows issues are filed and linked from `BREAKING.md`. - ---- - -# Part 2 — `docs/src/migration.md` (PR 12) - -- **Purpose** — one page a v2.0 user can read top to bottom and fix their script. -- **Outline** - - `## Start here: `Flow` needs an integrator` — the single most common first failure - - `## What was renamed` — the old→new table, matching - [`00-cahier-des-charges.md`](00-cahier-des-charges.md) §8.2 - - `## What changed shape` — the `Flow` call convention, constrained flows, - `augment=` → `variable_costate=`, the `is_` keyword prefix - - `## What changed meaning silently` — the three that will **not** announce themselves: - - `Lift(f)` on a plain `Function` no longer returns an `AbstractHamiltonian` - - `OpenLoop` is unconditionally non-autonomous — `u(t)`, never `u()` - - on an `OptimalControlFlow`, `f(t0, x0, tf, λ)` matches the Hamiltonian method and - misreads its arguments - - `## What you get instead of an error` — the shim table: which spellings now throw a - `PreconditionError` - - `## What could not be shimmed, and why` — `augment=`, the `is_`-prefixed constructor - keywords; Julia cannot dispatch on a keyword name. What the raw `MethodError` looks like - (it does name the offending keyword) - - `## Legacy initial guesses` — the NamedTuple form dropped from - `attic/manual-initial-guess.md` - - `## v1.x → v2.0` — a pointer to `BREAKING.md`, not a copy -- **Code blocks are inert.** This is the one page in the site that deliberately shows - spellings that error; it must not execute. Say so at the top. -- **Source** — `BREAKING.md` is the authority; this page is its user-facing rendering, not a - duplicate. Keep them in sync by making the page link into `BREAKING.md` for the detail. - -## Acceptance criteria (PR 12) - -- [ ] The page covers every row of §8.2 and every row of the verdict table above. -- [ ] The three silent-semantics changes each have a `!!! warning`. -- [ ] The page is explicitly non-executing and says why. -- [ ] `geometry/overview.md`, `geometry/ad.md`, `geometry/lift.md`, `results/solution.md` - and `flows/simulation.md` all link here. - ---- - -# Part 3 — delete `docs/attic/` (PR 13) - -Small and mechanical, split out from PR 12 so the migration page's content review isn't -gated on a repo-wide grep. Depends on PR 12: `attic/manual-initial-guess.md` is the source -for the page's "Legacy initial guesses" section, so the file must still exist while that -section is written. - -- Delete every file under `docs/attic/`, including `docs/attic/README.md`. -- Grep `docs/src/` for any remaining `attic/` reference and fix or remove it — there should be - none left once PR 12 has landed, since every page's spec in this directory says its source is - either `attic/.md §
` (harvested) or `new`. -- The references to `docs/attic/` inside `docs/reports/*.md` (this directory) are historical - and stay — they document where each page's content came from, and `docs/reports/` is outside - both Documenter's and VitePress's source scan. - -## Acceptance criteria (PR 13) - -- [ ] `docs/attic/` no longer exists. -- [ ] No file under `docs/src/` references `attic/`. -- [ ] `julia --project=docs docs/make.jl` still runs clean. diff --git a/docs/reports/README.md b/docs/reports/README.md deleted file mode 100644 index 99301234e..000000000 --- a/docs/reports/README.md +++ /dev/null @@ -1,113 +0,0 @@ -# Documentation rewrite — specification set - -**Date**: 2026-08-09 · **Target**: OptimalControl.jl v2.1.0-beta · **Status**: specification -**Scope**: `docs/` (full rewrite) + `src/deprecated.jl` (new) - -The v2.1.0-beta upgrade ([`.reports/upgrade-v2.1.0-beta.md`](../../.reports/upgrade-v2.1.0-beta.md)) -deliberately left `docs/` out of scope. This is that follow-up. It is too large for one PR, so -the work is split in two levels: **this specification set**, then **one PR per section**, each -backed by its own report here. - -> **Why `docs/reports/` and not `.reports/`.** `.reports/` is untracked local scratch; this -> specification is the contract for twelve PRs and has to survive in the repository. It sits -> beside `docs/src/`, not inside it, so Documenter and VitePress never see it — same -> arrangement as `docs/attic/`. - -## How to read this - -- Start with [`00-cahier-des-charges.md`](00-cahier-des-charges.md) — audience, principles, - the sitemap, the cross-cutting conventions. Everything else refines it. -- [`01-infrastructure.md`](01-infrastructure.md) is what must land first; nothing else builds - without it. -- Reports `02`–`08` are the per-section page specs. They share one template (§"Template" in - the cahier des charges) and are meant to be kept open while writing the pages. -- [`99-api-coverage.md`](99-api-coverage.md) is the objective acceptance criterion for the - whole effort: every re-exported symbol lands in at least one guide page and in the API - reference. - -## Files - -| File | Role | -| --- | --- | -| [`00-cahier-des-charges.md`](00-cahier-des-charges.md) | Main spec: audience, principles, sitemap, conventions, acceptance criteria | -| [`01-infrastructure.md`](01-infrastructure.md) | `docs/Project.toml`, `make.jl`, `api_reference.jl`, VitePress, Literate, InterLinks, archiving | -| [`02-getting-started.md`](02-getting-started.md) | Installation · First problem · Guided tour | -| [`03-modelling.md`](03-modelling.md) | Formulation · `@def` · Functional API · No control · Inspect · AI | -| [`04-solve-direct.md`](04-solve-direct.md) | Overview · Initial guess · Choosing a method · Options · Explicit mode · GPU | -| [`05-flows-indirect.md`](05-flows-indirect.md) | PMP · From an OCP · From Hamiltonians · Loops · Accessors · Multi-phase · Constrained arcs · Shooting | -| [`06-geometry.md`](06-geometry.md) | `Lift` · `ad` · `Poisson` & `@Lie` · `∂ₜ` · AD backend | -| [`07-results.md`](07-results.md) | Solution object · Plotting · Save & load | -| [`08-examples.md`](08-examples.md) | The example gallery | -| [`09-api-reference.md`](09-api-reference.md) | Thematic generated pages + Internals + Ecosystem | -| [`10-migration.md`](10-migration.md) | `src/deprecated.jl` design + the "Migrating to v2.1" page | -| [`99-api-coverage.md`](99-api-coverage.md) | Symbol → page matrix | - -## Work board - -Status legend: ⬜ not started · 🟡 in progress · ✅ merged - -| # | PR | Section | Spec | Depends on | Status | -| --- | --- | --- | --- | --- | --- | -| 1 | `docs: specification reports` | — | all of this directory | — | ✅ | -| 2 | [`docs: infrastructure`](https://github.com/control-toolbox/OptimalControl.jl/pull/854) | build + skeleton | [`01`](01-infrastructure.md) | 1 | ✅ | -| 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 | ✅ | -| 6 | [`docs: solve`](https://github.com/control-toolbox/OptimalControl.jl/pull/866) | Solve (direct) | [`04`](04-solve-direct.md) | 5 | ✅ | -| 7 | [`docs: results`](https://github.com/control-toolbox/OptimalControl.jl/pull/867) | 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 | ✅ | -| 9 | [`docs: geometry`](https://github.com/control-toolbox/OptimalControl.jl/pull/869) | Geometry | [`06`](06-geometry.md) | 8 | ✅ | -| 10 | [`docs: examples`](https://github.com/control-toolbox/OptimalControl.jl/pull/872) | Examples | [`08`](08-examples.md) | 8, 9 | ✅ | -| 11 | [`docs: getting started`](https://github.com/control-toolbox/OptimalControl.jl/pull/873) | Getting started + `index.md` | [`02`](02-getting-started.md) | 5–10 | ✅ | -| 12 | [`docs: migration page`](https://github.com/control-toolbox/OptimalControl.jl/pull/874) | Migration page (`docs/src/migration.md`) | [`10`](10-migration.md) §2 | all | ✅ | -| 13 | [`docs: drop docs/attic`](https://github.com/control-toolbox/OptimalControl.jl/pull/940) | Delete `docs/attic/` | [`10`](10-migration.md) §2 | 12 | 🟡 | - -PR 3 is code-only and independent — it can run in parallel with any docs PR. - -## Decisions already taken - -Recorded here so they are not re-litigated mid-PR. - -| Question | Decision | -| --- | --- | -| Top-level structure | **By capability**, not Diátaxis buckets. Sections are named after what the user is doing. The catch-all "Manual" node disappears. | -| Deprecation shims | **One `src/deprecated.jl` in OptimalControl**, covering removed *names* and removed *call signatures*. Piracy accepted; it is the package the user loads. | -| Old markdown | **Archive to `docs/attic/`**, rewrite from a blank page, harvest from the attic, delete the attic in PR 13. | -| API reference | **Thematic pages generated from a Julia manifest** in `docs/api_reference.jl`. No hand-written symbol list. | -| `[compat]` | `docs/Project.toml` **mirrors the root `Project.toml` exactly**. The newer sibling releases (CTBase 0.29, CTModels 0.16, CTFlows 0.17, CTLie 0.2, CTSolvers 0.5) will not resolve — CTDirect and CTParser still require the older ones. | -| Shapes | **"1-D is a scalar" is in force and tested** (`test/suite/shape/test_shape_contract.jl`). Guides write scalar-style for 1-D; only the in-place buffer stays a vector. The old functional-API page teaches the opposite and that section is deleted. | -| Flow accessors | PR 8 re-exports `hamiltonian`, `hamiltonian_vector_field`, `vector_field` and the four `get_*_gradient` functions. `system` / `integrator` stay qualified. | -| `DirectShooting` | Out of scope — not functional yet. Not mentioned anywhere in the docs. | -| Serialization | The user API is `format=:JLD` / `:JSON`; the `*Tag` types are internal and stay out of the docs. | - -## Branching - -**No stacking.** Each PR branches from `main`, is reviewed, and merges back. - -PR 1 (this directory) is inert — it adds `docs/reports/` and nothing under `docs/src/`, so -neither Documenter nor VitePress reads it and no CI job changes behaviour. It can merge to -`main` immediately, which is the whole point: once it is in, every later branch starts from a -`main` that already contains the specification. - -``` -main ──●──────●──────●──────●── … - \ \ \ \ - PR 1 PR 2 PR 3 PR 5 … - (spec) (infra) (shims) (modelling) -``` - -The one real ordering constraint is **PR 2**: it creates the page skeleton with the final -`@id` anchors that PRs 4–11 fill in. Branch those from `main` *after* PR 2 has merged. If PR 2 -is still in review and you want to start a content PR, branch from PR 2's head rather than -from `main` — that is the only case where stacking is justified, and it should be temporary. - -A long-lived integration branch collecting all twelve PRs would defeat the purpose: the value -of the split is that each piece is reviewable and mergeable on its own. - -## Conventions for these reports - -- Every claim about the code cites `file:line`. If it is not cited, it was not verified. -- Every page spec names the exact symbols it must cover — that is what feeds - [`99-api-coverage.md`](99-api-coverage.md). -- "Source" on a page spec is either `attic/.md §
` (harvest) or - `new` (write from scratch). No page is ever "keep as is" — the API changed under all of them. diff --git a/docs/src/migration.md b/docs/src/migration.md index 8b36767fc..3b2a90064 100644 --- a/docs/src/migration.md +++ b/docs/src/migration.md @@ -241,8 +241,9 @@ method CTFlows still needs, or duplicating trait-detection logic that belongs up | Spelling | What actually happens today | Why it isn't shimmed | | --- | --- | --- | -| `Flow(ocp, u, g, μ)` (3 positional) | Upstream `PreconditionError` (not from `OptimalControl`) — but its `suggestion` text is currently wrong for this case | A 4-arity shim here would overwrite CTFlows' own method for this exact signature. Filed as [CTFlows#401](https://github.com/control-toolbox/CTFlows.jl/issues/401). | +| `Flow(ocp, u, g, μ)` (3 positional) | `PreconditionError`, thrown by CTFlows (not by `OptimalControl`), naming the keyword form: `Flow(ocp, u; constraint=g, multiplier=μ)` | A 4-arity shim here would overwrite CTFlows' own method for this exact signature — and it need not, CTFlows guards it directly ([CTFlows#401](https://github.com/control-toolbox/CTFlows.jl/issues/401), fixed). | | `augment=true` | Bare `MethodError`, not caught anywhere | Same positional signature as the still-valid call — a shim would overwrite CTFlows' own `OptimalControlFlow` method and breaks precompilation (`Method overwriting is not permitted`). Filed as [CTFlows#402](https://github.com/control-toolbox/CTFlows.jl/issues/402). | +| `CTSolvers.Modelers.ADNLP()`, `CTDirect.Collocation()` | `UndefVarError` on `CTSolvers` / `CTDirect` — the module name is not bound under `using OptimalControl` | `CTSolvers`, `CTDirect` and `CTParser` are implementation packages and are deliberately **not** re-exported (unlike `CTBase`, `CTModels`, `CTFlows`, `CTLie`), so there is no bound name to attach a throwing method to. The constructors themselves *are* re-exported: write `OptimalControl.ADNLP()` / `OptimalControl.Collocation()`. | | Per-call integrator options (`saveat=`, `abstol=`, `reltol=`, `alg=`, …) | Bare `MethodError` | Same reason: the call signature is closed to `variable`/`unsafe`/`variable_costate` only; a shim would overwrite CTFlows' own call method. Unlike the two rows above, this looks like a deliberate design choice (options belong at construction, baked into the flow's type), not a bug — not filed upstream. Construction-time options are unaffected. | | `@Lie [X, Y] autonomous=false` | `IncorrectArgument`, raised at macro-expansion time | Already thrown upstream in CTLie — nothing to add. | | `autonomous=`/`variable=`/`inplace=` on `VectorField`, `Hamiltonian`, and other `Data` constructors | Silently ignored or `MethodError`, depending on the constructor | Julia cannot dispatch on a keyword's *name*; a shim would mean re-detecting the trait by hand at all 14 entry points, duplicating logic CTBase already owns. |