#!/usr/bin/env python3
"""verify_P304.py — Verifier for Addendum 304 (P18-T2 sub-result (i),
layer-cycle sector).

Asserts, recomputing from scratch, that the Z_3 layer-cycle sector
operator T_cycle of P18 S3.4 is FORCED: it is the only operator in its
category given the fixed orientation (Paper 06 requires L(3,1), Paper 28
fixes positive orientation). This is a verified per-category uniqueness
result for ONE sector of P18-T2 sub-result (i); the other sectors remain
open, and A294's re-typing of the whole P18-T2 stands.

The Z_3 action is built as its regular representation: the cyclic shift
matrix C and its square C^2 = C^{-1}. The orientation invariant is the
signed imaginary part of the eigenvalue each generator carries on the
fixed k=1 Fourier character v1 = (1, omega, omega^2)/sqrt(3); it is
+sin(2pi/3) for the forward (positively oriented) generator and
-sin(2pi/3) for the backward one. L(3,2) = -L(3,1) (Reidemeister-Franz),
so the orientation selects exactly one generator.

  S1  Z_3 group and its two generators   - checks 1-3
  S2  Eigenstructure and conjugacy        - checks 4-6
  S3  Orientation forces a unique pick    - checks 7-8
"""
import sys
import math

import numpy as np

PASS = FAIL = 0


def check(n, desc, cond):
    global PASS, FAIL
    ok = bool(cond)
    PASS += ok
    FAIL += (not ok)
    print(f"  [{'PASS' if ok else 'FAIL'}] {n:>2}. {desc}")


PI = math.pi
OMEGA = complex(math.cos(2 * PI / 3), math.sin(2 * PI / 3))
SIN120 = math.sin(2 * PI / 3)
I3 = np.eye(3)

# Regular representation of Z_3: cyclic shift and its inverse.
SHIFT = np.array([[0.0, 0.0, 1.0],
                  [1.0, 0.0, 0.0],
                  [0.0, 1.0, 0.0]])
S = SHIFT
S2 = np.linalg.matrix_power(SHIFT, 2)

# k=1 Fourier character (common eigenbasis of the group).
V1 = np.array([1.0 + 0j, OMEGA, OMEGA ** 2]) / math.sqrt(3.0)


def mat_order(M):
    P = np.eye(3)
    for k in range(1, 4):
        P = P @ M
        if np.allclose(P, I3, atol=1e-12):
            return k
    return 0


def fiber_char_im(M):
    """Im of the eigenvalue M carries on the fixed k=1 character."""
    return float(complex(np.vdot(V1, M @ V1)).imag)


# Identify the forward (positively oriented) generator C = T_cycle and its
# square C2 = T_cycle^2 by the sign of the orientation invariant.
if fiber_char_im(S) > 0:
    C, C2 = S, S2
else:
    C, C2 = S2, S

# Group elements: nontrivial generators are the order-3 elements.
elements = {"I": I3, "C": C, "C2": C2}
order3 = [name for name, M in elements.items() if mat_order(M) == 3]

eigC = np.linalg.eigvals(C)
eigC_set = sorted([complex(round(z.real, 8), round(z.imag, 8)) for z in eigC],
                  key=lambda z: np.mod(np.angle(z), 2 * PI))
cube_roots = sorted([1.0 + 0j, OMEGA, OMEGA ** 2],
                    key=lambda z: np.mod(np.angle(z), 2 * PI))

oriC = fiber_char_im(C)
oriC2 = fiber_char_im(C2)
selected = [name for name, M in [("C", C), ("C2", C2)] if fiber_char_im(M) > 0]

print("S1  Z_3 group and its two nontrivial generators")
check(1, "Z_3 has exactly two order-3 generators (got %s)" % order3,
      len(order3) == 2 and set(order3) == {"C", "C2"})
check(2, "C^3 = I and (C^2)^3 = I",
      np.allclose(np.linalg.matrix_power(C, 3), I3, atol=1e-12)
      and np.allclose(np.linalg.matrix_power(C2, 3), I3, atol=1e-12))
check(3, "neither generator is the identity (order > 1)",
      mat_order(C) == 3 and mat_order(C2) == 3)

print("S2  Eigenstructure {1, omega, omega^2} and conjugacy")
check(4, "eigenvalues of C are the cube roots of unity",
      all(any(abs(e - cr) < 1e-8 for cr in cube_roots) for e in eigC_set)
      and len(eigC_set) == 3)
check(5, "C and C^2 are inverse representations: C @ C^2 = I",
      np.allclose(C @ C2, I3, atol=1e-12))
check(6, "C^2 = C^T (complex-conjugate / inverse rep of the perm shift)",
      np.allclose(C2, C.T, atol=1e-12))

print("S3  Orientation forces a unique generator")
check(7, "orientation invariant flips sign: C=%+.4f, C^2=%+.4f (sin120=%.4f)"
      % (oriC, oriC2, SIN120),
      abs(oriC - SIN120) < 1e-10 and abs(oriC2 + SIN120) < 1e-10
      and oriC * oriC2 < 0)
check(8, "fixed positive orientation (Paper 06 L(3,1)) selects exactly one "
      "generator (C = T_cycle)",
      len(selected) == 1 and selected[0] == "C")

print(f"\n{'='*60}\nRESULT: {PASS} PASS / {FAIL} FAIL")
sys.exit(0 if FAIL == 0 else 1)
