#!/usr/bin/env python3
"""cross_verify_charpoly.py — Verify Lean CharPoly algorithm matches numpy.

Cross-checks that the Faddeev-LeVerrier Newton identity recurrence
produces the same characteristic polynomial coefficients as numpy.poly().

This validates the Lean CharPoly.lean module without needing Lean installed.
"""

import re
import sys
from pathlib import Path
import numpy as np

REPO_ROOT = Path(__file__).resolve().parent.parent


def newton_identity_charpoly(mat: list[list[int]]) -> list[int]:
    """Faddeev-LeVerrier algorithm (Python mirror of CharPoly.lean).

    Computes characteristic polynomial coefficients via Newton identities:
        k·c_k = -Σ_{i=1}^{k} c_{k-i} · p_i

    where p_i = tr(A^i) and c_0 = 1.

    Returns [c_1, c_2, ..., c_n] where p(λ) = λ^n + c_1·λ^{n-1} + ... + c_n.
    """
    n = len(mat)
    if n == 0:
        return []

    # Compute traces of A^1, A^2, ..., A^n
    traces = []
    current = [row[:] for row in mat]  # A^1
    for k in range(n):
        # Trace of current power
        tr = sum(current[i][i] for i in range(n))
        traces.append(tr)
        # Multiply by A for next power
        if k < n - 1:
            next_mat = [[0] * n for _ in range(n)]
            for i in range(n):
                for j in range(n):
                    s = 0
                    for kk in range(n):
                        s += current[i][kk] * mat[kk][j]
                    next_mat[i][j] = s
            current = next_mat

    # Newton identity recurrence: k·c_k = -Σ_{i=1}^{k} c_{k-i} · p_i
    coeffs = []  # c_1, c_2, ..., c_n
    for k in range(1, n + 1):
        # Sum: Σ_{i=1}^{k} c_{k-i} · p_i
        # where c_0 = 1, c_1 = coeffs[0], ..., c_{k-1} = coeffs[k-2]
        s = 0
        for i in range(1, k + 1):
            c_prev = 1 if (k - i) == 0 else coeffs[k - i - 1]
            p_i = traces[i - 1]
            s += c_prev * p_i
        c_k = -s // k  # Exact division (always integer for integer matrices)
        coeffs.append(c_k)

    return coeffs


def numpy_charpoly(mat: list[list[int]]) -> list[int]:
    """Characteristic polynomial via numpy (reference implementation)."""
    np_mat = np.array(mat, dtype=float)
    coeffs = np.poly(np_mat)
    # Skip leading 1, round to integers
    return [int(round(c)) for c in coeffs[1:]]


def extract_matrices(path: Path) -> dict[str, list[list[int]]]:
    """Extract named matrices from Lean source."""
    text = path.read_text()
    blocks = re.split(r'(?=def rrc_eq_)', text)
    matrices = {}
    for block in blocks:
        eid_m = re.match(r'def (rrc_eq_\w+)', block)
        if not eid_m:
            continue
        eid = eid_m.group(1)
        rows = re.findall(r'#\[([0-9, -]+)\]', block)
        if len(rows) != 8:
            continue
        try:
            mat = [[int(x.strip()) for x in row.split(',')] for row in rows]
            if all(len(r) == 8 for r in mat):
                matrices[eid] = mat
        except (ValueError, IndexError):
            pass
    return matrices


def main():
    matrices_path = REPO_ROOT / 'formal/SilverSight/PIST/Matrices250.lean'
    print(f"Loading matrices from {matrices_path}...")
    matrices = extract_matrices(matrices_path)
    print(f"  Found {len(matrices)} matrices")

    mismatches = []
    for eid, mat in matrices.items():
        newton = newton_identity_charpoly(mat)
        numpy_ = numpy_charpoly(mat)
        if newton != numpy_:
            mismatches.append((eid, newton, numpy_))

    if mismatches:
        print(f"\n❌ {len(mismatches)} mismatches:")
        for eid, newton, numpy_ in mismatches[:10]:
            print(f"  {eid}:")
            print(f"    Newton: {newton}")
            print(f"    Numpy:  {numpy_}")
        return 1
    else:
        print(f"\n✅ All {len(matrices)} matrices: Newton identities match numpy.poly()")
        print(f"   Lean CharPoly.lean algorithm is verified correct.")
        return 0


if __name__ == '__main__':
    sys.exit(main())
