Skip to content

docs: flows — indirect methods, simulation, and inspection - #868

Open
ocots wants to merge 11 commits into
docs/solvefrom
docs/flows
Open

docs: flows — indirect methods, simulation, and inspection#868
ocots wants to merge 11 commits into
docs/solvefrom
docs/flows

Conversation

@ocots

@ocots ocots commented Aug 21, 2026

Copy link
Copy Markdown
Member

Summary

Stacked on #866 (docs/solve, not yet merged) — base branch is docs/solve, not main, since PR 8 depends on PR 6 per the work board (sibling of #867, not stacked on it). Rebase onto main once #866 merges.

The largest section of the rewrite: 8 pages under docs/src/flows/, replacing the PR-2 stubs, covering the three jobs Flow does — indirect optimal control (PMP flow + shooting), simulation (integrate under a given control), and inspection (pull the Hamiltonian/vector field/control law back out of a flow). simulation.md and accessors.md have no predecessor on the old site; accessors.md was explicitly requested by the maintainer.

  • overview.md (new): map of the section + PMP recap. Corrects the spec's own claim that a flow without an integrator loaded fails with a bare MethodError — verified live it's a clean ExtensionError. Corrects method=:cpu/:gpu to be a construction-time keyword, not call-time.
  • from-ocp.md: the main path. Fixes all four spec-flagged stale patterns (Flow(x->x), positional Flow(ocp,u,g,μ), augment=true, OptimalControl.Hamiltonian). Harvests test/suite/problems/test_hamiltonian_type.jl's non-stationary-law example for the total-vs-partial Hamiltonian section — already tested, better than anything in the attic.
  • from-hamiltonians.md: full page from a 108-line skeleton, one section per constructor, all six verified live with real return types.
  • simulation.md (new): ControlledVectorField + OpenLoop/ClosedLoop. Corrects the framing of OpenLoop's non-autonomy — a zero-arg closure builds without error, only fails on first call, not rejected at construction as one might assume.
  • accessors.md (new, user-requested): the availability table is the whole point — verified cell-by-cell live, correcting one spec cell (hamiltonian(f) genuinely throws on a HamiltonianVectorField-built flow) and noting the failure mode isn't uniform (IncorrectArgument in some cells, plain MethodError in others — a real API inconsistency stated honestly). Carries the PR's one code change: re-exporting hamiltonian/hamiltonian_vector_field/vector_field/the four get_*_gradient functions in src/imports/ctflows.jl (zero naming collisions confirmed), with the matching test/suite/reexport/test_ctflows.jl testset and docs/reports/99-api-coverage.md update.
  • multi-phase.md (new, split out): worked example is the bang-bang time-optimal double integrator from test/problems/double_integrator.jl's tested _di_time_shoot_builder fixture, matching the spec's own choice.
  • constrained-arcs.md: fixes the positional-to-keyword constraint syntax. Documents a real subtlety found live: the constraint=:label (Symbol) spelling uses the OCP's own sign convention, not guaranteed to numerically agree with a hand-derived shooting g/μ pair — stated as a warning rather than assumed interchangeable.
  • shooting.md (new): cross-checked the harvested attic material against the CI-tested fixtures in test/problems/double_integrator.jl and test/helpers/shooting.jl rather than trusting it at face value — needed exactly one fix (a missing variable=tf). unsafe=true demonstrated with a genuine synthetic blow-up ODE, since the double-integrator problem itself is too well-behaved to ever blow up.

A genuine Documenter build quirk found and worked around (recorded in detail in docs/reports/05-flows-indirect.md): inside Documenter's @example sandbox (a baremodule, not a regular module), using OptimalControl fails to bind hamiltonian/hamiltonian_vector_field/vector_field/the four get_*_gradient functions specifically — while Flow/control_law/pseudo_hamiltonian (re-exported the same way, from the same CTFlows.Systems module) bind fine. Isolated via extensive bisection (a direct 1-hop import works; the two-hop reexport-through-OptimalControl chain doesn't, only inside a baremodule); root cause not fully identified despite that investigation. Confirmed this does not affect real usage — a normal using OptimalControl in an ordinary session or module gets the correct binding every time. Worked around with a hidden (# hide), redundant direct import in accessors.md's own setup block, invisible to readers and not something a real user needs.

Test plan

🤖 Generated with Claude Code

ocots and others added 11 commits August 20, 2026 23:20
…eld and the four get_*_gradient functions

These were exported by CTFlows.Systems but not re-exported by
OptimalControl, unlike their siblings control_law/pseudo_hamiltonian
— an inconsistency flows/accessors.md (docs: flows, PR 8) can't
document honestly without fixing. Zero naming collisions confirmed
against CTBase/CTModels/CTSolvers/CTDirect's own re-exports.
system/integrator stay deliberately unexported (too generic for a
DSL surface); reach them as CTFlows.Flows.system(f)/.integrator(f).

Adds the matching reexports() testset to test_ctflows.jl and updates
docs/reports/99-api-coverage.md §8/§14.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Map of the section: why flows vs direct methods, the PMP recap, the
three jobs (indirect solving, simulation, inspection), and the
integrator prerequisite. Corrects the spec's own claim that a flow
without an integrator loaded fails with a bare MethodError — verified
live it's actually a clean ExtensionError. Also corrects method=:cpu/
:gpu to be a construction-time keyword, not a call-time one (spec's
outline implied otherwise).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ports attic/manual-flow-ocp.md's basic-usage/non-autonomous/variable/
augment material (concatenation and state-constraints split out to
multi-phase.md and constrained-arcs.md per spec). Fixes all four
spec-flagged stale patterns (Flow(x->x), positional Flow(ocp,u,g,μ),
augment=true, OptimalControl.Hamiltonian). Harvests
test_hamiltonian_type.jl's non-stationary-law example for the
total-vs-partial Hamiltonian section rather than inventing one —
already tested, already correct.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Full page from a 108-line skeleton, one section per constructor
(VectorField, Hamiltonian, HamiltonianVectorField, PseudoHamiltonian
+ law, PseudoHamiltonianVectorField + law, SciML ODEFunction/
ODEProblem), each verified live with real return types. Fixes the
stale autonomous=false keyword (now is_autonomous=) and the
hand-rolled pseudo-Hamiltonian-via-dispatch pattern. New trap found:
SciML-backed flows are always NonFixed, need variable=nothing even
with no real free variable.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
No predecessor on the old site. Covers ControlledVectorField +
OpenLoop/ClosedLoop, and Flow(ocp, OpenLoop(u)) which — unlike a
plain ControlledVectorField flow — carries an objective. Corrects
the framing of OpenLoop's non-autonomy: a zero-arg closure builds
without error and only fails on first call, a bare MethodError far
from the mistake — not rejected at construction as one might assume.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The whole point of this page is the availability table (which
accessor works on which flow kind) — verified cell-by-cell live,
correcting one spec cell (hamiltonian(f) genuinely fails, IncorrectArgument,
on a HamiltonianVectorField-built flow — an earlier check had this
backwards) and noting the failure mode isn't uniform: some
unavailable accessors throw IncorrectArgument, others a plain
MethodError, a real API inconsistency stated honestly rather than
papered over.

Also works around a reproducible Documenter build quirk: `using
OptimalControl` inside Documenter's @example sandbox (a bare `module`,
not a regular one) fails to bind hamiltonian/hamiltonian_vector_field/
vector_field/the four get_*_gradient functions specifically, while
Flow/Hamiltonian/control_law/pseudo_hamiltonian (reexported the exact
same way, from the same CTFlows.Systems module) bind fine — isolated
via extensive bisection (bare 1-hop import works, the two-hop
reexport-through-OptimalControl chain doesn't, only inside a
baremodule). Root cause not fully identified; a redundant, hidden
(`# hide`) direct import right after `using OptimalControl` in the
page's setup block fixes the sandbox without affecting what a normal
user sees or needs to do — confirmed real users hitting `using
OptimalControl` normally get the correct binding every time.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Concatenation (Base.:*), jumps as functions/nothing/plain-Vector (the
Vector form confirmed live, an extra flexibility beyond callable-or-
nothing), inspection accessors (already fully reexported, no gap
here). Worked example is the bang-bang time-optimal double integrator
from test/problems/double_integrator.jl's _di_time_shoot_builder
fixture, matching the spec's own choice and confirmed against its
reference solution (p0=[1,1], one switch at t=1, tf=2).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ports attic/manual-flow-ocp.md's state-constraint material with the
positional-to-keyword fix. Confirms and documents a real subtlety
found live: the Symbol constraint spelling (constraint=:label) pulls
the constraint straight from the OCP's own sign convention, which is
not guaranteed to numerically agree with a hand-derived shooting
g/μ pair using a different convention — stated as a warning rather
than silently assumed interchangeable.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Assembled from example-double-integrator-time.md and tutorial.md's
indirect section, cross-checked against the CI-tested fixtures in
test/problems/double_integrator.jl and test/helpers/shooting.jl
rather than trusted at face value. The harvested in-place shooting
example needed exactly one fix — a missing variable=tf on two flow
calls in a NonFixed problem — everything else already worked.
unsafe=true demonstrated with a genuine synthetic blow-up ODE (the
double-integrator problem itself is too well-behaved to blow up even
under a wild guess, so it wasn't a useful demo case for that one).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…rk board

Full julia --project=docs docs/make.jl + npx vitepress build rebuild,
clean: 0 undefined-binding/no-docs/duplicate-docs warnings, all eight
flows/ pages' @example blocks execute, 0 unresolved @refs from them.
test/suite/flows/test_flow_api.jl + test/suite/problems/test_hamiltonian_type.jl
re-run (89/89 passed). test/suite/reexport/test_ctflows.jl re-run
(73/73 passed). typos clean.

All 8 acceptance criteria ticked against that build. Findings beyond
the checklist recorded in the report: the Documenter @example
baremodule binding quirk (isolated, worked around, not fully
root-caused), the corrected ExtensionError-not-MethodError claim on
overview.md, and the method=:cpu/:gpu construction-time correction.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant