#!/usr/bin/env python3
"""
verify_P030.py -- Paper 30: Geometric Observer Network.

This verifier checks the explicit quaternion/Hopf architecture arithmetic and
flags benchmark/protocol claims that need stronger assumptions or a supplied
implementation.
"""

from __future__ import annotations

import math
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent))
from verify_common import CheckResult, Verifier


class ModernVerifier(Verifier):
    """Local output adapter: tolerance logic byte-identical to
    verify_common.Verifier.check; emits the modern corpus line format
    ("  [PASS] {n:>2}. {desc}") with computed/claimed/tolerance values
    kept as indented info lines, and the modern RESULT footer."""

    def __init__(self, name: str):
        self.name = name
        self.results = []
        self._n = 0
        print(name)

    def check(self, label, computed, claimed, *, rel=1e-3, abs_tol=None, detail=""):
        if abs_tol is not None:
            ok = abs(computed - claimed) <= abs_tol
            err = abs(computed - claimed)
            err_detail = f"abs err={err:.6g}, tol={abs_tol:.6g}"
        else:
            if claimed == 0:
                ok = abs(computed) <= (rel or 1e-12)
                err_detail = f"abs value={abs(computed):.6g}, tol={rel:.6g}"
            else:
                err = (computed - claimed) / abs(claimed)
                ok = abs(err) <= (rel or 0)
                err_detail = f"rel err={100 * err:+.6g}%, tol={100 * (rel or 0):.6g}%"
        return self._emit(label, ok, computed, claimed, err_detail, detail)

    def record(self, label, ok, computed="", claimed="", detail=""):
        return self._emit(label, ok, computed, claimed, "", detail)

    def _emit(self, label, ok, computed, claimed, info, ann):
        full_detail = (info + (f"; {ann}" if ann else "")) if info else ann
        self.results.append(CheckResult(label, ok, computed, claimed, full_detail))
        self._n += 1
        desc = f"{label} -- {ann}" if ann else label
        print(f"  [{'PASS' if ok else 'FAIL'}] {self._n:>2}. {desc}")
        if computed != "" or claimed != "":
            print(f"        computed: {computed}")
            print(f"        claimed : {claimed}")
        if info:
            print(f"        {info}")
        return ok

    def summary(self):
        passed = sum(bool(r.ok) for r in self.results)
        failed = len(self.results) - passed
        print(f"\n{'='*60}\nRESULT: {passed} PASS / {failed} FAIL")
        return 1 if failed else 0


v = ModernVerifier("P030 -- Geometric Observer Network")

ROOT = Path(__file__).resolve().parents[2]
TEX = (ROOT / "toe" / "30_Paper_GeometricObserverNetwork.tex").read_text()

PI = math.pi


def qmul(q: tuple[float, float, float, float], r: tuple[float, float, float, float]) -> tuple[float, float, float, float]:
    a, b, c, d = q
    e, f, g, h = r
    return (
        a * e - b * f - c * g - d * h,
        a * f + b * e + c * h - d * g,
        a * g - b * h + c * e + d * f,
        a * h + b * g - c * f + d * e,
    )


def qconj(q: tuple[float, float, float, float]) -> tuple[float, float, float, float]:
    a, b, c, d = q
    return (a, -b, -c, -d)


def qnorm(q: tuple[float, float, float, float]) -> float:
    return math.sqrt(sum(x * x for x in q))


def qscale(q: tuple[float, float, float, float], s: float) -> tuple[float, float, float, float]:
    return tuple(s * x for x in q)  # type: ignore[return-value]


def qnormalize(q: tuple[float, float, float, float]) -> tuple[float, float, float, float]:
    return qscale(q, 1.0 / qnorm(q))


def qexp_pure(vec: tuple[float, float, float]) -> tuple[float, float, float, float]:
    x, y, z = vec
    phi = math.sqrt(x * x + y * y + z * z)
    if phi == 0.0:
        return (1.0, 0.0, 0.0, 0.0)
    s = math.sin(phi) / phi
    return (math.cos(phi), s * x, s * y, s * z)


def hopf_v(q: tuple[float, float, float, float]) -> float:
    a, b, c, d = q
    return (a * a + b * b) - (c * c + d * d)


v.record("TeX source is present", "Geometric Observer Network" in TEX and "Quaternion Evolution" in TEX)

q_plus = qnormalize((0.82, 0.12, -0.35, 0.43))
q_minus = qnormalize((0.61, -0.24, 0.70, 0.28))
omega_plus = (0.11, -0.20, 0.07)
omega_minus = (-0.05, 0.17, 0.13)
h = 0.01

u_plus = qexp_pure(tuple(0.5 * h * x for x in omega_plus))
u_minus = qexp_pure(tuple(0.5 * h * x for x in omega_minus))
q_plus_next = qmul(u_plus, q_plus)
q_minus_next = qmul(u_minus, q_minus)
q_rel = qmul(q_minus_next, qconj(q_plus_next))

v.check("quaternion exponential has unit norm", qnorm(u_plus), 1.0, rel=1e-15)
v.check("exponential update preserves q+ norm", qnorm(q_plus_next), 1.0, rel=2e-15)
v.check("exponential update preserves q- norm", qnorm(q_minus_next), 1.0, rel=2e-15)
v.check("relational quaternion remains unit norm", qnorm(q_rel), 1.0, rel=3e-15)

vv = hopf_v(q_rel)
v.record("Hopf projection stays in [-1, 1] for unit quaternion", -1.0 <= vv <= 1.0, computed=vv, claimed="in [-1, 1]")
v.record("lapse is real-valued for Hopf v", 1.0 - vv * vv >= 0.0, computed=math.sqrt(max(0.0, 1.0 - vv * vv)), claimed="real")

a, b, c, d = q_rel
imag_norm = math.sqrt(b * b + c * c + d * d)
r = math.acos(max(-1.0, min(1.0, a)))
eta = 2.0 * r
n_parallel = b / imag_norm
axis_rhs = n_parallel * n_parallel + (1.0 - n_parallel * n_parallel) * math.cos(eta)
v.check("axis-resolved identity from log map", axis_rhs, vv, rel=2e-14)

v.check("Omega0 fixed constant", PI**3 / 4.0, 7.751569170074954, rel=1e-15)
v.check("monad closure fixed constant", 4.0 * PI**3 + PI**2 + PI, 137.036304, rel=3e-9)
v.check("initial-state degrees of freedom", 8 - 2, 6, rel=0)
v.check("generator schedule input count for N steps", 6 * 2500, 15000, rel=0)
v.check("MLP comparator input dimension", 3 + 3 + 4 + 4, 14, rel=0)
v.check("FLOP total from stated table", 104 * 2500, 2.6e5, rel=1e-15)
v.record(
    "plain weight decay pulls nonzero promoted constants toward zero",
    True,
    computed="d/dtheta lambda*theta^2 = 2*lambda*theta for theta != 0",
    claimed="L2 regularization penalizes geometric constants",
)

v.record(
    "log map is defined at every S3 state",
    False,
    computed="at q=(1,0,0,0), |(b,c,d)|=0 so n=(b,c,d)/|(b,c,d)| is undefined",
    claimed="Layer 5 log map/axis diagnostic for q_rel,k=(a,b,c,d)",
    detail="Expected fail: the implementation needs an explicit identity/antipode branch.",
)
v.record(
    "norm drift bound < 1e-15 is consistent with implementation estimate",
    False,
    computed=f"N*eps_machine ~= {2500 * 1e-16:.2e}, while the paper later expects drift ~1e-13",
    claimed="norm drift < 1e-15",
    detail="Expected precision-threshold fail.",
)
v.record(
    "mini-batch noise makes exact parameters drift on clean identity data",
    False,
    computed="if every sample has zero residual at the exact graph, every mini-batch gradient of MSE is also zero; mini-batching alone does not create gradient noise",
    claimed="with stochastic mini-batch noise, parameters drift and RMSE increases monotonically",
    detail="Expected benchmark-claim fail.",
)
v.record(
    "finite-capacity MLP cannot reach zero error on the benchmark",
    False,
    computed="on the finite sampled benchmark, an overparameterized MLP can interpolate labels; a nonzero floor would require a continuum task, capacity bound, and optimization proof",
    claimed="for any finite-capacity MLP, RMSE >= epsilon(L,W) > 0",
    detail="Expected protocol-scope fail.",
)
v.record(
    "zero hyperparameters in full experiment",
    False,
    computed="the architecture accepts step size h and horizon T, and the protocol chooses M=1000, N=2500, optimizer, steps, layer counts, and widths",
    claimed="no hyperparameters to be tuned",
    detail="Expected wording fail: zero trainable core parameters is narrower.",
)
v.record(
    "two correct implementations produce bitwise-identical outputs",
    False,
    computed="different but correct operation order, libm trig functions, FMA choices, or hardware can change final floating-point bits",
    claimed="bitwise-identical outputs in the same floating point environment",
    detail="Expected reproducibility overstatement.",
)
v.record(
    "Z3 extension keeps Omega in real R3 without extra structure",
    False,
    computed="sum_j omega^j Omega_{k,j} is generally complex-valued when omega=e^{2pi i/3} and Omega_{k,j} are real R3 vectors",
    claimed="Omega^(pm)_k in R3 with Hermitian cyclic Z3 layer",
    detail="Expected type/embedding gap.",
)
v.record(
    "J3(O) associative-subalgebra variant has dimension 27",
    False,
    computed="3x3 Hermitian matrices over O have real dimension 27; over an associative subalgebra such as H the dimension is 15, not 27",
    claimed="3x3 Hermitian matrix over an associative subalgebra of O, dimension cascade J3(O) (27)",
    detail="Expected algebra/dimension mismatch.",
)
v.record(
    "outside-domain behavior is silent rather than approximately wrong",
    False,
    computed="the formulas still return values for arbitrary supplied states/schedules; domain validation is not specified as a rejecting/silent mechanism",
    claimed="outside its domain, it is silent and produces no output rather than a wrong output",
    detail="Expected operational-spec gap.",
)

sys.exit(v.summary())
