https://github.com/leanprover/sos
Harrison's sum-of-squares decision procedure for nonlinear real arithmetic in Lean 4
https://github.com/leanprover/sos
formal-verification lean4 nonlinear-real-arithmetic semidefinite-programming sum-of-squares theorem-proving
Last synced: 16 days ago
JSON representation
Harrison's sum-of-squares decision procedure for nonlinear real arithmetic in Lean 4
- Host: GitHub
- URL: https://github.com/leanprover/sos
- Owner: leanprover
- License: apache-2.0
- Created: 2026-05-08T13:36:52.000Z (24 days ago)
- Default Branch: main
- Last Pushed: 2026-05-15T02:44:23.000Z (18 days ago)
- Last Synced: 2026-05-15T04:39:08.685Z (17 days ago)
- Topics: formal-verification, lean4, nonlinear-real-arithmetic, semidefinite-programming, sum-of-squares, theorem-proving
- Language: Lean
- Size: 396 KB
- Stars: 1
- Watchers: 0
- Forks: 0
- Open Issues: 9
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
Awesome Lists containing this project
README
# sos
[](https://github.com/leanprover/sos/actions/workflows/ci.yml)
Harrison's sum-of-squares decision procedure for nonlinear real
arithmetic, in Lean 4. Based on the design from
[Harrison 2007 (TPHOLs)](https://link.springer.com/chapter/10.1007/978-3-540-74591-4_9).
## Adding to your project
Add the following to your `lakefile.toml`:
```toml
[[require]]
name = "sos"
git = "https://github.com/leanprover/sos.git"
rev = "main"
```
Or, in a `lakefile.lean`:
```lean
require sos from git
"https://github.com/leanprover/sos.git" @ "main"
```
Then `import SOS` and use `by sos`. The system BLAS/LAPACK runtime must
be installed; see [Native dependency troubleshooting](#native-dependency-troubleshooting).
## Status
The `by sos` tactic closes nonlinear-real-arithmetic goals end-to-end:
reify the goal, encode an SDP, call CSDP, round the float Gram matrix
to rationals, decompose via LDLᵀ + Lagrange four-square, and dispatch
the matching verifier-soundness lemma.
## Examples
A curated version of this section lives in
[`SOSTest/Showcase.lean`](SOSTest/Showcase.lean), so `lake test`
checks that these public examples continue to elaborate.
```lean
import SOS
-- Cauchy–Schwarz (rank 1, deg 4, 4 vars)
example (a b c d : ℝ) :
0 ≤ (a^2 + b^2) * (c^2 + d^2) - (a*c + b*d)^2 := by sos
-- Cyclic Schur, 3 vars
example (a b c : ℝ) : 0 ≤ a^2 + b^2 + c^2 - a*b - b*c - a*c := by sos
-- AM ≥ GM squared
example (x y : ℝ) : 0 ≤ (x^2 + y^2)^2 - 4*x^2*y^2 := by sos
-- Strict positivity, multivariate
example (x y : ℝ) : 0 < x^2 + y^2 + 1 := by sos
-- Infeasibility
example (x : ℝ) : ¬ (x^4 + 1 ≤ 0) := by sos
-- Constrained
example (x : ℝ) (_h : 0 ≤ x) : 0 ≤ x^3 + x := by sos
example (x y : ℝ) (_hx : 0 ≤ x) (_hy : 0 ≤ y) :
0 ≤ x^2 + 2*x*y + y^2 := by sos
-- Strict-inequality hypothesis (promoted to `0 ≤ x` via `le_of_lt`)
example (x : ℝ) (_h : 0 < x) : 0 ≤ x^3 + x := by sos
-- Equality constraint: discriminant of a real-rooted quadratic
example (a b c x : ℝ) (_h : a*x^2 + b*x + c = 0) :
0 ≤ b^2 - 4*a*c := by sos
-- Discrete goal, lifted/refuted through ℝ
example : ∀ n : ℕ, n ≤ n * n := by sos
-- Euclidean division over ℕ
example : ∀ a b : ℕ, b ≠ 0 → a = b * (a / b) + a % b := by sos
```
`by sos?` reports the witness it found as a `Try this:` suggestion
that you can paste back as a `sos_witness …` invocation, freezing the
proof so it no longer depends on calling CSDP at compile time:
```lean
example (x : ℝ) : 0 ≤ x^2 + 1 := by sos?
-- Try this:
-- [apply] sos_witness
-- { sigma0 := { squares := [CMvPolynomial.C (1 : ℚ), CMvPolynomial.X 0] }, sigmas := [] }
```
Atoms are recovered as arbitrary `ℝ`-typed subterms (free variables,
function applications, projections — anything the reifier doesn't
recognise as a known operator). Constraint hypotheses can come from
the local context or from `→`-introduced binders.
The Motzkin polynomial `x⁴y² + x²y⁴ + 1 - 3x²y²` is non-negative but
not a sum of squares (Hilbert 1888 / Motzkin 1967); `by sos`
correctly fails to find a certificate, caught here by
`fail_if_success`:
```lean
example : True := by
fail_if_success
(have : ∀ x y : ℝ,
0 ≤ x^4 * y^2 + x^2 * y^4 + 1 - 3*x^2*y^2 := by sos)
trivial
```
The soundness lemmas reduce to `IsSumSq.nonneg` (Mathlib) once the
goal has been transported through the `aeval` ring-hom on CompPoly's
`CMvPolynomial n ℚ`. Two design points worth flagging, both following
Harrison's [TPHOLs 2007 paper]
(https://link.springer.com/chapter/10.1007/978-3-540-74591-4_9):
- **`min tr(X)` cost matrix.** CSDP's interior-point step has no
preferred direction on a singleton boundary feasible set, so we
give it the trace objective Harrison reports works empirically.
- **Zero-pivot LDLᵀ.** Rank-deficient SOS Grams (`(x + y)²` has
Gram `[[1,1],[1,1]]`, rank 1) require the "completing the square"
routine to accept a zero pivot when the residual column is also
zero. Our `SOS.LDL.decompose` does this; `LDL.reconstruct` already
drops the zero-D contributions.
## Implementation notes
This tactic depends on
[`Verified-zkEVM/CompPoly`](https://github.com/Verified-zkEVM/CompPoly)
for a kernel-decidable computational multivariate-polynomial type
(`CMvPolynomial n ℚ`), which is what the verifier reduces certificate
checks against. CompPoly itself depends on Mathlib. As a consequence,
`sos` is a downstream library and cannot be migrated into Mathlib
itself unless CompPoly is upstreamed first.
## Scope and limits
- **Rational certificates only.** Witnesses live in
`CMvPolynomial n ℚ` throughout. CSDP returns floats, which we round
against a denominator schedule (small ints, then powers of two up to
`2^20`) and decompose via rational LDLᵀ + Lagrange four-square.
There is no support for algebraic-extension coefficients — a goal
whose only SOS witness involves `√2` (or any other irrational) is
out of reach by construction.
- **Putinar form, with hypotheses as multipliers.** A certificate is
`target = σ₀ + Σᵢ σᵢ · gᵢ` with each `σᵢ` an SOS, where the `gᵢ`
are non-negativity hypotheses pulled from `intro`-binders and the
local context. Recognised constraint shapes are `0 ≤ g`, `g ≤ 0`
(encoded as `0 ≤ −g`), and `0 < g` (used via `le_of_lt`).
Unconstrained goals reduce to `target = σ₀`. Strict positivity
`0 < p` is handled by an LP-slack maximisation: one extra
decision variable `λ ≥ 0` enters the SDP via the constant-monomial
equality, CSDP maximises it to discover the largest admissible
slack `λ*`, and then `ε = 2^-k` near `λ*` is fed to the standard
feasibility pipeline to produce the verifiable certificate.
Infeasibility uses `target = −1`.
- **Single fixed relaxation level.** Multiplier basis sizes are set
once from a degree bound `D = max(deg(target), maxᵢ deg(gᵢ))`: the
σ₀ basis is monomials up to `⌈D/2⌉`, and each σᵢ basis is monomials
up to `⌈max(0, D − deg(gᵢ))/2⌉`. There is no hierarchy walk that
bumps the relaxation order on failure. Failures cover both
genuinely non-SOS non-negative polynomials (Motzkin
`x⁴y² + x²y⁴ + 1 − 3x²y²` is the canonical example) and goals that
would only succeed at a larger relaxation order than this fixed
search uses.
- **Search failure is not a soundness failure.** When CSDP returns an
unusable status, when no rounding denominator validates, or when
LDLᵀ / four-square reconstruction can't close the certificate,
`by sos` reports "no certificate found" and leaves the goal open.
The `Certificate.checks` predicate that closes the goal is
`decide +kernel`-checked against `Certificate n` data, so a proof
that goes through is independent of CSDP correctness.
- **The four-squares cap is the practical floor on `ε`.** Strict
positivity bounds smaller than `1/2^20 ≈ 10^-6` are out of reach
because `fourSquaresNat` brute-forces a Lagrange decomposition with
a cap at `n ≤ 2^20`. Lifting this requires a smarter four-squares
algorithm (e.g. Cornacchia / Pollard-style randomised search),
which is independent work.
## Building and testing
This repository is pinned to the Lean version in
[`lean-toolchain`](lean-toolchain); dependencies are pinned by
[`lake-manifest.json`](lake-manifest.json).
```
git clone https://github.com/leanprover/sos
cd sos
lake exe cache get
lake test
```
`lake test` elaborates `SOSTest`, which runs `by sos` against every
example in [`SOSTest/Examples.lean`](SOSTest/Examples.lean) — each
invocation calls CSDP, rounds the Gram matrix, reconstructs the
certificate, and checks it. A passing `lake test` is end-to-end
verification of the search/round/reconstruct/verify pipeline.
### Native dependency troubleshooting
The tactic calls CSDP through `csdp-ffi`, so BLAS/LAPACK must be
available before Lean can load the native solver. If `lake build` or
`lake test` fails while compiling or linking native code, run:
```
(cd .lake/packages/CSDP && lake script run checkNativeDeps)
```
That preflight reports the platform-specific packages or SDK paths
expected by the native build. For examples that invoke CSDP, prefer
Lake targets such as `lake test`; running a file directly with
`lake env lean SomeFile.lean` may bypass the native link setup needed
for the solver.
## Architecture
The tactic runs three stages on a `by sos` goal:
1. `SOS.Reify.parseGoalAtomic` walks the goal expression, collecting
atomic ℝ-typed subterms into an array and producing untyped
`SOS.Poly.Raw` ASTs for the conclusion and each constraint
hypothesis (drawn from `intro`-binders and the local context).
2. `SOS.Search.runSearch` builds the Putinar-form SDP, calls CSDP,
rounds the float Gram matrix to rationals, runs LDLᵀ, and
reconstructs squares — yielding a validated `SOS.Certificate n`.
3. `SOS.Tactic.closeSos` consumes the certificate and discharges the
real-arithmetic goal via the matching soundness lemma in
`SOS.Verifier`.
| Module | What it provides |
|---|---|
| `SOS.Raw` | `Poly.Raw` and typed `Poly n` ASTs + reflection theorem. |
| `SOS.Certificate` | `Goal n`, `SOSDecomp`, `Certificate n`, `checks` predicate. |
| `SOS.Verifier` | `sos_sound`, `sos_strict_sound`, `sos_infeasible_sound`, plus `aeval_*` and `evalReal_eq_aeval` bridge lemmas. |
| `SOS.LDL` | Rational LDLᵀ, Lagrange 4-square, Gram→SOS reconstruction. |
| `SOS.Search` | Putinar-form SDP encoding, CSDP integration, rounding loop, LP-slack strict positivity. |
| `SOS.Reify` | Atom-collecting Lean-`Expr` walker → `ParsedGoal` (atom array, untyped `SOS.Poly.Raw` for conclusion + constraints, hypothesis FVars). |
| `SOS.Tactic` | `by sos` (search-driven) and `by sos_witness ` elaborators. |
| `SOSTest.Examples` | Worked examples invoking the tactic; serves as the `lake test` driver. |
| `SOSTest.Showcase` | Curated launch/demo examples that are also covered by `lake test`. |
## Dependencies
| Package | Purpose |
|---|---|
| [`leanprover-community/mathlib4`](https://github.com/leanprover-community/mathlib4) | `IsSumSq.nonneg`, `ℝ`, `algebraMap ℚ ℝ`, `ring`, `push_cast`. |
| [`Verified-zkEVM/CompPoly`](https://github.com/Verified-zkEVM/CompPoly) | Computational `CMvPolynomial n R` substrate; sorry/axiom-free. |
| [`leanprover/csdp-ffi`](https://github.com/leanprover/csdp-ffi) | FFI wrapper around CSDP 6.2.0. Vendored CSDP source. |
System dependencies (BLAS/LAPACK, transitively via csdp-ffi):
| Platform | Packages |
|----------|---|
| Linux | `liblapack-dev libblas-dev gfortran` |
| macOS | Apple Command Line Tools (Accelerate framework) |
| Windows | MSYS2 mingw-w64 with `mingw-w64-x86_64-openblas` |
CI runs Linux-only.
## Contributing
See [`CONTRIBUTING.md`](CONTRIBUTING.md) for development workflow and
test expectations.
## Licence
Apache License 2.0 (see [LICENSE](LICENSE)). Transitively, CSDP is
distributed under the
[Eclipse Public License 1.0](https://github.com/coin-or/Csdp/blob/master/LICENSE).
## References
- John Harrison, "Verifying Nonlinear Real Formulas Via Sums of Squares" (TPHOLs 2007).
- Pablo Parrilo, *Structured Semidefinite Programs and Semialgebraic Geometry* (Caltech PhD, 2000).
- Helena Peyrl & Pablo Parrilo, "Computing sum of squares decompositions with rational coefficients" (Theor. Comput. Sci. 2008).
- Brian Borchers, [CSDP](https://github.com/coin-or/Csdp).
- [Coq Micromega `psatz`](https://coq.inria.fr/refman/addendum/micromega.html) — design template.