Skip to content
Open
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
11 changes: 11 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
## Stability migration

Stability uses SetProg families directly, or vectors with one family per node.
SetProg is a direct dependency; polynomial support is its optional SumOfSquares
extension. `QuadraticTemplate` forwards stability modeling to SetProg and keeps
the fitted quadratic functions for compatibility. The legacy quadratic models
remain for safety and optimal control, whose migration is deferred. SetProg
implements set variables and inclusion constraints. Certificate primitives live in `problems/gauges.jl`;
applicability, graph closures, and reindexing live in `problems/sets.jl`.
The legacy template architecture described below applies to deferred problems.

# CLAUDE.md

Operating guide for AI coding agents working in **PathCompleteCertificates.jl**. The developer
Expand Down
9 changes: 3 additions & 6 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,23 @@ JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
MathematicalSystems = "d14a8603-c872-5ed3-9ece-53e0e82e39da"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
SetProg = "39881422-4b75-5582-a5c7-0feb14562a65"

[compat]
HybridSystems = "0.4"
JuMP = "1.20"
LinearAlgebra = "1"
MathematicalSystems = "0.14"
Random = "1"
MultivariatePolynomials = "0.5"
RecipesBase = "1"
SetProg = "0.4.4"
SumOfSquares = "0.7, 0.8"
SumOfSquares = "0.8"
julia = "1.10"

[weakdeps]
MultivariatePolynomials = "102ac46a-7ee4-5c85-9060-abc95bfdeaa3"
RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
SetProg = "39881422-4b75-5582-a5c7-0feb14562a65"
SumOfSquares = "4b9e565b-77fc-50a5-a571-1244f986bda1"

[extensions]
PathCompleteCertificatesSumOfSquaresExt = "SumOfSquares"
PathCompleteCertificatesRecipesBaseExt = "RecipesBase"
PathCompleteCertificatesSetProgExt = "SetProg"
PathCompleteCertificatesSumOfSquaresExt = ["SumOfSquares", "MultivariatePolynomials"]
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,12 @@ Contributions are welcome. Please open an
[issue](https://github.com/dionysos-dev/PathCompleteCertificates.jl/issues) to report a bug or
discuss a feature, and see the Developer Docs for the setup, conventions and Git workflow.

One thing to read before writing code: the package is organised along **two independent axes**,
*template* (what the node functions are) and *problem* (what the edge inequality says), with the
graph cutting across both. A new template, problem, lift or refinement algorithm is one file in the
`catalogue/` of its directory under `src/`. The
[conventions](https://dionysos-dev.github.io/PathCompleteCertificates.jl/dev/developers/conventions/)
page explains why, and what goes wrong when the structure is ignored.
Stability certificates use [SetProg](https://github.com/blegat/SetProg.jl)
set families directly. SetProg owns their variables, inclusions and fitted gauges;
this package owns the systems, graphs, lifts and refinement algorithms.
Safety and optimal control retain their quadratic templates until their migration.
See the [conventions](https://dionysos-dev.github.io/PathCompleteCertificates.jl/dev/developers/conventions/)
for extending the package.

## References

Expand Down
6 changes: 3 additions & 3 deletions docs/Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# The examples are executed as part of the docs build, so this environment needs
# a solver, a plotting backend and the polynomial and set-programming stacks on
# top of the documentation tooling. None of them is a dependency of the package,
# nor of the environment CI instantiates for the tests.
# top of the documentation tooling. SetProg is a direct package dependency;
# SumOfSquares is optional, and solvers and plotting tools are for examples.
[deps]
Clarabel = "61c947e1-3e6d-4ee4-985a-eec8c727bd6e"
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
Expand All @@ -21,7 +21,7 @@ SumOfSquares = "4b9e565b-77fc-50a5-a571-1244f986bda1"
Documenter = "1"
DocumenterCitations = "1"
Literate = "2"
SetProg = "0.4.4"
SetProg = "0.4.5"
SumOfSquares = "0.7, 0.8"

[sources]
Expand Down
1 change: 1 addition & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import SetProg
using PathCompleteCertificates
using Documenter, Literate
import DocumenterCitations
Expand Down
10 changes: 5 additions & 5 deletions docs/src/bibliography.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ number quoted in the manual was read in the paper it is attributed to.

| | |
| :-- | :-- |
| [athanasopoulos2019polyhedral](@cite) | [`ParallelFacesPolyhedralTemplate`](@ref), fixed facets and solved-for weights, and the four partial lifts that are [`OutSplitLift`](@ref), [`InSplitLift`](@ref), [`ForwardProductLift`](@ref) and [`BackwardProductLift`](@ref) |
| [parrilo2008approximation](@cite) | [`SumOfSquaresTemplate`](@ref) |
| [debauche2024thesis](@cite) | [`DualCopositiveTemplate`](@ref), Def. 2.30 |
| [athanasopoulos2019polyhedral](@cite) | `ParallelFacesPolyhedralTemplate`, fixed facets and solved-for weights, and the four partial lifts that are [`OutSplitLift`](@ref), [`InSplitLift`](@ref), [`ForwardProductLift`](@ref) and [`BackwardProductLift`](@ref) |
| [parrilo2008approximation](@cite) | `SetProg.PolySet` |
| [debauche2024thesis](@cite) | `DualCopositiveTemplate`, Def. 2.30 |

[`QuadraticTemplate`](@ref) and [`LinearCopositiveTemplate`](@ref) are
standard. [`ConicPolyhedralTemplate`](@ref) is under submission and has no
`SetProg.Ellipsoid` and `LinearCopositiveTemplate` are
standard. `ConicPolyhedralTemplate` is under submission and has no
entry yet.

## Safety and optimal control
Expand Down
56 changes: 16 additions & 40 deletions docs/src/developers/conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,55 +16,31 @@ between problems:
| Safety | ``B_d(A_i x) \le B_s(x)`` |
| Optimal control | ``x^\top Q x + u^\top R u + V_d(A_i x + B_i u) \le V_s(x)`` |

Templates and problems are therefore two independent type hierarchies. The
**template**, `src/templates/`, says what the node functions are, and the
**problem**, `src/problems/`, says what the edge inequality is. A template
supplies primitives, a problem composes them, and neither names the other.
Stability set families live in SetProg. The problem builds SetProg variables,
normalizes their gauges, and models each edge by set inclusion. The fitted
node values are callable SetProg sets. PCC implements the certificate
primitives using SetProg's JuMP syntax:

```julia
# Template axis: one file per template, answering all of it.
add_function_variables!(model, template, dim, node) # the node function V_s, well defined
add_decrease!(model, template, V_src, V_dst, map; scale = 1)
# scale·V_src(x) − V_dst(map·x) ≥ 0
decrease_slack(template, V_src, V_dst, map; scale = 1) # the same, read off a solution; 0 when tight
add_normalization!(model, template, V) # excludes V ≡ 0
homogeneity_degree(template) # p: V(cx) = cᵖ V(x)
solution_value(template, V) # variables -> a callable node function
is_applicable(template, system) # default true; does the template apply at all?
is_closed_under(template, ::Operation, system) # default false; which lifts are valid
reindex(template, origins) # per-node data follows a refined graph
has_dual(template), dual(template), dual(template, V) # the dual norms, when they exist

# Problem axis: one file per problem, composing the above.
node_value(template, problem, V, x) # V(x), or V([x; 1]) for safety
optimization_model(template, graph, problem; optimizer, ...) # build, do not solve
certify(template, graph, problem; optimizer, ...) # build, solve, extract
best_certificate, objective, optimality_gap, edge_slacks # what refine reads

# The common function: each node value from the problem (node_value), their combination
# from the graph alone (graphs/observer.jl); the public method is in problems/certificate.jl.
common(template, graph, problem, Vs, x)
add_function_variables!(model, family, dimension, node)
add_normalization!(model, family, variable)
add_decrease!(model, family, source, destination, A; scale)
solution_value(family, variable)
homogeneity_degree(family)
decrease_slack(family, source, destination, A; scale)
```

Every primitive is public, since a user implements it. Problems express
their edge inequality through `add_decrease!`: quantifying an inequality over
every ``x`` needs a cone that depends only on the template, so stability's edge
condition is one call with `scale = γᵖ`. Optimal control is the exception. Its
inequality is convex only after the substitution ``S = P^{-1}``, ``Y = KS``,
so it cannot be written against `V_src` and `V_dst`; it works on the quadratic
template's variables directly, and a comment in the code says so. Each problem
writes its edge condition once, in a private `_edge_decrease` that both builds
the model and measures the slacks.

A template is an **instance**, never a type: `ParallelFacesPolyhedralTemplate` carries a
matrix per node, and `::Type{T}` has nowhere to put it. An `AbstractTemplate`
is the family; the fitted node function is what `solution_value` returns.
PCC owns the system and graph metadata in `src/problems/sets.jl`:
`is_applicable`, `is_closed_under`, `reindex`, `has_dual`, and `dual`.
Families are instances; a vector associates a family with each node.
Safety and optimal control retain the legacy quadratic template and its
primitives until their separate migration.

Each problem has its own certificate type. The shared fields live once in
`CertificateData`; what the problem certifies is a typed field of its own,
`rate`, `margin` or `gains`.

**A new template, problem, lift or refinement algorithm is one file in the
**A new problem, lift or refinement algorithm is one file in the
`catalogue/` of its directory, and touches nothing else.** Every directory with an
interface has that shape: the interface at the top, one file per inhabitant
under `catalogue/`, named after the type without the directory's suffix.
Expand Down
52 changes: 11 additions & 41 deletions docs/src/examples/set_prog.jl
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
# # Piecewise semi-ellipsoids: a template from SetProg
#
# [SetProg](https://github.com/blegat/SetProg.jl) writes optimisation problems
# over sets: ellipsoids, sublevel sets of polynomials, polytopes, and sets that
# are one of these on each cone of a partition. [`SetProgTemplate`](@ref)
# makes such a set the node function of a node, the function whose unit
# sublevel set it is, and the stability inequality
# ``V_d(A_i x) \le \gamma^p V_s(x)`` becomes the inclusion
# ``(A_i / \gamma)\, S_s \subseteq S_d``, which SetProg imposes. Three of its
# sets give templates this package already has, and the example checks that
# they certify the same rates; the fourth, the piecewise semi-ellipsoid, does
# not.
# SetProg families are accepted directly by PCC. Their stability inequality
# is modeled as the inclusion `(A / rate) * S_source ⊆ S_destination`.
# This example compares polynomial and piecewise set families.

import PathCompleteCertificates as PCC
import Clarabel, HiGHS, JuMP
Expand Down Expand Up @@ -43,38 +36,16 @@ polygon(normals) = polyhedron(hrep([HalfSpace(a, norm(a)) for a in normals]), LI

octagon = polygon([[2, 1], [1, 2], [-1, 2], [-2, 1], [-2, -1], [-1, -2], [1, -2], [2, -1]])

# ## Three sets that are templates already
#
# An ellipsoid is a quadratic form, the sublevel set of a polynomial is a sum
# of squares, and a polytope that is linear on each cone of a partition is the
# free-facet polyhedral template on the same cones; the cones over the facets
# of this octagon are those of [`planar_conic_partition`](@ref)`(1)`. Each
# pair certifies the same rate.
# ## Three homogeneous set families

using SumOfSquares
@polyvar x[1:2]

pairs = [
(
"quadratic",
PCC.QuadraticTemplate(),
PCC.SetProgTemplate(Ellipsoid(; symmetric = true)),
),
(
"sum of squares, degree 4",
PCC.SumOfSquaresTemplate(2, x),
PCC.SetProgTemplate(PolySet(; symmetric = true, degree = 4, variables = x)),
),
(
"free facets, 4 cones",
PCC.ConicPolyhedralTemplate([PCC.planar_conic_partition(1)]),
PCC.SetProgTemplate(Polytope(; symmetric = true, piecewise = octagon)),
),
]

[
(name, round(bound(ours).rate; digits = 4), round(bound(theirs).rate; digits = 4)) for
(name, ours, theirs) in pairs
families = [
("quadratic", PCC.QuadraticTemplate()),
("degree 4", PCC.SumOfSquaresTemplate(2, x)),
("polyhedral", Polytope(; symmetric = true, piecewise = octagon)),
]
[(name, round(bound(family).rate; digits = 4)) for (name, family) in families]

# ## The piecewise semi-ellipsoid
#
Expand Down Expand Up @@ -106,8 +77,7 @@ sixteen = polygon([
partitions = [("square", square), ("octagon", octagon), ("16-gon", sixteen)]

piecewise = [
(name, bound(PCC.SetProgTemplate(Ellipsoid(; symmetric = true, piecewise = P)))) for
(name, P) in partitions
(name, bound(Ellipsoid(; symmetric = true, piecewise = P))) for (name, P) in partitions
]

[
Expand Down
6 changes: 3 additions & 3 deletions docs/src/examples/templates_and_graphs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,9 @@ plot(left, right; layout = (1, 2), size = (1100, 620))
# ## Sums of squares
#
# [`SumOfSquaresTemplate`](@ref) takes polynomial node functions of higher
# degree ([parrilo2008approximation](@cite)). It is defined in a package
# extension loaded with SumOfSquares, and the polynomial variables are created
# once and shared by every node. Degree 2 is the quadratic template. The table
# degree ([parrilo2008approximation](@cite)). Load SumOfSquares before
# constructing it. The polynomial variables are created once and shared by
# every node. Degree 2 is the quadratic template. The table
# gives the rates of degrees 2 and 4 on one node and on `de_bruijn(1, 2)`.

@polyvar x[1:2]
Expand Down
10 changes: 5 additions & 5 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,14 @@ In reading order:

Safety and optimal control have one example each,
[Safety: a barrier certificate](@ref) and
[Optimal control: a feedback per node](@ref), and so does the SetProg
template, [Piecewise semi-ellipsoids: a template from SetProg](@ref).
[Optimal control: a feedback per node](@ref), and so do SetProg
piecewise families, [Piecewise semi-ellipsoids: a template from SetProg](@ref).

## Scope

Stability is implemented for every template, safety and optimal control for
the quadratic one. [`compare`](@ref) is stated for stability, as in the
literature, and so is [`refine`](@ref);
Stability uses SetProg families directly. Safety and optimal control retain
the legacy quadratic template until their migration. [`compare`](@ref) is stated
for stability, as in the literature, and [`refine`](@ref) runs on stability;
[What works with what](@ref) gives the full table. A bound is always an upper
bound, and a negative answer means that no certificate was found in this
template on this graph, not that the property fails.
Expand Down
2 changes: 1 addition & 1 deletion docs/src/manual/comparing.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ finds the order. The products give graphs with words on their edges, which

¹ The linear copositive norms are not closed under `min`, yet the min lift is
valid for them on positive systems ([debauche2024thesis](@cite), Thm. 7.43):
[`is_valid`](@ref) reads it through their dual, [`DualCopositiveTemplate`](@ref),
[`is_valid`](@ref) reads it through their dual, `DualCopositiveTemplate`,
closed under `max`.

² Sufficient only: [jongeneel2025ordering](@cite), Ex. IV.3, exhibits two
Expand Down
69 changes: 18 additions & 51 deletions docs/src/manual/support.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,54 +4,21 @@ CurrentModule = PathCompleteCertificates

# What works with what

Stability is implemented for all seven templates, safety and optimal control
for the quadratic one.

| | [`StabilityProblem`](@ref) | [`SafetyProblem`](@ref) | [`OptimalControlProblem`](@ref) |
| :----------------------------- | :------------------------: | :---------------------: | :-----------------------------: |
| [`QuadraticTemplate`](@ref) | ✓ | ✓ | ✓ |
| [`LinearCopositiveTemplate`](@ref) | ✓ ¹ | ✗ | ✗ |
| [`DualCopositiveTemplate`](@ref) | ✓ ¹ | ✗ | ✗ |
| [`ParallelFacesPolyhedralTemplate`](@ref) | ✓ | ✗ | ✗ |
| [`ConicPolyhedralTemplate`](@ref) | ✓ | ✗ | ✗ |
| [`SumOfSquaresTemplate`](@ref) | ✓ ² | ✗ | ✗ |
| [`SetProgTemplate`](@ref) | ✓ ² | ✗ | ✗ |

¹ Positive systems only: entrywise nonnegative mode matrices.

² Behind a package extension: `using SumOfSquares` first, or `using SetProg`
for [`SetProgTemplate`](@ref).

A ✗ throws an `ArgumentError` before any model is built. Optimal control needs
a **complete** graph, not merely a path-complete one. Stability accepts a
[`WordGraph`](@ref); safety and optimal control need one mode per edge.

## Comparing and refining

[`compare`](@ref) is stated for stability, as in the literature; which of its
results hold for a template depends on the template's closure properties.
[`refine`](@ref) runs on stability only, as in the literature, for every template that
implements [`decrease_slack`](@ref), from which the edge slacks are computed:

| | [`refine`](@ref) | lifts valid for [`compare`](@ref) |
| :-- | :-: | :-- |
| [`QuadraticTemplate`](@ref) | ✓ | identity, sum; composition if invertible |
| [`LinearCopositiveTemplate`](@ref) | ✓ ¹ | identity, sum, min without closure; composition if nonnegative with no zero column |
| [`DualCopositiveTemplate`](@ref) | ✓ ¹ | identity, max; sum through its dual |
| [`ParallelFacesPolyhedralTemplate`](@ref) | ✓ | identity; max when the node matrices coincide |
| [`ConicPolyhedralTemplate`](@ref) | ✓ | identity |
| [`SumOfSquaresTemplate`](@ref) | ✗ ³ | identity, sum; composition if invertible |
| [`SetProgTemplate`](@ref) | ✗ ³ | identity; sum when the nodes share the set variable |

³ No `decrease_slack` yet, so the loop cannot read where these templates are
tight.

## The gaps

Safety with the polyhedral templates is possible by the same construction in
homogeneous coordinates; it is not implemented. Optimal
control is tied to the quadratic template by the mathematics, the substitution
``S = P^{-1}``, ``Y = KS`` that makes it convex ([ninite2026path](@cite)). A
new template that implements the template primitives works with stability;
safety and optimal control need further primitives; see the
[developer conventions](@ref "Conventions").
Stability accepts SetProg families directly. Safety and optimal control retain
[`QuadraticTemplate`](@ref); their SetProg migration is deferred.

| Family | Stability | Refinement |
| :-- | :-: | :-: |
| symmetric ellipsoid | ✓ | ✓ |
| positive simplex or box | ✓ on positive systems | ✓ |
| parallelotope or conic polytope | ✓ | ✓ |
| polynomial set | ✓ with `using SumOfSquares` | |
| piecewise ellipsoid | ✓ | |
| piecewise polytope | ✓ | ✓ |

Refinement requires [`decrease_slack`](@ref). Fitted polynomial and piecewise-quadratic
slacks need a separate optimization and are not implemented.
See [Templates](@ref) for closures governing [`compare`](@ref).

Stability accepts [`WordGraph`](@ref). Safety and optimal control require one
mode per edge; optimal control requires a complete graph.
Loading
Loading