/- Copyright (c) 2026 Kim Morrison. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. `by sos` showcase — the primary entry point for evaluating this package. The tactic discharges polynomial (in)equality goals over ℝ (and ℤ / ℚ / ℕ, lifted automatically) by finding a Positivstellensatz certificate: it shells out to CSDP, rounds the floating-point Gram matrix to ℚ, then verifies the resulting sum-of-squares identity inside the kernel. Layout: * §1–§7 walk through the supported fragment by capability, starting with one-line positivity goals and building up through constrained, strict, equality-hypothesis, infeasibility, and ℕ/ℤ/ℚ-lifted forms. * §8 demonstrates the `sos?` → `sos_witness` workflow for inspecting and pinning certificates. * §9 covers graceful failure (Motzkin, infimum-0 strict positivity) and the out-of-scope-input error messages. The full Harrison `Examples/sos.ml` port (including known limitations) lives in `SOSTest.Harrison`. Internal-helper invariant checks live in `SOSTest.Internal`. Exact-rational simplex tests live in `SOSTest.RatSimplexTests`. Speed contract: this file builds in under 60s wall-clock. -/ import SOS open SOS CPoly /-! ## §1. Positivity over ℝ -/ example (x : ℝ) : 0 ≤ x^2 + 1 := by sos example (x : ℝ) : 0 ≤ x^4 + 1 := by sos -- Perfect squares, single-variable and multivariate. example (x : ℝ) : 0 ≤ x^2 + 2*x + 1 := by sos example (x y : ℝ) : 0 ≤ x^2 - 2*x*y + y^2 := by sos example (x y : ℝ) : 0 ≤ x^2 + 2*x*y + y^2 := by sos example (x : ℝ) : 0 ≤ x^4 - 2*x^2 + 1 := by sos -- Explicit pure-SOS surface: no hypotheses may contribute constraints. example (x y : ℝ) : 0 ≤ x^2 - 2*x*y + y^2 := by pure_sos /-- error: pure_sos: constraint hypotheses are not allowed -/ #guard_msgs in example (x : ℝ) (_h : 0 ≤ x) : 0 ≤ x^2 := by pure_sos -- Cyclic Schur, 3 variables. example (a b c : ℝ) : 0 ≤ a^2 + b^2 + c^2 - a*b - b*c - a*c := by sos -- AM ≥ GM squared, 2 variables, degree 4. example (x y : ℝ) : 0 ≤ (x^2 + y^2)^2 - 4*x^2*y^2 := by sos -- Cauchy–Schwarz: `(a²+b²)(c²+d²) ≥ (ac+bd)²` — rank-1, degree-4, 4 variables. example (a b c d : ℝ) : 0 ≤ (a^2 + b^2) * (c^2 + d^2) - (a*c + b*d)^2 := by sos /-! ## §2. General inequalities (`a ≤ b` / `a < b` form) `by sos` reifies arbitrary (in)equality conclusions, not just the `0 ≤ p` normal form — no manual rewrite required. -/ example (x : ℝ) : x ≤ x^2 + x + 1 := by sos example (x : ℝ) : x < x^2 + x + 2 := by sos example (x : ℝ) : -(x^2 + 1) ≤ 0 := by sos /-! ## §3. Strict positivity The strict-inequality path discovers a Putinar slack `λ*` via an LP solve and descends through `ε = 2^-k` from there. Including `polyDenom target` in the rounding schedule lets residuals with non-power-of-two denominators land on the natural rational grid. -/ example (x : ℝ) : 0 < x^2 + 1 := by sos example (x : ℝ) : 0 < x^4 + 1 := by sos example (x y : ℝ) : 0 < x^2 + y^2 + 1 := by sos -- Non-power-of-two denominator: the residual ends up at denom 3200 -- after ε = 1/128 against `1/100`, requiring polyDenom-aware rounding. example (x : ℝ) : 0 < x^2 + 1/100 := by sos -- Multivariate, non-power-of-two denominator. example (x y : ℝ) : 0 < x^2 + y^2 + 1/500 := by sos -- Small strict-positivity slack: `1/2^20`. Historically this was the -- tightest the search could close because of an old `fourSquaresNat` -- decomposition cap; now the limiting factor is CSDP's float rounding, -- not the rationaliser. example (x : ℝ) : 0 < x^2 + 1/1048576 := by sos /-! ## §4. Constrained goals (Putinar quadratic module) -/ example (x : ℝ) (_h : 0 ≤ x) : 0 ≤ x^3 + x := by sos example (x : ℝ) (_h : 0 ≤ x) : 0 ≤ x^2 - x + 1/4 := by sos example (x y : ℝ) (_hx : 0 ≤ x) (_hy : 0 ≤ y) : 0 ≤ x^2 + 2*x*y + y^2 := by sos -- Strict-inequality constraint hypothesis: promoted to `0 ≤` in the -- elaborator via `le_of_lt`. example (x : ℝ) (_h : 0 < x) : 0 ≤ x^3 + x := by sos -- Boundary-tight strict goal (issue #46). `runStrict`'s LP-slack pass -- correctly fails — `x² → 0` as `x → 0⁺`, so no uniform `ε > 0` -- admits `x² ≥ ε` for all `x > 0`. Closed via the strict-product -- Positivstellensatz fallback: `pol = x`, `i = 2` gives -- `−x² = 1 · (−x²)` against augmented `[x, −x²]`, with `x² > 0` -- recovered structurally from `0 < x` via `mul_pos`. example (x : ℝ) (_h : 0 < x) : 0 < x^2 := by sos -- Nonpos hypothesis (`h : x ≤ 0`), driving the `.neg`-wrapping in -- `recogniseConstraint` and the `aeval_nonneg_of_orig_neg` bridge in -- `SOS/Verifier.lean`. example (x : ℝ) (_h : x ≤ 0) : 0 ≤ -x := by sos -- General `a ≤ b` / `a < b` hypotheses with non-zero literals: the -- reifier converts `h : a ≤ b` to `0 ≤ b − a` via `sub_nonneg.mpr` -- and `h : a < b` to `0 < b − a` via `sub_pos.mpr` (see issue #49). example (x : ℝ) (_h : x ≤ 1) : 0 ≤ 1 - x := by sos example (x : ℝ) (_h : 1 ≤ x) : 0 ≤ x - 1 := by sos example (x : ℝ) (_h : -1 ≤ x) : 0 ≤ x + 1 := by sos example (r t : ℝ) (_h1 : -1 ≤ t) (_h2 : t ≤ 1) : 0 ≤ 1 + r^2 - 2*r*t := by sos example (x y : ℝ) (_hx : 1 ≤ x) (_hy : 1 ≤ y) : 0 ≤ x*y - (x + y - 1) := by sos example (x : ℝ) (_h : x < 1) : 0 ≤ 1 - x := by sos -- Strict variable-vs-variable form: `h : x < y → 0 ≤ y − x`. example (x y : ℝ) (_h : x < y) : 0 ≤ y - x := by sos -- Affine strict-strict goals are discharged by an exact rational LP -- fast path, avoiding the SDP search entirely (issue #50). example (x a : ℝ) (_h1 : 3*x + 7*a < 4) (_h2 : 3 < 2*x) : a < 0 := by sos /-! ## §5. Equality hypotheses The certificate gains a free polynomial cofactor `qⱼ` per equality `pⱼ = 0`. The verified identity becomes `target = σ₀ + Σᵢ σᵢ · gᵢ + Σⱼ qⱼ · pⱼ`. The reifier maps `a = b` to `pⱼ := a − b`; downstream the cofactor search is free to discover any sign for `qⱼ`. -/ -- From `x*y = 1` conclude `0 ≤ x*y − 1`. Cofactor `q := 1`. example (x y : ℝ) (_h : x*y = 1) : 0 ≤ x*y - 1 := by sos -- Degree-1 cofactor: `x = 1 → 0 ≤ x² − 1`. Search must discover -- `q := x + 1`. Load-bearing: the conclusion is false at `x := 0` -- without the equality. example (x : ℝ) (_h : x = 1) : 0 ≤ x^2 - 1 := by sos -- Strict positivity with equality, exercising `runStrict`'s cofactor -- path (both the λ-solve and the feasibility re-solve include cofactor -- blocks). Load-bearing: `0 < x²` is false at `x := 0`. example (x : ℝ) (_h : x = 1) : 0 < x^2 := by sos /-! ## §6. Infeasibility (`¬ p ≤ 0` conclusions) -/ example (x : ℝ) : ¬ (x^2 + 1 ≤ 0) := by sos example (x : ℝ) : ¬ (x^4 + 1 ≤ 0) := by sos /-! ## §7. Lifting ℕ / ℤ / ℚ goals to ℝ The lift pre-pass in `SOS/Lift.lean` runs before `parseGoalAtomic`. It intros leading ℕ / ℤ / ℚ / ℝ universal binders, splits equality conclusions via `le_antisymm`, rewrites ℕ / ℤ strict inequalities via `lt_iff_add_one_le`, applies the cast bridge (`Nat.cast_le.mp`, etc.) on the conclusion, runs `rify at *` to lift hypotheses, and adds a `0 ≤ (↑a : ℝ)` hypothesis for every ℕ-typed cast atom now in the goal. The user-visible tactic name does not change — `by sos` auto-dispatches on the (in)equality type. Goals already over ℝ pay no overhead. -/ -- ℤ: `(a − b)² ≥ 0`. example (a b : ℤ) : 2*a*b ≤ a^2 + b^2 := by sos -- ℤ Schur: `(a−b)² + (b−c)² + (a−c)² ≥ 0` divided by two. example (a b c : ℤ) : a*b + b*c + a*c ≤ a^2 + b^2 + c^2 := by sos -- ℚ strict: routed through `Rat.cast_lt.mp` to the ℝ strict-positivity path. example (x : ℚ) : 0 < x^2 + 1 := by sos -- ℚ: `(x² − y²)² ≥ 0`. example (x y : ℚ) : 4*x^2*y^2 ≤ (x^2 + y^2)^2 := by sos -- Mixed ℕ + ℝ — ℕ binder lifted, ℝ atom preserved. example : ∀ n : ℕ, ∀ x : ℝ, 0 ≤ x^2 + n := by sos -- ℕ-cast atom appears only in a hypothesis (conclusion is over ℝ with -- no ℕ casts). The lift pre-pass must scan local hypothesis types too, -- otherwise the `0 ≤ ↑n` fact never reaches the SOS reifier. example (n : ℕ) (x : ℝ) (_h : (n : ℝ) = x) : 0 ≤ x := by sos -- Strict ℕ via `Nat.lt_iff_add_one_le`. `n < n+1` rewrites to -- `n+1 ≤ n+1`, which the rewrite step closes reflexively before the -- cast bridge is needed. example : ∀ n : ℕ, n < n + 1 := by sos -- ℕ equality via `le_antisymm` split (Harrison `sos.ml:1725`). After -- the antisymmetric split both subgoals reduce to `0 ≤ 0`. example : ∀ m n : ℕ, 2*m + n = (n + m) + m := by sos -- ℕ-discreteness via the negate-and-refute path (Harrison's `INT_SOS`, -- `sos.ml:1728`). At `n := 0.5`, `n*n − n = −0.25 < 0`, so this is not -- in the quadratic module of `{n ≥ 0}` over `ℝ[n]` and the direct -- Putinar path fails. `runSosWithLift` then negates the conclusion, -- applies `Nat.lt_iff_add_one_le`, and finds the infeasibility cert -- `(5↑n − 3)²/16 + (5/16)·↑n + (25/16)·(↑n − ↑n² − 1) = −1`. example : ∀ n : ℕ, n ≤ n * n := by sos /-! ## §8. `sos?` — inspect, then pin the witness `sos?` runs the search and prints a `Try this:` suggestion of an explicit `sos_witness`. The witness is then statically checked at elaboration time, with no CSDP call — useful for committing a certificate that you don't want re-derived on every build. -/ /-- info: Try this: [apply] sos_witness { sigmas := [([], { terms := [((1 : ℚ), CMvPolynomial.C (1 : ℚ)), ((1 : ℚ), CMvPolynomial.X 0)] })] } -/ #guard_msgs in example (x : ℝ) : 0 ≤ x^2 + 1 := by sos? -- And the suggested replacement compiles: example (x : ℝ) : 0 ≤ x^2 + 1 := by sos_witness { sigmas := [([], { terms := [((1 : ℚ), CMvPolynomial.C (1 : ℚ)), ((1 : ℚ), CMvPolynomial.X 0)] })] } -- For strict positivity, the `Try this:` suggestion includes `with ε := …`. /-- info: Try this: [apply] sos_witness { sigmas := [([], { terms := [((1 : ℚ), CMvPolynomial.X 0)] })] } with ε := (1 : ℚ) -/ #guard_msgs in example (x : ℝ) : 0 < x^2 + 1 := by sos? example (x : ℝ) : 0 < x^2 + 1 := by sos_witness { sigmas := [([], { terms := [((1 : ℚ), CMvPolynomial.X 0)] })] } with ε := (1 : ℚ) -- For boundary-tight strict goals (issue #46), `sos?` emits the -- replayable `sos_witness with exponent := ` form. The -- inline cert verifies `−pol^n` against the augmented inequality -- list `gs ++ [−p]`. The exact CSDP-found cert isn't a stable -- string (Gram entries depend on the SDP solver path), so we only -- check that the hand-rolled minimal witness replays. The minimal -- cert here is `σ_{[1]} = 1`, contributing `1 · (−x²) = −x²` against -- `gs ++ [-p] = [x, -x²]`. example (x : ℝ) (_h : 0 < x) : 0 < x^2 := by sos_witness { sigmas := [([1], { terms := [((1 : ℚ), CMvPolynomial.C (1 : ℚ))] })] } with exponent := 2 -- For equality goals the suggestion includes `eqCofs := …`. /-- info: Try this: [apply] sos_witness { sigmas := [([], { terms := [] })], eqCofs := [CMvPolynomial.C (1 : ℚ)] } -/ #guard_msgs in example (x y : ℝ) (_h : x*y = 1) : 0 ≤ x*y - 1 := by sos? -- Equality-elimination reconstruction with a two-step substitution chain. example (x y : ℝ) (_hxy : x - y = 0) (_hy : y - 1 = 0) : 0 ≤ x - 1 := by sos /-! ### `sos_witness` direct use The witness elaborator also accepts certificates for constrained and infeasibility goals. Sigma entries are subset-indexed; their indices are bounds-checked against `gs.length` by `cert.checks`, but the list itself is sparse (no entry needed for a constraint the witness doesn't use). The `eqCofs` list, in contrast, is length-aligned with the equality constraints. -/ -- Constrained — trivial witness, exercising the constraint structural check. example (x : ℝ) (_h : 0 ≤ x) : 0 ≤ x^2 := by sos_witness { sigmas := [([], { terms := [((1 : ℚ), CMvPolynomial.X 0)] })] } -- Infeasibility — `-1 = x² + 1·(-x² - 1)` proves the constraint set -- `{x² + 1 ≤ 0}` is infeasible. example (x : ℝ) : ¬ (x^2 + 1 ≤ 0) := by sos_witness { sigmas := [([], { terms := [((1 : ℚ), CMvPolynomial.X 0)] }), ([0], { terms := [((1 : ℚ), CMvPolynomial.C (1 : ℚ))] })] } -- Combined inequality + equality: from `0 ≤ x − 1` and `x = 0` derive -- `False`. Certificate: `−1 = 0 + 1·(x − 1) + (−1)·x`. example (x : ℝ) (_hx : 0 ≤ x - 1) (_hxz : x = 0) : False := by sos_witness { sigmas := [([0], { terms := [((1 : ℚ), CMvPolynomial.C (1 : ℚ))] })], eqCofs := [-CMvPolynomial.C (1 : ℚ)] } -- Negative-coefficient guard. The polynomial identity `−x² = (−1) · x²` -- holds exactly, but `coeffsNonneg` rejects the negative weight, so -- `Certificate.checks` returns `false` and `sos_witness` does NOT -- close this (false) goal. Without the `coeffsNonneg` check the -- witness elaborator would happily prove `0 ≤ −x²`. example : True := by fail_if_success (have : ∀ x : ℝ, 0 ≤ -x^2 := by intro x sos_witness { sigmas := [([], { terms := [((-1 : ℚ), CMvPolynomial.X 0)] })] }) trivial /-! ## §9. Graceful failure & out-of-scope guards -/ -- Motzkin is nonneg but not SOS, so the *default* search (no power -- refutation) has no certificate to find and must fail gracefully. 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 -- ...but `maxRefutationPower` closes it via the Positivstellensatz power -- refutation plus facial-reduction rational recovery (issue: non-SOS -- non-negative targets). Harrison's `REAL_SOS` proves the same bare -- Motzkin via `REAL_NONLINEAR_PROVER` at degree 8. example (x y : ℝ) : 0 ≤ x^4*y^2 + x^2*y^4 + 1 - 3*x^2*y^2 := by sos (config := { maxRefutationPower := 1 }) -- Infimum-0 strict positivity must also fail gracefully. `p = (x*y − -- 1)² + x²` is strictly positive everywhere on ℝ² but its infimum is -- 0 along `x → 0, y = 1/x`. No positive ε admits a Putinar certificate. example : True := by fail_if_success (have : ∀ x y : ℝ, 0 < (x*y - 1)^2 + x^2 := by sos) trivial -- Controls for the equality-hypothesis examples in §5: same conclusion -- without the equality must fail, confirming the equality path was -- genuinely exercised above. example : True := by fail_if_success (have : ∀ x : ℝ, 0 ≤ x^2 - 1 := by sos) trivial example : True := by fail_if_success (have : ∀ x : ℝ, 0 < x^2 := by sos) trivial -- Truncated ℕ subtraction is refused with a hint. /-- error: sos: `by sos` does not handle truncated ℕ subtraction in goals; cast to `Int.sub`, or rewrite via `Nat.sub_eq` with `m ≤ n` in context. -/ #guard_msgs in example : ∀ n : ℕ, n - 1 ≤ n := by sos -- ℕ / ℤ DIV/MOD is supported via the enrichment witnesses introduced -- by the lift pre-pass: literal divisors enrich unconditionally -- (issue #24); non-literal divisors enrich when an in-scope positivity -- hypothesis (`b ≠ 0`, `0 < b`, `m < b`, …) is discharged by `omega` -- (issue #45). Here `b ≠ 0` lets the strict-bound witness fire, and -- `(a/b)·b ≤ a` follows directly from the div/mod identity. example : ∀ a b : ℕ, b ≠ 0 → a / b * b ≤ a := by sos /-! ## §10. Boolean combinations in conclusions `by sos` splits a conjunctive conclusion `p ∧ q` via `And.intro` and runs the rest of the pipeline on each subgoal; for a disjunctive conclusion `p ∨ q` it tries `Or.inl` first and falls back to `Or.inr` on failure. Nesting is capped at depth 3. Disjunctive *hypotheses* are out of scope. -/ -- Simple conjunction. example (x : ℝ) : 0 ≤ x^2 ∧ 0 ≤ x^4 := by sos -- Conjunction under a leading ∀. example : ∀ x : ℝ, 0 ≤ x^2 ∧ 0 ≤ x^4 := by sos -- Disjunction where the left disjunct succeeds. example : ∀ x : ℝ, 0 ≤ x^2 ∨ 0 ≤ -(x^2 + 1) := by sos -- Disjunction where the left disjunct fails (false at `x = 0`), forcing -- the `Or.inr` retry to find the certificate. example : ∀ x : ℝ, 0 ≤ -(x^2 + 1) ∨ 0 ≤ x^2 := by sos -- Nested: `(p ∧ q) ∧ r` — depth 2. example (x y : ℝ) : (0 ≤ x^2 ∧ 0 ≤ y^2) ∧ 0 ≤ x^2 + y^2 := by sos -- Mixed nesting: conjunction inside disjunction. example (x y : ℝ) : (0 ≤ -(x^2 + 1)) ∨ (0 ≤ x^2 ∧ 0 ≤ y^2) := by sos -- Constrained conjunction: each conjunct uses the shared hypothesis. example (x : ℝ) (_h : 0 ≤ x) : 0 ≤ x^3 + x ∧ 0 ≤ x^2 - x + 1/4 := by sos -- ℤ conjunction — the lift pre-pass runs on each conjunct independently. example (a b : ℤ) : 2*a*b ≤ a^2 + b^2 ∧ 0 ≤ a^2 + b^2 := by sos -- A graceful failure: neither disjunct is true (both polynomials are -- strictly negative), so both `Or.inl` and `Or.inr` arms fail. example : True := by fail_if_success (have : ∀ x : ℝ, (0 ≤ -(x^2 + 1)) ∨ (0 ≤ -(x^4 + 1)) := by sos) trivial -- Depth cap: exactly three nested splits is fine. example (x : ℝ) : ((0 ≤ x^2 ∧ 0 ≤ x^2) ∧ 0 ≤ x^2) ∧ 0 ≤ x^2 := by sos -- Depth cap: a left-nested chain of four `And`s exceeds the limit. -- The check is a global preflight scan, so a too-deep subtree is -- rejected even if some other branch would have closed easily. /-- error: sos: boolean nesting in conclusion exceeds depth 3 (found 4); flatten the goal or split manually -/ #guard_msgs in example (x : ℝ) : (((0 ≤ x^2 ∧ 0 ≤ x^2) ∧ 0 ≤ x^2) ∧ 0 ≤ x^2) ∧ 0 ≤ x^2 := by sos