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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions BREAKINGS.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,22 @@ This document outlines all breaking changes introduced in CTBase v0.18.0-beta co
- `NotStored` / `NotStoredType` are unchanged and remain extraction-internal to
`CTBase.Options` (the defining file was renamed `not_provided.jl` → `not_stored.jl`).

## Non-breaking note (0.26.1-beta)

- **Traits**: New `Feedback` trait family added for encoding how a control law closes the loop
- **New trait family**: `AbstractFeedback` with tags `OpenLoopFeedback`, `ClosedLoopFeedback`, `DynClosedLoopFeedback`
- **Type-parameter-only contract**: trait value read from a type parameter by the `feedback` accessor; no `has_feedback_trait` guard
- **Derived predicates**: `is_open_loop(obj)`, `is_closed_loop(obj)`, `is_dyn_closed_loop(obj)`
- **No breaking changes**: Purely additive. Existing code unaffected.
- **Data**: New `PseudoHamiltonian` and `ControlLaw` data types added
- **PseudoHamiltonian**: scalar function `H̃(t, x, p, u[, v]) → ℝ` with explicit control argument; `AbstractPseudoHamiltonian` supertype and `PseudoHamiltonian` concrete struct
- **ControlLaw**: control law function `u(⋯) → 𝒰` with feedback trait; `AbstractControlLaw` supertype, `ControlLaw` concrete struct, and `OpenLoop`/`ClosedLoop`/`DynClosedLoop` user-facing constructors
- **No breaking changes**: Purely additive. Existing data types unchanged.
- **Differentiation**: New pseudo-Hamiltonian gradient methods added
- `pseudo_hamiltonian_gradient(backend, h̃, t, x, p, u, v) → (∂H̃/∂x, ∂H̃/∂p)`
- `pseudo_hamiltonian_control_gradient(backend, h̃, t, x, p, u, v) → ∂H̃/∂u`
- **No breaking changes**: Purely additive. Existing gradient methods unchanged.

## Non-breaking note (0.26.0-beta)

- **Traits**: New `ControlDependence` family added for encoding control presence in optimal control problems
Expand Down
56 changes: 56 additions & 0 deletions CHANGELOGS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,62 @@ All notable changes to CTBase will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.26.1-beta] - 2026-07-04

### ✨ New Features

#### **Feedback Trait Family**

- **New `AbstractFeedback` trait family** in `CTBase.Traits` for encoding how a control law closes the loop:
- **Tags**: `OpenLoopFeedback` (time/variable only), `ClosedLoopFeedback` (time + state), `DynClosedLoopFeedback` (time + state + costate)
- **Type-parameter-only contract**: the trait value is read from a type parameter of the concrete data type by the `feedback` accessor; no `has_feedback_trait` guard
- **Derived predicates**: `is_open_loop(obj)`, `is_closed_loop(obj)`, `is_dyn_closed_loop(obj)` dispatch on the feedback type parameter
- **Dynamics trait mapping**: open-loop and closed-loop → `StateDynamics`; dynamic closed-loop → `HamiltonianDynamics`

#### **PseudoHamiltonian**

- **New `PseudoHamiltonian` data type** in `CTBase.Data` for scalar functions `H̃(t, x, p, u[, v]) → ℝ` that extend the standard Hamiltonian with an explicit control argument `u`:
- **Abstract supertype**: `AbstractPseudoHamiltonian{TD,VD}` with trait accessors
- **Concrete struct**: `PseudoHamiltonian{F,TD,VD}` with keyword constructor `PseudoHamiltonian(f; is_autonomous, is_variable)`
- **Natural and uniform call signatures** matching the time/variable dependence traits
- **Dynamics trait**: always `HamiltonianDynamics`

#### **ControlLaw**

- **New `ControlLaw` data type** in `CTBase.Data` for control law functions `u(⋯) → 𝒰`:
- **Abstract supertype**: `AbstractControlLaw{FB,TD,VD}` with feedback, time, and variable dependence trait accessors
- **Concrete struct**: `ControlLaw{F,FB,TD,VD}` with typed constructor
- **User-facing constructors**: `OpenLoop(f; ...)`, `ClosedLoop(f; ...)`, `DynClosedLoop(f; ...)` fixing the feedback trait
- **Feedback-dependent call signatures**: natural and uniform signatures vary by feedback trait
- **Dynamics trait**: determined by feedback (open/closed-loop → `StateDynamics`; dynamic closed-loop → `HamiltonianDynamics`)

#### **Pseudo-Hamiltonian Gradient Methods**

- **New AD contract methods** in `CTBase.Differentiation` for pseudo-Hamiltonian gradients:
- `pseudo_hamiltonian_gradient(backend, h̃, t, x, p, u, v) → (∂H̃/∂x, ∂H̃/∂p)`: non-negated partial derivatives
- `pseudo_hamiltonian_control_gradient(backend, h̃, t, x, p, u, v) → ∂H̃/∂u`: control gradient (vanishes along optimal trajectories by PMP stationarity)
- **Rationale**: splitting `∂H̃/∂u` from `(∂H̃/∂x, ∂H̃/∂p)` reflects the distinct role of the stationarity condition and avoids computing the control gradient when only the state/costate gradients are needed
- **Extension implementation**: `CTBaseDifferentiationInterface` extension provides concrete implementations using `DifferentiationInterface.jl`
- **Fallback**: `NotImplemented` on `AbstractADBackend` without the extension

### 📚 Documentation

- **Guide pages updated**:
- `traits.md`: added Feedback trait family section with type-parameter-only contract note, predicates, and dynamics trait mapping; added Feedback to Other families table and accessor/predicate summary
- `data.md`: added PseudoHamiltonian section (construction, calling, dynamics trait) and ControlLaw section (constructors, call signatures, feedback→dynamics mapping); updated overview table, typed constructors, and See also
- `differentiation.md`: updated contract count to nine methods; added Pseudo-Hamiltonian gradients section with examples; updated extension note and See also
- **Docstring compliance**: aligned all docstrings in `feedback.jl`, `abstract_control_law.jl`, `control_law.jl`, `abstract_pseudo_hamiltonian.jl`, `pseudo_hamiltonian.jl`, `abstract_ad_backend.jl`, and `CTBaseDifferentiationInterface.jl` with the Handbook conventions (`See also:` inline lines, `# Returns`/`# Arguments` sections, `# Output` for show methods)
- **API reference**: added `feedback.jl`, `abstract_control_law.jl`, `control_law.jl`, `abstract_pseudo_hamiltonian.jl`, `pseudo_hamiltonian.jl` to auto-generated API pages

### 🧪 Testing

- Added tests for `pseudo_hamiltonian_gradient` (return signature `(grad_x, grad_p)`) and `pseudo_hamiltonian_control_gradient` (return `grad_u`), including `NotImplemented` error tests and `DifferentiationInterface` extension tests
- Added `pseudo_hamiltonian_control_gradient` to export-list checks in `test_differentiation_module.jl`

### ✅ Compatibility

- **No breaking changes**: purely additive. Existing code unaffected. See [BREAKINGS.md](BREAKINGS.md).

## [0.26.0-beta] - 2026-06-28

### ✨ New Features
Expand Down
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "CTBase"
uuid = "54762871-cc72-4466-b8e8-f6c8b58076cd"
version = "0.26.0-beta"
version = "0.26.1-beta"
authors = ["Olivier Cots <olivier.cots@irit.fr>", "Jean-Baptiste Caillau <caillau@univ-cotedazur.fr>"]

[deps]
Expand Down
5 changes: 5 additions & 0 deletions docs/api_reference.jl
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ function generate_api_reference(src_dir::String)
joinpath("Data", "hamiltonian.jl"),
joinpath("Data", "abstract_hamiltonian_vector_field.jl"),
joinpath("Data", "hamiltonian_vector_field.jl"),
joinpath("Data", "abstract_control_law.jl"),
joinpath("Data", "control_law.jl"),
joinpath("Data", "abstract_pseudo_hamiltonian.jl"),
joinpath("Data", "pseudo_hamiltonian.jl"),
),
),
(
Expand Down Expand Up @@ -175,6 +179,7 @@ function generate_api_reference(src_dir::String)
joinpath("Traits", "time_dependence.jl"),
joinpath("Traits", "variable_dependence.jl"),
joinpath("Traits", "control_dependence.jl"),
joinpath("Traits", "feedback.jl"),
),
),
(
Expand Down
130 changes: 124 additions & 6 deletions docs/src/guide/data.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,13 @@ using LinearAlgebra
|---|---|---|
| [`Data.VectorField`](@ref CTBase.Data.VectorField) | ``X : \mathcal{X} \to \mathbb{R}^n`` | `X(x)` |
| [`Data.Hamiltonian`](@ref CTBase.Data.Hamiltonian) | ``H : T^*\mathcal{X} \to \mathbb{R}`` | `H(x, p)` |
| [`Data.PseudoHamiltonian`](@ref CTBase.Data.PseudoHamiltonian) | ``\tilde{H} : T^*\mathcal{X} \times \mathcal{U} \to \mathbb{R}`` | `H̃(x, p, u)` |
| [`Data.HamiltonianVectorField`](@ref CTBase.Data.HamiltonianVectorField) | ``\vec{H} : T^*\mathcal{X} \to \mathbb{R}^{2n}`` | `HVF(x, p)` |
| [`Data.ControlLaw`](@ref CTBase.Data.ControlLaw) | ``u : \cdots \to \mathcal{U}`` | `u()` / `u(x)` / `u(x, p)` |

All three share the same trait axes — time dependence, variable dependence and
mutability. See [Traits](traits.md) for the full call-signature tables.
All five share the same trait axes — time dependence and variable dependence.
`VectorField` and `HamiltonianVectorField` also carry a mutability trait;
`ControlLaw` carries a feedback trait (see [Traits](traits.md)).

---

Expand Down Expand Up @@ -204,6 +207,117 @@ For a plain user-supplied function that does not accept `variable_costate`, pass

---

## PseudoHamiltonian

A `PseudoHamiltonian` wraps a scalar function ``\tilde{H}(x, p, u) \in \mathbb{R}``
that extends the standard Hamiltonian with an explicit control argument ``u``.

```math
\tilde{H} : T^*\mathcal{X} \times \mathcal{U} \to \mathbb{R}, \quad (x, p, u) \mapsto \tilde{H}(x, p, u).
```

Unlike [`Data.Hamiltonian`](@ref CTBase.Data.Hamiltonian), which encodes the
control implicitly, a pseudo-Hamiltonian takes the control as an additional
argument. This enables dynamic closed-loop flows where the control is computed
from the pseudo-Hamiltonian's maximisation condition (PMP stationarity:
``\partial \tilde{H} / \partial u = 0``).

### Construction

```@example data
# Autonomous, fixed (default): H̃(x, p, u)
ph1 = Data.PseudoHamiltonian((x, p, u) -> dot(p, x) + u^2)

# Non-autonomous, fixed: H̃(t, x, p, u)
ph2 = Data.PseudoHamiltonian((t, x, p, u) -> t * dot(p, x) + u^2; is_autonomous=false)

# Autonomous, non-fixed: H̃(x, p, u, v)
ph3 = Data.PseudoHamiltonian((x, p, u, v) -> dot(p, x) + u^2 + v[1]; is_variable=true)

ph1
```

### Calling

```@example data
x0, p0, u0 = [1.0, 0.5], [0.3, 0.7], 2.0

ph1(x0, p0, u0) # natural call
ph1(0.0, x0, p0, u0, nothing) # uniform call
ph2(0.5, x0, p0, u0) # non-autonomous
```

The dynamics trait of a `PseudoHamiltonian` is always
[`Traits.HamiltonianDynamics`](@ref CTBase.Traits.HamiltonianDynamics), since
pseudo-Hamiltonians involve both state and costate.

---

## ControlLaw

A `ControlLaw` wraps a function ``u(\cdots)`` that provides the control input for
an optimal control problem. The feedback trait (see [Traits](traits.md))
determines which arguments the control law depends on.

Three user-facing constructors fix the feedback trait:

| Constructor | Feedback trait | Natural signature (autonomous/fixed) |
|---|---|---|
| [`Data.OpenLoop`](@ref CTBase.Data.OpenLoop) | `OpenLoopFeedback` | `u()` |
| [`Data.ClosedLoop`](@ref CTBase.Data.ClosedLoop) | `ClosedLoopFeedback` | `u(x)` |
| [`Data.DynClosedLoop`](@ref CTBase.Data.DynClosedLoop) | `DynClosedLoopFeedback` | `u(x, p)` |

### Construction

```@example data
# Open-loop, autonomous, fixed (default): u()
u_ol = Data.OpenLoop(() -> 1.0)

# Closed-loop, autonomous, fixed: u(x)
u_cl = Data.ClosedLoop(x -> -x)

# Dynamic closed-loop, autonomous, fixed: u(x, p)
u_dcl = Data.DynClosedLoop((x, p) -> -x - p)

u_ol
```

Non-autonomous and variable variants are constructed with the same keywords as
other data types:

```@example data
u_ol_na = Data.OpenLoop((t, v) -> t * v; is_autonomous=false, is_variable=true)
```

### Calling

Every `ControlLaw` is callable via its **natural** signature (matching the
traits) and via a **uniform** signature that depends on the feedback trait:

| Feedback | Natural `(Aut, Fixed)` | Uniform |
|---|---|---|
| `OpenLoop` | `u()` | `u(t, v)` |
| `ClosedLoop` | `u(x)` | `u(t, x, v)` |
| `DynClosedLoop` | `u(x, p)` | `u(t, x, p, v)` |

```@example data
u_ol() # natural call
u_ol(0.0, nothing) # uniform call

u_cl(x0) # natural call
u_cl(0.0, x0, nothing) # uniform call

u_dcl(x0, p0) # natural call
u_dcl(0.0, x0, p0, nothing) # uniform call
```

The feedback trait determines the dynamics trait: open-loop and closed-loop
control laws carry [`Traits.StateDynamics`](@ref CTBase.Traits.StateDynamics),
while dynamic closed-loop control laws carry
[`Traits.HamiltonianDynamics`](@ref CTBase.Traits.HamiltonianDynamics).

---

## Querying traits

Because the trait metadata lives in the type, it can be recovered from any data
Expand Down Expand Up @@ -251,15 +365,19 @@ Traits.time_dependence(vf_typed)
```

The same positional form exists for [`Data.Hamiltonian`](@ref CTBase.Data.Hamiltonian)
(`Hamiltonian(f, TD, VD)`) and
(`Hamiltonian(f, TD, VD)`),
[`Data.PseudoHamiltonian`](@ref CTBase.Data.PseudoHamiltonian)
(`PseudoHamiltonian(f, TD, VD)`),
[`Data.HamiltonianVectorField`](@ref CTBase.Data.HamiltonianVectorField)
(`HamiltonianVectorField(f, TD, VD, MD)`).
(`HamiltonianVectorField(f, TD, VD, MD)`), and
[`Data.ControlLaw`](@ref CTBase.Data.ControlLaw)
(`ControlLaw(f, FB, TD, VD)`).

---

## See also

- [`Data.VectorField`](@ref CTBase.Data.VectorField), [`Data.Hamiltonian`](@ref CTBase.Data.Hamiltonian), [`Data.HamiltonianVectorField`](@ref CTBase.Data.HamiltonianVectorField) — concrete data types.
- [`Data.AbstractVectorField`](@ref CTBase.Data.AbstractVectorField), [`Data.AbstractHamiltonian`](@ref CTBase.Data.AbstractHamiltonian), [`Data.AbstractHamiltonianVectorField`](@ref CTBase.Data.AbstractHamiltonianVectorField) — abstract supertypes.
- [`Data.VectorField`](@ref CTBase.Data.VectorField), [`Data.Hamiltonian`](@ref CTBase.Data.Hamiltonian), [`Data.PseudoHamiltonian`](@ref CTBase.Data.PseudoHamiltonian), [`Data.HamiltonianVectorField`](@ref CTBase.Data.HamiltonianVectorField), [`Data.ControlLaw`](@ref CTBase.Data.ControlLaw) — concrete data types.
- [`Data.AbstractVectorField`](@ref CTBase.Data.AbstractVectorField), [`Data.AbstractHamiltonian`](@ref CTBase.Data.AbstractHamiltonian), [`Data.AbstractPseudoHamiltonian`](@ref CTBase.Data.AbstractPseudoHamiltonian), [`Data.AbstractHamiltonianVectorField`](@ref CTBase.Data.AbstractHamiltonianVectorField), [`Data.AbstractControlLaw`](@ref CTBase.Data.AbstractControlLaw) — abstract supertypes.
- [Traits](traits.md) — the three trait axes, the dynamics trait, and call-signature tables.
- [Exceptions](exceptions.md) — `PreconditionError` and `IncorrectArgument`, raised by the constructors and the `variable_costate` path.
41 changes: 37 additions & 4 deletions docs/src/guide/differentiation.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,15 @@ The concrete strategy stores a single option, `:ad_backend`, holding an
`AutoForwardDiff()`). `ADTypes` is a hard dependency of CTBase, so the default is
always available.

The contract has seven methods.
The contract has nine methods.
[`Differentiation.ad_backend`](@ref CTBase.Differentiation.ad_backend) — the accessor
returning the wrapped ADTypes backend — is resolved in core and always available; the
six differentiation primitives below live in an extension.
eight differentiation primitives below live in an extension.

!!! note "The differentiation methods live in an extension"
The contract methods (`gradient`, `derivative`, `differentiate`,
`pushforward`, `hamiltonian_gradient`, `variable_gradient`) are implemented in
`pushforward`, `hamiltonian_gradient`, `variable_gradient`,
`pseudo_hamiltonian_gradient`, `pseudo_hamiltonian_control_gradient`) are implemented in
the `CTBaseDifferentiationInterface` **package extension**. They become
available only once `DifferentiationInterface` *and* a concrete AD package
(e.g. `ForwardDiff`) are loaded. Without the extension, every contract method
Expand Down Expand Up @@ -164,6 +165,38 @@ hv = Data.Hamiltonian((x, p, v) -> 0.5 * (sum(abs2, x) + sum(abs2, p) + sum(abs2
Differentiation.variable_gradient(backend, hv, 0.0, [1.0, 2.0], [3.0, 4.0], [5.0, 6.0])
```

## Pseudo-Hamiltonian gradients

The two pseudo-Hamiltonian methods operate directly on a
[`Data.PseudoHamiltonian`](@ref CTBase.Data.PseudoHamiltonian) (see the [Data](data.md)
guide).
[`Differentiation.pseudo_hamiltonian_gradient`](@ref CTBase.Differentiation.pseudo_hamiltonian_gradient)
returns `(∂H̃/∂x, ∂H̃/∂p)`, **non-negated** — the caller applies the signs of
Hamilton's equations:

```@example diff
# H̃(x, p, u) = ½(‖x‖² + ‖p‖²) + u² → ∂H̃/∂x = x, ∂H̃/∂p = p
ph = Data.PseudoHamiltonian((x, p, u) -> 0.5 * (sum(abs2, x) + sum(abs2, p)) + u^2)
∂x, ∂p = Differentiation.pseudo_hamiltonian_gradient(backend, ph, 0.0, [1.0, 2.0], [3.0, 4.0], 2.0, nothing)
(∂x, ∂p)
```

[`Differentiation.pseudo_hamiltonian_control_gradient`](@ref CTBase.Differentiation.pseudo_hamiltonian_control_gradient)
returns `∂H̃/∂u`, which vanishes along optimal trajectories by the Pontryagin
Maximum Principle stationarity condition:

```@example diff
# ∂H̃/∂u = 2u = 4.0
Differentiation.pseudo_hamiltonian_control_gradient(backend, ph, 0.0, [1.0, 2.0], [3.0, 4.0], 2.0, nothing)
```

For a non-fixed pseudo-Hamiltonian, the `v` argument carries the variable:

```@example diff
phv = Data.PseudoHamiltonian((x, p, u, v) -> 0.5 * (sum(abs2, x) + sum(abs2, p)) + u^2 + v[1]; is_variable=true)
Differentiation.pseudo_hamiltonian_control_gradient(backend, phv, 0.0, [1.0, 2.0], [3.0, 4.0], 2.0, [5.0])
```

## The contract without the extension

Every contract method has a fallback on `AbstractADBackend` that throws
Expand All @@ -186,5 +219,5 @@ end # hide

- [`Differentiation.AbstractADBackend`](@ref CTBase.Differentiation.AbstractADBackend), [`Differentiation.DifferentiationInterface`](@ref CTBase.Differentiation.DifferentiationInterface) — the strategy types.
- [Implementing a Strategy](@ref), [Options System](@ref) — the contract these build on.
- [Data](data.md) — `Hamiltonian` wrappers consumed by the gradient methods.
- [Data](data.md) — `Hamiltonian` and `PseudoHamiltonian` wrappers consumed by the gradient methods.
- [Exceptions](exceptions.md) — `NotImplemented`, raised by the contract fallbacks.
Loading