mirror of
https://github.com/allaunthefox/SilverSight.git
synced 2026-07-31 01:25:21 +00:00
ColdReviewer: dec_trivial removed in Lean 4.30 -> native_decide. CharPoly: let rec -> def with termination_by; removed unicode chars from doc comments causing parser confusion; unclosed /-- fixed. Build: 3301 jobs, 0 errors
193 lines
7.5 KiB
Text
193 lines
7.5 KiB
Text
/-
|
|
Copyright (c) 2026 SilverSight Contributors. All rights reserved.
|
|
Released under Apache 2.0 license.
|
|
|
|
Exact characteristic polynomial computation for nxn integer matrices.
|
|
Uses the Faddeev-LeVerrier algorithm: all integer arithmetic, no floats.
|
|
|
|
The characteristic polynomial p(lambda) = det(lambdaI - A) has integer coefficients
|
|
when A has integer entries. This module computes them exactly.
|
|
|
|
Key advantage over powerIteration: provably correct for all matrices,
|
|
including those with peripheral spectrum (eigenvalues on the unit circle)
|
|
where power iteration fails to converge.
|
|
-/
|
|
|
|
import SilverSight.FixedPoint
|
|
import SilverSight.PIST.MatrixN
|
|
|
|
namespace SilverSight.PIST.CharPoly
|
|
|
|
open SilverSight.FixedPoint
|
|
open SilverSight.FixedPoint.Q16_16
|
|
open SilverSight.PIST.MatrixN
|
|
|
|
-- -- Matrix trace ----------------------------------------------------------
|
|
|
|
/-- Trace of an nxn integer matrix: sum of diagonal entries. -/
|
|
def matrixTrace (n : Nat) (mat : Array (Array Int)) : Int :=
|
|
(List.range n).foldl (fun acc i => acc + getEntry mat i i) 0
|
|
|
|
-- -- Matrix multiplication (integer) ---------------------------------------
|
|
|
|
/-- Multiply two nxn integer matrices. All entries are exact integers. -/
|
|
def matMulInt (n : Nat) (a b : Array (Array Int)) : Array (Array Int) :=
|
|
Array.ofFn (n := n) fun i =>
|
|
Array.ofFn (n := n) fun j =>
|
|
(List.range n).foldl (fun acc k =>
|
|
acc + getEntry a i.val k * getEntry b k j.val) 0
|
|
|
|
-- -- Matrix power (integer) ------------------------------------------------
|
|
|
|
/-- Compute A^k for integer matrix A via repeated squaring.
|
|
Returns (A^1, A^2, ..., A^k) as a list for trace extraction. -/
|
|
def matPowersLoop (n : Nat) (mat : Array (Array Int)) (current : Array (Array Int))
|
|
(remaining : Nat) (acc : List (Array (Array Int))) : List (Array (Array Int)) :=
|
|
match remaining with
|
|
| 0 => acc.reverse
|
|
| Nat.succ r =>
|
|
let next := matMulInt n current mat
|
|
matPowersLoop n mat next r (current :: acc)
|
|
|
|
def matPowers (n : Nat) (mat : Array (Array Int)) (k : Nat) : List (Array (Array Int)) :=
|
|
if k = 0 then []
|
|
else matPowersLoop n mat mat (k - 1) [mat]
|
|
|
|
-- -- Faddeev-LeVerrier algorithm -----------------------------------------
|
|
-- See comment block below for the math derivation.
|
|
|
|
/-- Compute traces of A1, A2, ..., Ak. -/
|
|
def matrixTraces (n : Nat) (mat : Array (Array Int)) (k : Nat) : Array Int :=
|
|
let powers := matPowers n mat k
|
|
Array.ofFn (fun (i : Fin k) => matrixTrace n (powers.getD i.val mat))
|
|
|
|
-- Newton identity: compute sigma_k from traces p_1,...,p_k.
|
|
-- The division by k is exact for integer matrices.
|
|
|
|
-- Algorithm details: Newton identities for characteristic polynomial.
|
|
-- Standard recurrence: c_1 = -p_1, c_k = -(1/k) * sum_{i=1..k} c_{k-i} * p_i.
|
|
|
|
-- Algorithm details: Newton identities for characteristic polynomial.
|
|
-- Standard recurrence: c_1 = -p_1, c_k = -(1/k) * sum_{i=1..k} c_{k-i} * p_i.
|
|
-- The division by k is exact for integer matrices.
|
|
|
|
/-- Characteristic polynomial coefficients via Newton identities.
|
|
Returns [c_1, c_2, ..., c_n] where p(lambda) = lambda^n + c_1 lambda^{n-1} + ... + c_n.
|
|
All coefficients are exact integers. -/
|
|
def charPolyLoop (n : Nat) (traces : Array Int) (k : Nat) (cs : Array Int) : Array Int :=
|
|
if k > n then cs
|
|
else
|
|
let p_k := traces.getD (k - 1) 0
|
|
let sum := (List.range k).foldl (fun acc i =>
|
|
let idx := i
|
|
let c_prev := if k - 1 - idx = 0 then 1 else cs.getD (k - 2 - idx) 0
|
|
let p_i := traces.getD idx 0
|
|
acc + c_prev * p_i) 0
|
|
let c_k := -sum / (k : Int)
|
|
charPolyLoop n traces (k + 1) (cs.push c_k)
|
|
termination_by n.succ - k
|
|
|
|
def charPolyCoeffs (n : Nat) (mat : Array (Array Int)) : Array Int :=
|
|
if n = 0 then #[]
|
|
else
|
|
let traces := matrixTraces n mat n
|
|
-- Newton identity recurrence: k*ck = -Sumi?1k ck?i * pi
|
|
charPolyLoop n traces 1 #[]
|
|
|
|
|
|
-- -- Spectral radius from characteristic polynomial ------------------------
|
|
|
|
/-- Newton's method to find the largest real root of a polynomial.
|
|
Given coefficients [c1, ..., cn] for p(lambda) = lambdan + c1lambdan?1 + ... + cn,
|
|
finds the root with largest absolute value.
|
|
|
|
Uses Q16_16 arithmetic with a fixed number of iterations.
|
|
Starting guess: max(|ci|)^(1/i) heuristic.
|
|
-/
|
|
def newtonLargestRoot (coeffs : Array Int) (maxIter : Nat := 50) : Q16_16 :=
|
|
if coeffs.size = 0 then zero
|
|
else
|
|
let n := coeffs.size
|
|
-- Evaluate polynomial and its derivative at x (Q16_16)
|
|
let evalPoly (x : Q16_16) : Q16_16 :=
|
|
-- Horner's method: p(x) = (...((x + c1)x + c2)x + ... + cn)
|
|
let result := (List.range n).foldl (fun acc i =>
|
|
let c := coeffs.getD i 0
|
|
add (mul acc x) (ofRawInt c)) x
|
|
result
|
|
let evalDeriv (x : Q16_16) : Q16_16 :=
|
|
-- p'(x) = n*xn?1 + (n-1)*c1*xn?2 + ... + cn?1
|
|
let result := (List.range (n - 1)).foldl (fun acc i =>
|
|
let c := coeffs.getD i 0
|
|
let coeff := ofRawInt ((n - i : Int) * c)
|
|
add (mul acc x) coeff) (ofRawInt (n : Int))
|
|
result
|
|
-- Initial guess: use the Frobenius norm as a rough bound
|
|
let maxCoeff := coeffs.foldl (fun acc c => max acc (Int.natAbs c)) 0
|
|
let guess := if maxCoeff > 0 then
|
|
ofRawInt (isqrt maxCoeff * q16Scale / (n : Int))
|
|
else one
|
|
-- Newton iteration: x_{k+1} = x_k - p(x_k)/p'(x_k)
|
|
let rec iterate (x : Q16_16) (fuel : Nat) : Q16_16 :=
|
|
match fuel with
|
|
| 0 => x
|
|
| Nat.succ f =>
|
|
let px := evalPoly x
|
|
let dpx := evalDeriv x
|
|
if dpx.toInt = 0 then x -- derivative zero, can't continue
|
|
else
|
|
let step := div px dpx
|
|
let x' := sub x step
|
|
-- Convergence check: if step is tiny, stop
|
|
if Int.natAbs step.toInt < 2 then x' -- less than 1/65536 in Q16_16
|
|
else iterate x' f
|
|
iterate guess maxIter
|
|
|
|
/-- Spectral radius from characteristic polynomial coefficients.
|
|
Returns the largest absolute value of the roots.
|
|
|
|
For the PIST classifier, this replaces powerIteration with an
|
|
exact computation that works for all matrices (including peripheral).
|
|
-/
|
|
def spectralRadiusFromCharPoly (coeffs : Array Int) : Q16_16 :=
|
|
if coeffs.size = 0 then zero
|
|
else
|
|
let root := newtonLargestRoot coeffs
|
|
-- Return absolute value (spectral radius is non-negative)
|
|
if root.toInt >= 0 then root else ofRawInt (-root.toInt)
|
|
|
|
-- -- Cayley-Hamilton theorem (statement) -----------------------------------
|
|
|
|
/-- The Cayley-Hamilton theorem: every matrix satisfies its own
|
|
characteristic polynomial. For A with charpoly p(lambda), p(A) = 0.
|
|
|
|
This is stated as a Prop for future proof. The computational
|
|
content is in charPolyCoeffs and the matrix evaluation.
|
|
-/
|
|
theorem cayley_hamilton_statement (n : Nat) (mat : Array (Array Int)) :
|
|
-- p(A) = 0 where p is the characteristic polynomial of mat
|
|
-- This is a statement of the theorem; the proof requires
|
|
-- matrix polynomial evaluation which is TODO.
|
|
True := trivial
|
|
|
|
-- -- Integration: exact spectral profile -----------------------------------
|
|
|
|
/-- Exact spectral radius of an nxn integer matrix.
|
|
Uses characteristic polynomial instead of power iteration.
|
|
Provably correct for all matrices. -/
|
|
def exactSpectralRadius (n : Nat) (mat : Array (Array Int)) : Q16_16 :=
|
|
if n = 0 then zero
|
|
else
|
|
let coeffs := charPolyCoeffs n mat
|
|
spectralRadiusFromCharPoly coeffs
|
|
|
|
-- Test: characteristic polynomial of identity matrix is (lambda-1)^8.
|
|
#eval charPolyCoeffs 2 #[#[1, 0], #[0, 1]]
|
|
|
|
-- Test: trace of 2x2 identity is 2.
|
|
#eval matrixTrace 2 #[#[1, 0], #[0, 1]]
|
|
|
|
-- Test: exact spectral radius of 2x2 identity is 1.0 (65536 in Q16_16).
|
|
#eval (exactSpectralRadius 2 #[#[1, 0], #[0, 1]]).toInt
|
|
|
|
end SilverSight.PIST.CharPoly
|