#!/usr/bin/env python3
"""Exact canonical normal-structure algebra and transported surface jets."""
from pathlib import Path
from itertools import combinations
from datetime import datetime, timezone
import argparse
import hashlib,json,math
import sympy as S
checks=0

def require(ok):
 global checks
 assert ok
 checks+=1

def bracket(A,B):return A*B-B*A

def anti(B):
 R,I=S.re(B),S.im(B)
 return R.row_join(I).col_join(I.row_join(-R))

def linear(B):
 R,I=S.re(B),S.im(B)
 return R.row_join(-I).col_join(I.row_join(R))

def E(q,a,b):
 B=S.zeros(q);B[a,b]=1;B[b,a]=-1
 return B

def rank(ms,n):
 return S.Matrix.hstack(*(m.reshape(n*n,1) for m in ms)).rank() if ms else 0

def independent(ms,n):
 if not ms:return []
 M=S.Matrix.hstack(*(m.reshape(n*n,1) for m in ms))
 return [ms[k] for k in M.rref()[1]]

def closure(ms,n):
 basis=independent(ms,n)
 while True:
  nxt=independent(basis+[bracket(a,b) for a,b in combinations(basis,2)],n)
  if len(nxt)==len(basis):return basis
  basis=nxt

cases=[]
for q,depths in [(4,[2]),(3,[3,4]),(2,[5,6]),(0,[10])]:
 if q==0:
  cases.append(dict(q=q,physical_depths=depths,orientation_dimension=0,curvature_rank=0,central_rank=0,traceless_rank=0,holonomy_dimension=0));continue
 n=2*q;J=linear(S.I*S.eye(q));zero=S.zeros(n)
 mb=[anti(f*E(q,a,b)) for a,b in combinations(range(q),2) for f in [1,S.I]]
 require(len(mb)==q*(q-1));require(rank(mb,n)==q*(q-1))
 for X in mb:
  require(X.T==-X);require(X*J+J*X==zero)
 comm=[bracket(X,Y) for X,Y in combinations(mb,2)]
 for Z in comm:
  require(Z.T==-Z);require(bracket(Z,J)==zero)
 cent=lambda Z:(J.T*Z).trace()*J/S.Integer(n)
 cr=rank([cent(Z) for Z in comm],n)
 tr=rank([Z-cent(Z) for Z in comm],n)
 total=rank(comm,n);hol=len(closure(comm,n))
 expected={4:(16,1,15),3:(9,1,8),2:(1,1,0)}[q]
 require((total,cr,tr)==expected);require(hol==total)
 for X,Y in combinations(mb,2):
  require(bracket(bracket(X,J),bracket(Y,J))/4==bracket(X,Y))
 if q==2:
  # Entire traceless isotropy kernel: real skew E12, imaginary symmetric,
  # and traceless imaginary diagonal all act trivially on m.
  su=[linear(E(q,0,1)),linear(S.I*S.Matrix([[0,1],[1,0]])),linear(S.I*S.diag(1,-1))]
  for H in su:
   for X in mb:require(bracket(H,X)==zero)
 cases.append(dict(q=q,physical_depths=depths,orientation_dimension=q*(q-1),curvature_rank=total,central_rank=cr,traceless_rank=tr,holonomy_dimension=hol))
surfaces=[]
for q in [3,4]:
 n=2*q;X=anti(E(q,0,1))
 Ys=[anti(S.I*E(q,0,1))]
 for k in range(2,q):
  for a in [0,1]:
   Ys.extend([anti(E(q,a,k)),anti(S.I*E(q,a,k))])
 # Y(s)=sum s^r Ys[r], J=g J0 g^-1, g=exp(sX)exp(tY(s)).
 # Along t=0 theta_s=X, theta_t=Y(s), A_h=0.
 # Thus these are exact curvature jets / r! in a common transported frame.
 coefficients=[-bracket(X,Y) for Y in Ys]
 jets=[math.factorial(r)*C for r,C in enumerate(coefficients)]
 hol=closure(jets,n);require(len(hol)==q*q)
 require(rank(jets,n)==len(Ys))
 J=linear(S.I*S.eye(q));cent=lambda Z:(J.T*Z).trace()*J/S.Integer(n)
 require(rank([H-cent(H) for H in hol],n)==q*q-1)
 # A Vandermonde evaluation at distinct points is an alternative to jets.
 values=[sum((S.Integer(s)**r*C for r,C in enumerate(coefficients)),S.zeros(n)) for s in range(len(Ys))]
 require(rank(values,n)==len(Ys));require(len(closure(values,n))==q*q)
 # Arbitrarily small distinct rational s work by the same Vandermonde fact.
 require(S.Matrix([[S.Rational(s,100)**r for r in range(len(Ys))] for s in range(len(Ys))]).det()!=0)
 surfaces.append(dict(q=q,base_dimension=2,curvature_at_one_point_rank=1,transported_curvature_span=len(Ys),jet_orders=list(range(len(Ys))),holonomy_dimension=len(hol),traceless_dimension=q*q-1,coefficient_matrices=[[[str(z) for z in row] for row in C.tolist()] for C in coefficients]))
# Direct second-order adapted-frame expansion, independent of the curvature ranks.
for q in [2,3,4]:
 n=2*q;J=linear(S.I*S.eye(q));P=anti(E(q,0,1));Q=anti(S.I*E(q,0,1))
 Ph=lambda Z:(Z-J*Z*J)/2
 require(Ph(Q)==S.zeros(n))
 # exp(-eps P) d_z exp(eps(P+zQ)) at z=0:
 second=(P*Q+Q*P)/2-P*Q
 require(Ph(second)==-bracket(P,Q)/2)
 # Curvature from d_t a_s at t=0; polynomial Y' stays in m.
 require(Ph(bracket(P,Q)+Q)==bracket(P,Q))
# Chern-root algebra for W=Lambda^2 Q, independent symbolic root check.
x,y,z=S.symbols('x y z');h=x+y+z
roots=[h-x,h-y,h-z]
require(S.expand(sum(roots))==2*h)
require(S.expand(sum(a*b for a,b in combinations(roots,2))-h*h-(x*y+x*z+y*z))==0)
require(S.expand(S.prod(roots)-h*(x*y+x*z+y*z)+x*y*z)==0)
require(S.expand(4*h*h-2*(h*h+x*y+x*z+y*z)-2*(h*h-(x*y+x*z+y*z)))==0)
# Spin lift and the distinct determinant branches, exact rational weights.
spin={str(q):{'spin_weights': [str(S.Rational(r)-S.Rational(q,2)) for r in range(q+1)],'vacuum_weight':str(-S.Rational(q,2))} for q in [2,3,4]}
require(spin['3']['spin_weights']==['-3/2','-1/2','1/2','3/2'])
parser = argparse.ArgumentParser()
parser.add_argument('--report', type=Path, required=True)
args = parser.parse_args()
result = {
    'generated_utc': datetime.now(timezone.utc).isoformat(),
    'scope': 'Exact canonical normal-structure algebra, transported two-dimensional curvature jets and Spin weights; no particle dynamics.',
    'checks_passed': checks,
    'cases': cases,
    'occupied_cases': cases,
    'two_dimensional_pullbacks': surfaces,
    'spin_lift': spin,
    'sources': {'experiments/canonical-normal-structure-checks.py': hashlib.sha256(Path(__file__).read_bytes()).hexdigest()}
}
args.report.write_text(json.dumps(result, indent=2)+'\n')
print(json.dumps({'checks_passed': checks, 'surface_holonomy': [(x['q'], x['holonomy_dimension']) for x in surfaces]}))
