Skip to content

Commit 4c2b549

Browse files
authored
Merge pull request #465 from control-toolbox/move/ctflows-to-ctbase-phase-d
Phase D: add CTBase.Differentiation submodule + DI extension (0.24)
2 parents 2215d42 + 109acb1 commit 4c2b549

18 files changed

Lines changed: 1526 additions & 2 deletions

Project.toml

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,57 @@
11
name = "CTBase"
22
uuid = "54762871-cc72-4466-b8e8-f6c8b58076cd"
3-
version = "0.23.0-beta"
3+
version = "0.24.0-beta"
44
authors = ["Olivier Cots <olivier.cots@irit.fr>", "Jean-Baptiste Caillau <caillau@univ-cotedazur.fr>"]
55

66
[deps]
7+
ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b"
78
DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
89

910
[weakdeps]
1011
Coverage = "a2441757-f6aa-5fb2-8edb-039e3f45d037"
12+
DifferentiationInterface = "a0c0ee7d-e4b9-4e03-894e-1c5f64a51d63"
1113
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
1214
Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a"
1315
MarkdownAST = "d0879d2d-cac2-40c8-9cee-1863dc0c7391"
1416
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
1517

1618
[extensions]
19+
CTBaseDifferentiationInterface = ["DifferentiationInterface"]
1720
CoveragePostprocessing = ["Coverage"]
1821
DocumenterReference = ["Documenter", "Markdown", "MarkdownAST"]
1922
TestRunner = ["Test"]
2023

2124
[extras]
25+
ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b"
2226
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
27+
Coverage = "a2441757-f6aa-5fb2-8edb-039e3f45d037"
28+
DifferentiationInterface = "a0c0ee7d-e4b9-4e03-894e-1c5f64a51d63"
29+
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
30+
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
31+
Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a"
32+
MarkdownAST = "d0879d2d-cac2-40c8-9cee-1863dc0c7391"
2333
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
2434

2535
[targets]
2636
test = [
2737
"Aqua",
2838
"Coverage",
39+
"DifferentiationInterface",
2940
"Documenter",
41+
"ForwardDiff",
3042
"Markdown",
3143
"MarkdownAST",
3244
"Test"
3345
]
3446

3547
[compat]
48+
ADTypes = "1"
3649
Aqua = "0.8"
3750
Coverage = "1"
51+
DifferentiationInterface = "0.7"
3852
DocStringExtensions = "0.9"
3953
Documenter = "1"
54+
ForwardDiff = "0.10, 1"
4055
Markdown = "1"
4156
MarkdownAST = "0.1"
4257
Test = "1"

docs/Project.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
11
[deps]
2+
ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b"
3+
CTBase = "54762871-cc72-4466-b8e8-f6c8b58076cd"
24
Coverage = "a2441757-f6aa-5fb2-8edb-039e3f45d037"
5+
DifferentiationInterface = "a0c0ee7d-e4b9-4e03-894e-1c5f64a51d63"
36
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
47
DocumenterVitepress = "4710194d-e776-4893-9690-8d956a29c365"
8+
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
59
LiveServer = "16fef848-5104-11e9-1b77-fb7a48bbb589"
610
MarkdownAST = "d0879d2d-cac2-40c8-9cee-1863dc0c7391"
711

812
[compat]
13+
ADTypes = "1"
914
Coverage = "1"
15+
DifferentiationInterface = "0.7"
1016
Documenter = "1"
1117
DocumenterVitepress = "0.3"
18+
ForwardDiff = "0.10, 1"
1219
LiveServer = "1"
1320
MarkdownAST = "0.1"
1421
julia = "1.10"

docs/api_reference.jl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ function generate_api_reference(src_dir::String)
3232
joinpath("Data", "abstract_hamiltonian_vector_field.jl"),
3333
joinpath("Data", "hamiltonian_vector_field.jl"),
3434
)),
35+
(mod=CTBase.Differentiation, title="Differentiation", filename="differentiation", files=src(
36+
joinpath("Differentiation", "Differentiation.jl"), joinpath("Differentiation", "default.jl"),
37+
joinpath("Differentiation", "abstract_ad_backend.jl"),
38+
joinpath("Differentiation", "differentiation_interface.jl"),
39+
joinpath("Differentiation", "building.jl"),
40+
)),
3541
(mod=CTBase.Descriptions, title="Descriptions", filename="descriptions", files=src(
3642
joinpath("Descriptions", "Descriptions.jl"), joinpath("Descriptions", "types.jl"),
3743
joinpath("Descriptions", "similarity.jl"), joinpath("Descriptions", "display.jl"),
@@ -106,6 +112,9 @@ function generate_api_reference(src_dir::String)
106112
internals_modules = Any[cfg.mod => cfg.files for cfg in modules_config]
107113

108114
for (sym, files) in [
115+
(:CTBaseDifferentiationInterface, ext(
116+
joinpath("CTBaseDifferentiationInterface.jl"),
117+
)),
109118
(:DocumenterReference, ext(
110119
joinpath("DocumenterReference", "DocumenterReference.jl"),
111120
joinpath("DocumenterReference", "config_helpers.jl"),

docs/make.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ with_api_reference(src_dir) do api_pages
8080
"Implementing a Strategy" => joinpath("guide", "implementing-a-strategy.md"),
8181
"Strategy Parameters" => joinpath("guide", "strategy-parameters.md"),
8282
"Orchestration & Routing" => joinpath("guide", "orchestration-and-routing.md"),
83+
"Differentiation" => joinpath("guide", "differentiation.md"),
8384
],
8485
"Developer Tools" => [
8586
"Test Runner" => joinpath("guide", "test-runner.md"),

docs/src/getting-started.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ Three things to keep in mind:
3131
CTBase.Exceptions.NotImplemented
3232
```
3333
2. **Submodule-first API.** The public API lives in named submodules
34-
(`Core`, `Descriptions`, `Exceptions`, `Traits`, `Data`, `DevTools`, `Unicode`, …).
34+
(`Core`, `Exceptions`, `Traits`, `Data`, `Descriptions`, `Options`, `Strategies`,
35+
`Orchestration`, `Differentiation`, `Interpolation`, `DevTools`, `Unicode`, …).
3536
You can bring a submodule's exports into scope explicitly:
3637
```julia
3738
using CTBase.Exceptions # brings IncorrectArgument, NotImplemented, … into scope
@@ -40,6 +41,8 @@ Three things to keep in mind:
4041
3. **Extension-backed features.** `run_tests`, `postprocess_coverage`, and
4142
`automatic_reference_documentation` require loading the matching weak dependency
4243
(`Test`, `Coverage`, `Documenter` respectively) before they become active.
44+
Likewise, the differentiation primitives of `CTBase.Differentiation` become active
45+
only once `DifferentiationInterface` and an AD package (e.g. `ForwardDiff`) are loaded.
4346

4447
## 5-Minute Walkthrough
4548

@@ -134,6 +137,10 @@ For more, see the **[Data guide](guide/data.md)**.
134137
| Compile-time traits and dispatch | [Traits](guide/traits.md) |
135138
| Trait-carrying vector fields and Hamiltonians | [Data](guide/data.md) |
136139
| Descriptions catalogue and completion | [Descriptions](guide/descriptions.md) |
140+
| Option schema, validation, and aliases | [Options System](guide/options-system.md) |
141+
| Strategy contract and registration | [Implementing a Strategy](guide/implementing-a-strategy.md) |
142+
| Routing options to strategies | [Orchestration & Routing](guide/orchestration-and-routing.md) |
143+
| AD backends and differentiation primitives | [Differentiation](guide/differentiation.md) |
137144
| Modular test runner setup | [Test Runner](guide/test-runner.md) |
138145
| Coverage report generation | [Coverage](guide/coverage.md) |
139146
| Auto-generated API reference | [API Documentation](guide/api-documentation.md) |

docs/src/guide/differentiation.md

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
# Differentiation: AD backend strategies
2+
3+
```@meta
4+
CurrentModule = CTBase
5+
```
6+
7+
The [`CTBase.Differentiation`](@ref CTBase.Differentiation) submodule provides an
8+
**automatic differentiation (AD) backend** abstraction built on the
9+
[Strategies](implementing-a-strategy.md) contract. It exposes a small set of
10+
differentiation primitives — gradients, derivatives, partial derivatives and
11+
Jacobian–vector products — behind a single strategy type, so that consumers
12+
(Hamiltonian systems, flows, differential geometry) never depend on a concrete
13+
AD package directly.
14+
15+
!!! tip "Prerequisites"
16+
Read the [Implementing a Strategy](@ref) and [Options System](@ref) guides
17+
first: an AD backend is a strategy with a single `:ad_backend` option.
18+
19+
```@setup diff
20+
using CTBase
21+
using CTBase.Differentiation
22+
using CTBase.Data
23+
using CTBase.Strategies
24+
# Loading DifferentiationInterface (+ an AD package) activates the extension
25+
# that provides the actual differentiation methods.
26+
using DifferentiationInterface
27+
using ForwardDiff
28+
import ADTypes
29+
```
30+
31+
## Overview
32+
33+
The module is organised around a two-level strategy contract:
34+
35+
| Level | Symbol | Role |
36+
|---|---|---|
37+
| Family | [`Differentiation.AbstractADBackend`](@ref CTBase.Differentiation.AbstractADBackend) | abstract strategy; defines the AD contract |
38+
| Concrete | [`Differentiation.DifferentiationInterface`](@ref CTBase.Differentiation.DifferentiationInterface) | wraps a [DifferentiationInterface.jl](https://github.com/JuliaDiff/DifferentiationInterface.jl) backend |
39+
40+
The concrete strategy stores a single option, `:ad_backend`, holding an
41+
[ADTypes.jl](https://github.com/SciML/ADTypes.jl) backend (default
42+
`AutoForwardDiff()`). `ADTypes` is a hard dependency of CTBase, so the default is
43+
always available.
44+
45+
The contract has seven methods.
46+
[`Differentiation.ad_backend`](@ref CTBase.Differentiation.ad_backend) — the accessor
47+
returning the wrapped ADTypes backend — is resolved in core and always available; the
48+
six differentiation primitives below live in an extension.
49+
50+
!!! note "The differentiation methods live in an extension"
51+
The contract methods (`gradient`, `derivative`, `differentiate`,
52+
`pushforward`, `hamiltonian_gradient`, `variable_gradient`) are implemented in
53+
the `CTBaseDifferentiationInterface` **package extension**. They become
54+
available only once `DifferentiationInterface` *and* a concrete AD package
55+
(e.g. `ForwardDiff`) are loaded. Without the extension, every contract method
56+
throws a [`CTBase.Exceptions.NotImplemented`](@ref) with a helpful message.
57+
58+
## Building a backend
59+
60+
Use [`Differentiation.build_ad_backend`](@ref CTBase.Differentiation.build_ad_backend)
61+
to construct a backend with the default AD type:
62+
63+
```@example diff
64+
backend = Differentiation.build_ad_backend()
65+
```
66+
67+
Pass an explicit ADTypes backend through the `ad_backend` option (the aliases
68+
`backend` and `ad` resolve to the same option):
69+
70+
```@example diff
71+
Differentiation.build_ad_backend(; ad_backend = ADTypes.AutoForwardDiff())
72+
```
73+
74+
The wrapped AD type is recovered with
75+
[`Differentiation.ad_backend`](@ref CTBase.Differentiation.ad_backend):
76+
77+
```@example diff
78+
Differentiation.ad_backend(backend)
79+
```
80+
81+
Because it is a regular strategy, its metadata is introspectable at the type level:
82+
83+
```@example diff
84+
Strategies.metadata(Differentiation.DifferentiationInterface)
85+
```
86+
87+
## Differentiation primitives
88+
89+
### Gradient and derivative
90+
91+
[`Differentiation.gradient`](@ref CTBase.Differentiation.gradient) differentiates a
92+
scalar function of a vector, and
93+
[`Differentiation.derivative`](@ref CTBase.Differentiation.derivative) a scalar
94+
function of a scalar:
95+
96+
```@example diff
97+
f(x) = sum(x .^ 2) # ∇f = 2x
98+
Differentiation.gradient(backend, f, [1.0, 2.0])
99+
```
100+
101+
```@example diff
102+
g(t) = t^3 # g'(t) = 3t²
103+
Differentiation.derivative(backend, g, 2.0)
104+
```
105+
106+
### Partial derivative at a slot
107+
108+
[`Differentiation.differentiate`](@ref CTBase.Differentiation.differentiate)
109+
computes the partial derivative of `f` with respect to the argument at a
110+
**compile-time slot** `Val(Slot)`, holding the other arguments fixed. The active
111+
argument comes first, the frozen ones follow in slot order:
112+
113+
```@example diff
114+
# H(x, p) = ½‖p‖² + ‖x‖² → ∂H/∂x = 2x, ∂H/∂p = p
115+
H(x, p) = 0.5 * sum(p .^ 2) + sum(x .^ 2)
116+
x = [1.0, 2.0]; p = [3.0, 4.0]
117+
118+
∂x = Differentiation.differentiate(backend, H, Val(1), x, p) # active = x (slot 1), const = p
119+
∂p = Differentiation.differentiate(backend, H, Val(2), p, x) # active = p (slot 2), const = x
120+
(∂x, ∂p)
121+
```
122+
123+
A scalar slot yields a scalar derivative:
124+
125+
```@example diff
126+
# f(t, x) = t * x[1] → ∂f/∂t = x[1]
127+
ft(t, x) = t * x[1]
128+
Differentiation.differentiate(backend, ft, Val(1), 2.0, [3.0, 4.0])
129+
```
130+
131+
### Pushforward (Jacobian–vector product)
132+
133+
[`Differentiation.pushforward`](@ref CTBase.Differentiation.pushforward) computes the
134+
directional derivative of `f` at `x` along `dx`, freezing the other arguments:
135+
136+
```@example diff
137+
A = [0.0 1.0; -1.0 0.0]
138+
X(x) = A * x # J_X = A → pushforward = A·dx
139+
Differentiation.pushforward(backend, X, Val(1), [1.0, 2.0], [5.0, 6.0])
140+
```
141+
142+
## Hamiltonian gradients
143+
144+
The two domain-specific methods operate directly on a
145+
[`Data.Hamiltonian`](@ref CTBase.Data.Hamiltonian) (see the [Data](data.md) guide).
146+
[`Differentiation.hamiltonian_gradient`](@ref CTBase.Differentiation.hamiltonian_gradient)
147+
returns `(∂H/∂x, ∂H/∂p)`, **non-negated** — the caller applies the signs of
148+
Hamilton's equations (`ẋ = ∂H/∂p`, `ṗ = -∂H/∂x`):
149+
150+
```@example diff
151+
# H(x, p) = ½(‖x‖² + ‖p‖²) → ∂H/∂x = x, ∂H/∂p = p
152+
h = Data.Hamiltonian((x, p) -> 0.5 * (sum(abs2, x) + sum(abs2, p)))
153+
∂x, ∂p = Differentiation.hamiltonian_gradient(backend, h, 0.0, [1.0, 2.0], [3.0, 4.0], nothing)
154+
(∂x, ∂p)
155+
```
156+
157+
For a non-fixed Hamiltonian,
158+
[`Differentiation.variable_gradient`](@ref CTBase.Differentiation.variable_gradient)
159+
returns `∂H/∂v`:
160+
161+
```@example diff
162+
# H(x, p, v) = ½(‖x‖² + ‖p‖² + ‖v‖²) → ∂H/∂v = v
163+
hv = Data.Hamiltonian((x, p, v) -> 0.5 * (sum(abs2, x) + sum(abs2, p) + sum(abs2, v)); is_variable=true)
164+
Differentiation.variable_gradient(backend, hv, 0.0, [1.0, 2.0], [3.0, 4.0], [5.0, 6.0])
165+
```
166+
167+
## The contract without the extension
168+
169+
Every contract method has a fallback on `AbstractADBackend` that throws
170+
[`CTBase.Exceptions.NotImplemented`](@ref) (see the [Exceptions](exceptions.md)
171+
guide). This is what users hit when the AD package is not loaded, and what custom
172+
backends must override:
173+
174+
```@repl diff
175+
struct MyBackend <: Differentiation.AbstractADBackend
176+
options::Strategies.StrategyOptions
177+
end
178+
try # hide
179+
Differentiation.gradient(MyBackend(Strategies.StrategyOptions()), x -> sum(x), [1.0])
180+
catch e # hide
181+
showerror(IOContext(stdout, :color => false), e) # hide
182+
end # hide
183+
```
184+
185+
## See also
186+
187+
- [`Differentiation.AbstractADBackend`](@ref CTBase.Differentiation.AbstractADBackend), [`Differentiation.DifferentiationInterface`](@ref CTBase.Differentiation.DifferentiationInterface) — the strategy types.
188+
- [Implementing a Strategy](@ref), [Options System](@ref) — the contract these build on.
189+
- [Data](data.md)`Hamiltonian` wrappers consumed by the gradient methods.
190+
- [Exceptions](exceptions.md)`NotImplemented`, raised by the contract fallbacks.

docs/src/index.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ It provides the **base layer** shared by all packages: common types, structured
2929
| [`CTBase.Traits`](@ref) | Compile-time trait types for time dependence, variable dependence, mutability, and dynamics dispatch |
3030
| [`CTBase.Data`](@ref) | Trait-carrying function wrappers: `VectorField`, `Hamiltonian`, `HamiltonianVectorField` |
3131
| [`CTBase.Descriptions`](@ref) | Symbolic description tuples: catalogue management, pattern completion, similarity search |
32+
| [`CTBase.Options`](@ref) | Generic option handling: provenance tracking, schema definition, validation, and aliases |
33+
| [`CTBase.Strategies`](@ref) | Strategy contract, registry, building/validation, and metadata for pluggable algorithmic components |
34+
| [`CTBase.Orchestration`](@ref) | Option routing and disambiguation between problem-level actions and strategies |
35+
| [`CTBase.Differentiation`](@ref) | AD-backend strategies for gradients, derivatives, partial derivatives, and Jacobian–vector products |
36+
| [`CTBase.Interpolation`](@ref) | Linear and piecewise-constant interpolation with flat extrapolation |
3237
| [`CTBase.DevTools`](@ref) | Developer tools with tag-based dispatch for `run_tests`, `postprocess_coverage`, and `automatic_reference_documentation` |
3338
| [`CTBase.Unicode`](@ref) | Unicode subscript/superscript helpers for display |
3439

@@ -39,6 +44,11 @@ It provides the **base layer** shared by all packages: common types, structured
3944
- **[Traits](guide/traits.md)** — compile-time trait types, the opt-in contract, and predicate functions.
4045
- **[Data](guide/data.md)** — trait-carrying wrappers for vector fields and Hamiltonians.
4146
- **[Descriptions](guide/descriptions.md)** — catalogue API, pattern matching, error handling.
47+
- **[Options System](guide/options-system.md)** — option schema, validation, aliases, and provenance.
48+
- **[Implementing a Strategy](guide/implementing-a-strategy.md)** — the strategy contract and how to add one.
49+
- **[Strategy Parameters](guide/strategy-parameters.md)** — declaring and resolving strategy options.
50+
- **[Orchestration & Routing](guide/orchestration-and-routing.md)** — routing options to strategies with disambiguation.
51+
- **[Differentiation](guide/differentiation.md)** — AD-backend strategies and differentiation primitives (extension-backed).
4252
- **[Test Runner](guide/test-runner.md)** — modular test infrastructure with `CTBase.DevTools.run_tests`.
4353
- **[Coverage](guide/coverage.md)** — post-processing coverage artifacts with `CTBase.postprocess_coverage`.
4454
- **[API Documentation](guide/api-documentation.md)** — auto-generating per-module API pages.

0 commit comments

Comments
 (0)