Skip to main content

logicaffeine_proof/
relax_replay.rs

1//! Settled-answer replay — the residual certificate for physics-solved linear systems.
2//!
3//! The trust covenant: the physical solver (or its behavioral model) is UNTRUSTED by
4//! construction — no trajectory, no convergence argument, no producer arithmetic is believed.
5//! The claim is neutral data: `A`, `b`, the settled readout `x̂`, and a declared ∞-norm bound
6//! `ε` — all exact rationals. [`check_relax_claim`] re-multiplies `r = A·x̂ − b` with THIS
7//! crate's own exact arithmetic ([`crate::linalg::matvec`]) and demands `‖r‖∞ ≤ ε`,
8//! component by component. Fail-closed: a malformed shape, a negative ε, or an empty system
9//! is a rejection, never a pass.
10//!
11//! The same mathematics will judge the thermal map (`G·ΔT = P`) — one residual checker,
12//! two physical planes.
13
14use logicaffeine_base::numeric::Rational;
15
16use crate::linalg::matvec;
17
18/// The neutral claim a settling run emits. Everything exact; nothing trusted.
19#[derive(Clone, Debug, PartialEq)]
20pub struct RelaxClaim {
21    /// The system matrix, row-major, n×n.
22    pub a: Vec<Vec<Rational>>,
23    /// The right-hand side, length n.
24    pub b: Vec<Rational>,
25    /// The settled readout, length n.
26    pub x_hat: Vec<Rational>,
27    /// The declared bound: `‖A·x̂ − b‖∞ ≤ eps`.
28    pub eps: Rational,
29}
30
31/// The neutral claim a convex-quadratic settle emits: `minimize ½xᵀQx + bᵀx`, the settled
32/// readout, a claimed strong-convexity parameter, and a claimed suboptimality bound.
33/// Everything exact; nothing trusted.
34#[derive(Clone, Debug, PartialEq)]
35pub struct MinimizeClaim {
36    pub q: Vec<Vec<Rational>>,
37    pub b: Vec<Rational>,
38    pub x_hat: Vec<Rational>,
39    /// Claimed lower bound on Q's spectrum (strong-convexity parameter), > 0.
40    pub mu: Rational,
41    /// Claimed bound: `f(x̂) − f* ≤ subopt`.
42    pub subopt: Rational,
43}
44
45/// Judge a minimize claim by THEOREM, not trust: for symmetric Q with spectrum ≥ μ > 0
46/// (verified against the exact Gershgorin row bound, which this checker recomputes itself),
47/// strong convexity gives `f(x̂) − f* ≤ ‖∇f(x̂)‖² / (2μ)` — the gradient is re-derived with
48/// this crate's own arithmetic and the inequality checked exactly. Fail-closed throughout.
49pub fn check_minimize_claim(claim: &MinimizeClaim) -> bool {
50    let n = claim.q.len();
51    if n == 0 || claim.b.len() != n || claim.x_hat.len() != n {
52        return false;
53    }
54    if claim.q.iter().any(|row| row.len() != n) {
55        return false;
56    }
57    if !claim.mu.is_positive() || claim.subopt.is_negative() {
58        return false;
59    }
60    // Symmetry, and the Gershgorin row bound: min_i (q_ii − Σ_{j≠i} |q_ij|) ≥ μ.
61    for i in 0..n {
62        for j in (i + 1)..n {
63            if claim.q[i][j] != claim.q[j][i] {
64                return false;
65            }
66        }
67        let off = (0..n)
68            .filter(|j| *j != i)
69            .fold(Rational::zero(), |acc, j| acc.add(&claim.q[i][j].abs()));
70        if claim.q[i][i].sub(&off) < claim.mu {
71            return false;
72        }
73    }
74    // ∇f(x̂) = Q·x̂ + b, re-multiplied here; then ‖∇‖² ≤ 2μ·subopt exactly.
75    let qx = matvec(&claim.q, &claim.x_hat);
76    let mut norm2 = Rational::zero();
77    for i in 0..n {
78        let g = qx[i].add(&claim.b[i]);
79        norm2 = norm2.add(&g.mul(&g));
80    }
81    let two_mu_subopt = Rational::from_i64(2).mul(&claim.mu).mul(&claim.subopt);
82    norm2 <= two_mu_subopt
83}
84
85/// The Ising candidate's neutral claim: the instance, the returned spins, the claimed exact
86/// energy, and the claimed 1-flip stability verdict. BY DESIGN there is no optimality field
87/// — a soft-spin settle returns an attractor, and this type cannot express more.
88#[derive(Clone, Debug, PartialEq)]
89pub struct IsingClaim {
90    /// Symmetric coupling matrix with zero diagonal.
91    pub j: Vec<Vec<Rational>>,
92    /// External fields.
93    pub h: Vec<Rational>,
94    /// The candidate spins, each exactly ±1.
95    pub spins: Vec<i8>,
96    /// Claimed `H(s) = −Σ_{i<j} J_ij·s_i·s_j − Σ h_i·s_i`, exact.
97    pub energy: Rational,
98    /// Claimed: no single spin flip lowers H.
99    pub flip_stable: bool,
100}
101
102/// Judge an Ising candidate: recompute the exact energy and the exact 1-flip stability with
103/// this crate's own arithmetic and demand agreement with BOTH claims. Fail-closed on shape
104/// garbage (asymmetry, nonzero diagonal, non-±1 spins).
105pub fn check_ising_claim(claim: &IsingClaim) -> bool {
106    let n = claim.j.len();
107    if n == 0 || claim.h.len() != n || claim.spins.len() != n {
108        return false;
109    }
110    if claim.j.iter().any(|row| row.len() != n) {
111        return false;
112    }
113    for i in 0..n {
114        if !claim.j[i][i].is_zero() {
115            return false;
116        }
117        for jx in (i + 1)..n {
118            if claim.j[i][jx] != claim.j[jx][i] {
119                return false;
120            }
121        }
122    }
123    if claim.spins.iter().any(|s| *s != 1 && *s != -1) {
124        return false;
125    }
126    let s_of = |i: usize| Rational::from_i64(claim.spins[i] as i64);
127
128    // Exact energy.
129    let mut energy = Rational::zero();
130    for i in 0..n {
131        for jx in (i + 1)..n {
132            energy = energy.sub(&claim.j[i][jx].mul(&s_of(i)).mul(&s_of(jx)));
133        }
134        energy = energy.sub(&claim.h[i].mul(&s_of(i)));
135    }
136    if energy != claim.energy {
137        return false;
138    }
139
140    // Exact 1-flip stability: flipping spin i changes H by ΔH_i = 2·s_i·(Σ_j J_ij·s_j + h_i);
141    // the candidate is flip-stable iff every ΔH_i ≥ 0.
142    let mut stable = true;
143    for i in 0..n {
144        let mut field = claim.h[i].clone();
145        for jx in 0..n {
146            if jx != i {
147                field = field.add(&claim.j[i][jx].mul(&s_of(jx)));
148            }
149        }
150        let delta = Rational::from_i64(2).mul(&s_of(i)).mul(&field);
151        if delta.is_negative() {
152            stable = false;
153            break;
154        }
155    }
156    stable == claim.flip_stable
157}
158
159/// THE judge. Recomputes the residual with this crate's own exact multiplication and checks
160/// the declared bound. Rejects malformed claims outright.
161pub fn check_relax_claim(claim: &RelaxClaim) -> bool {
162    let n = claim.a.len();
163    if n == 0 || claim.b.len() != n || claim.x_hat.len() != n {
164        return false;
165    }
166    if claim.a.iter().any(|row| row.len() != n) {
167        return false;
168    }
169    if claim.eps.is_negative() {
170        return false;
171    }
172    let ax = matvec(&claim.a, &claim.x_hat);
173    ax.iter()
174        .zip(&claim.b)
175        .all(|(axi, bi)| axi.sub(bi).abs() <= claim.eps)
176}