SilverSight/python/semisymmetry_discriminator.py
allaun be5d4e9de5 feat(semisym): §0 discriminator — Δ₃ verdict PROPER (∇R≠0, R·R≠L·Q); roadmap §0 active
0a: static Fisher–Rao on Δ₃ proven constant-curvature 1/4, ∇R≡0 (symbolic).
0b: rossbyDriftFromChirality drift-flip metric has signature (2,1), drift
direction time-like at 3 exact rational points.
0c: drift-flipped metric is PROPER — not locally symmetric (108 nonzero
∇R components at centroid, exact), not semisymmetric, not Deszcz-
pseudosymmetric (inconsistent L ratios at two points).
0d: obstruction is carried by the drift direction.
Refutes the semi-symmetry hypothesis for the drift-FLIP geometrization at
m=4; Randers/torsion geometrizations and the m=8 Sidon-block case remain
open (Δ₇ run pending).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 02:23:33 -05:00

375 lines
15 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""semisymmetry_discriminator.py — Milestone §0: covariant semi-symmetry test.
Places the drift-perturbed FisherRao geometry on the classical ladder
(docs/reviews/CONJECTURE_UPGRADE_ROADMAP.md §0):
locally symmetric ∇R = 0 (Cartan)
semisymmetric R·R = 0, ∇R ≠ 0 (Szabó)
pseudosymmetric R·R = L·Q(g,R) (Deszcz)
Geometry. The open simplex Δ_{m1} = {p ∈ ^m_{>0} : Σp = 1} in reduced
coordinates p₁..p_{m1} (p_m = 1 Σ) carries the FisherRao metric
g_ij = δ_ij / p_i + 1 / p_m (positive-definite, (m1, 0)).
0a BASELINE: g has constant sectional curvature 1/4 (isometric to an
orthant of the radius-2 sphere via p ↦ 2√p), hence ∇R ≡ 0 — fully
symmetric, holonomy SO(m1). Verified symbolically below.
0b DRIFT PERTURBATION: BraidStateN.lean's rossbyDriftFromChirality
assigns per-strand weights (left = +1, right = 1, scarred = +1/2,
achiral = 0). Mean-centering w gives a tangent drift vector β
(Σβ = 0). The *drift-flipped* metric
g' = g 2 (β♭ ⊗ β♭) / g(β, β), β♭ = g β,
is the reflection of g along β: it flips the sign of g exactly on
span(β), so sig(g') = (1, m2) with the TIME-LIKE direction = the
drift direction. This is the concrete geometrization of "the
Kelvin/Rossby directionality supplies the (1, m2) signature"
(§0-0b). β is extended as a constant field in the reduced chart —
a modelling choice, stated explicitly.
0c DISCRIMINATOR: compute R', ∇R', R'·R' and Q(g',R') for g' and test
the ladder. Conventions (KobayashiNomizu):
R^ρ_{σμν} = ∂_μ Γ^ρ_{νσ} ∂_ν Γ^ρ_{μσ}
+ Γ^ρ_{μλ} Γ^λ_{νσ} Γ^ρ_{νλ} Γ^λ_{μσ}
R_{ρσμν} = g_{ρλ} R^λ_{σμν}
Derivation action of an endomorphism A on a (0,4) tensor T:
(A·T)_{ijkl} = A^m_i T_{mjkl} A^m_j T_{imkl}
A^m_k T_{ijml} A^m_l T_{ijkm}
R·R uses A = R(∂a,∂b) (components A^m_i = R^m_{iab});
Q(g,R) uses A = ∂a ∧_g ∂b (components A^m_i = δ^m_a g_{bi} δ^m_b g_{ai}).
Pseudosymmetry asks for a *function* L with R·R = L·Q(g,R).
All arithmetic exact (sympy Rational); point evaluations at rational
points. A nonzero tensor at one rational point is a PROOF of ≠ 0;
symbolic identities are proven over the whole chart.
Usage:
python3 python/semisymmetry_discriminator.py # m = 4 (Δ₃)
python3 python/semisymmetry_discriminator.py --m 8 # Δ₇ (slow)
"""
from __future__ import annotations
import argparse
import itertools
import sys
import sympy as sp
# ── tensor machinery (exact, chart-based) ─────────────────────────────
def fisher_metric(m: int, ps):
"""FisherRao on Δ_{m1} in reduced coordinates."""
pm = 1 - sum(ps)
n = m - 1
g = sp.zeros(n, n)
for i in range(n):
for j in range(n):
g[i, j] = (1 / ps[i] if i == j else 0) + 1 / pm
return sp.Matrix(g)
def christoffel(g: sp.Matrix, coords):
n = len(coords)
ginv = g.inv()
Gamma = [[[sp.S.Zero] * n for _ in range(n)] for _ in range(n)]
dg = [[[sp.diff(g[i, j], coords[k]) for k in range(n)]
for j in range(n)] for i in range(n)]
for l in range(n):
for i in range(n):
for j in range(n):
s = sp.S.Zero
for k in range(n):
s += ginv[l, k] * (dg[k][i][j] + dg[k][j][i] - dg[i][j][k])
Gamma[l][i][j] = sp.together(s / 2)
return Gamma
def riemann(g: sp.Matrix, Gamma, coords):
"""R^ρ_{σμν} (up) and R_{ρσμν} (down)."""
n = len(coords)
Rup = [[[[sp.S.Zero] * n for _ in range(n)] for _ in range(n)] for _ in range(n)]
for rho in range(n):
for sig in range(n):
for mu in range(n):
for nu in range(mu + 1, n): # antisymmetry in (μ,ν)
term = sp.diff(Gamma[rho][nu][sig], coords[mu]) \
- sp.diff(Gamma[rho][mu][sig], coords[nu])
for lam in range(n):
term += Gamma[rho][mu][lam] * Gamma[lam][nu][sig] \
- Gamma[rho][nu][lam] * Gamma[lam][mu][sig]
term = sp.together(term)
Rup[rho][sig][mu][nu] = term
Rup[rho][sig][nu][mu] = -term
Rdn = [[[[sp.S.Zero] * n for _ in range(n)] for _ in range(n)] for _ in range(n)]
for rho in range(n):
for sig in range(n):
for mu in range(n):
for nu in range(mu + 1, n):
s = sp.S.Zero
for lam in range(n):
s += g[rho, lam] * Rup[lam][sig][mu][nu]
s = sp.together(s)
Rdn[rho][sig][mu][nu] = s
Rdn[rho][sig][nu][mu] = -s
return Rup, Rdn
def cov_deriv_riemann(Rdn, Gamma, coords):
"""∇_a R_{ijkl} — the local-symmetry obstruction."""
n = len(coords)
out = [[[[[sp.S.Zero] * n for _ in range(n)] for _ in range(n)]
for _ in range(n)] for _ in range(n)]
for a in range(n):
for i in range(n):
for j in range(n):
for k in range(n):
for l in range(n):
s = sp.diff(Rdn[i][j][k][l], coords[a])
for mth in range(n):
s -= Gamma[mth][a][i] * Rdn[mth][j][k][l]
s -= Gamma[mth][a][j] * Rdn[i][mth][k][l]
s -= Gamma[mth][a][k] * Rdn[i][j][mth][l]
s -= Gamma[mth][a][l] * Rdn[i][j][k][mth]
out[a][i][j][k][l] = sp.together(s)
return out
def derivation_action(A, Rdn, n):
"""(A·R)_{ijkl} for endomorphism components A[m][i] = A^m_i."""
out = [[[[sp.S.Zero] * n for _ in range(n)] for _ in range(n)] for _ in range(n)]
for i in range(n):
for j in range(n):
for k in range(n):
for l in range(n):
s = sp.S.Zero
for mth in range(n):
s -= A[mth][i] * Rdn[mth][j][k][l]
s -= A[mth][j] * Rdn[i][mth][k][l]
s -= A[mth][k] * Rdn[i][j][mth][l]
s -= A[mth][l] * Rdn[i][j][k][mth]
out[i][j][k][l] = s
return out
def rr_and_q(g, Rup, Rdn, n):
"""R·R and Q(g,R): 6-index tensors indexed [a][b] → (0,4) blocks."""
RR = {}
Q = {}
for a in range(n):
for b in range(a + 1, n):
A_R = [[Rup[mth][i][a][b] for i in range(n)] for mth in range(n)]
A_w = [[(g[b, i] if mth == a else 0) - (g[a, i] if mth == b else 0)
for i in range(n)] for mth in range(n)]
RR[(a, b)] = derivation_action(A_R, Rdn, n)
Q[(a, b)] = derivation_action(A_w, Rdn, n)
return RR, Q
# ── evaluation helpers ────────────────────────────────────────────────
def eval_tensor_at(t, subsmap, depth_idx):
"""Exact-substitute every component; return list of nonzero (idx, val)."""
nz = []
for idx in depth_idx:
expr = t
for i in idx:
expr = expr[i]
v = sp.nsimplify(sp.together(expr).subs(subsmap))
v = sp.cancel(v)
if v != 0:
nz.append((idx, v))
return nz
def signature_by_minors(gm: sp.Matrix):
"""Signature via Jacobi/Sylvester: signs of leading principal minors.
Valid when all leading minors are nonzero (checked)."""
n = gm.shape[0]
minors = [gm[:k, :k].det() for k in range(1, n + 1)]
assert all(mv != 0 for mv in minors), "degenerate leading minor"
signs = [sp.sign(mv) for mv in minors]
neg = 0
prev = 1
for s in signs:
if s * prev < 0:
neg += 1
prev = s
return (n - neg, neg), minors
# ── main ──────────────────────────────────────────────────────────────
def main(argv=None) -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--m", type=int, default=4,
help="ambient outcomes (simplex dim = m1); default 4")
ap.add_argument("--fast", action="store_true",
help="defer simplification; point-evaluation only "
"(≠0 results remain exact proofs; ≡0 results are "
"sample-point evidence, confirm symbolically later)")
args = ap.parse_args(argv)
if args.fast:
sp.together = lambda x: x # simplification deferred to point-eval
m = args.m
n = m - 1
ps = sp.symbols(f"p1:{m}", positive=True)
coords = list(ps)
g = fisher_metric(m, ps)
# rossbyDriftFromChirality weights (BraidStateN.lean):
# left=+1, right=1, scarred=+1/2, achiral=0 — cycled over strands.
base = [sp.Integer(1), sp.Integer(-1), sp.Rational(1, 2), sp.Integer(0)]
w = [base[i % 4] for i in range(m)]
wbar = sp.Rational(sum(w), m)
beta_amb = [wi - wbar for wi in w] # Σ = 0: tangent
beta = sp.Matrix(beta_amb[:n]) # reduced-chart components
print(f"Δ_{n}: m={m} outcomes, chirality weights w={w}, β_reduced={list(beta)}")
# ---- 0a: baseline ------------------------------------------------
print("\n[0a] static FisherRao baseline")
Gamma = christoffel(g, coords)
Rup, Rdn = riemann(g, Gamma, coords)
K = sp.Rational(1, 4)
csc_ok = True
for i, j, k, l in itertools.product(range(n), repeat=4):
expect = K * (g[i, k] * g[j, l] - g[i, l] * g[j, k])
if sp.simplify(sp.together(Rdn[i][j][k][l] - expect)) != 0:
csc_ok = False
break
print(f" R = 1/4 (g∧g) (constant curvature 1/4, symbolic): {csc_ok}")
if csc_ok:
print(" ⇒ ∇R ≡ 0 (constant-curvature ⇒ locally symmetric): True")
print(" ⇒ signature (m1, 0) positive-definite, holonomy SO(m1)")
else:
print(" UNEXPECTED — baseline is not constant curvature; abort")
return 1
# ---- 0b: drift-flipped metric -------------------------------------
print("\n[0b] drift-flipped metric g' = g 2 β♭⊗β♭ / g(β,β)")
beta_flat = g * beta
beta_norm2 = (beta.T * g * beta)[0, 0]
gp = sp.Matrix(n, n, lambda i, j: sp.together(
g[i, j] - 2 * beta_flat[i] * beta_flat[j] / beta_norm2))
# signature at rational sample points (exact minors)
pts = []
centroid = {ps[i]: sp.Rational(1, m) for i in range(n)}
pts.append(("centroid", centroid))
off1 = {ps[i]: sp.Rational(i + 2, 2 * m * (m + 2)) for i in range(n)}
pts.append(("offcenter1", off1))
off2 = {ps[i]: sp.Rational(2 * i + 1, m * m + 3) for i in range(n)}
pts.append(("offcenter2", off2))
for name, pt in pts:
gm = sp.Matrix(n, n, lambda i, j: sp.cancel(gp[i, j].subs(pt)))
sig, _ = signature_by_minors(gm)
# time-like = drift: g'(β,β) = g(β,β) < 0
bb = sp.cancel((beta.T * gm * beta)[0, 0])
print(f" {name}: signature {sig}, g'(β,β) = {bb} (<0 ⇒ β time-like)")
# ---- 0c: discriminator on g' --------------------------------------
print("\n[0c] discriminator on g'")
Gp = christoffel(gp, coords)
Rpu, Rpd = riemann(gp, Gp, coords)
idx4 = list(itertools.product(range(n), repeat=4))
idx5 = list(itertools.product(range(n), repeat=5))
nablaR = cov_deriv_riemann(Rpd, Gp, coords)
name0, pt0 = pts[0]
nz = eval_tensor_at(nablaR, pt0, idx5)
print(f" ∇R' at {name0}: {len(nz)} nonzero components "
f"{'⇒ NOT locally symmetric' if nz else '⇒ vanishes here'}")
if nz:
idx, v = nz[0]
print(f" e.g. (∇R')_{idx} = {v}")
RR, Q = rr_and_q(gp, Rpu, Rpd, n)
max_rr_nz = 0
ratios = {}
ok_pseudo = True
L_val = None
for (a, b) in RR:
for name, pt in pts[:2]:
rr_nz = {i: sp.cancel(sp.together(RRc).subs(pt))
for i, RRc in _iter4(RR[(a, b)], idx4)}
q_nz = {i: sp.cancel(sp.together(Qc).subs(pt))
for i, Qc in _iter4(Q[(a, b)], idx4)}
rr_nz = {i: v for i, v in rr_nz.items() if v != 0}
q_nz = {i: v for i, v in q_nz.items() if v != 0}
max_rr_nz = max(max_rr_nz, len(rr_nz))
if not rr_nz:
continue
# pseudosymmetry: R·R = L · Q(g,R) componentwise
for i, v in rr_nz.items():
if i not in q_nz:
ok_pseudo = False
break
r = sp.cancel(v / q_nz[i])
ratios.setdefault((name, a, b), set()).add(r)
if set(rr_nz) != set(q_nz):
# Q has support where R·R doesn't (or vice versa) → L=0 forced there
extra = set(q_nz) - set(rr_nz)
if extra:
ratios.setdefault((name, a, b), set()).add(sp.S.Zero)
if max_rr_nz == 0:
print(" R'·R' = 0 at sample points (candidate SEMISYMMETRIC — "
"confirm symbolically)")
if args.fast:
verdict = ("SEMISYMMETRIC (Szabó) at sample points — symbolic "
"≡ 0 confirmation pending (rerun without --fast)")
else:
allzero = all(sp.simplify(sp.together(c)) == 0
for (a, b) in RR for _, c in _iter4(RR[(a, b)], idx4))
print(f" R'·R' ≡ 0 symbolically: {allzero}")
verdict = "SEMISYMMETRIC (Szabó)" if allzero and nz else "check"
else:
per_point = {}
for (name, a, b), rs in ratios.items():
per_point.setdefault(name, set()).update(rs)
for name, rs in per_point.items():
print(f" L candidates at {name}: {rs}")
consistent = all(len(rs) == 1 for rs in per_point.values()) and ok_pseudo
if consistent:
L_val = {name: next(iter(rs)) for name, rs in per_point.items()}
verdict = f"PSEUDOSYMMETRIC (Deszcz), L = {L_val}"
else:
verdict = ("PROPER (neither semi- nor pseudo-symmetric at sample "
"points) — R·R ≠ L·Q(g,R)")
print(f"\n[verdict] baseline: LOCALLY SYMMETRIC (∇R=0). "
f"drift-perturbed: {'∇R ≠ 0; ' if nz else ''}{verdict}")
# ---- 0d: drift-direction diagnostics ------------------------------
print("\n[0d] obstruction vs drift direction (centroid)")
ib = [sp.S.Zero] * 4
# contract ∇R' with β in the derivative slot vs a g-orthogonal vector
contr_beta = sp.S.Zero
for a in range(n):
for idx in idx4:
comp = nablaR[a][idx[0]][idx[1]][idx[2]][idx[3]]
contr_beta += (beta[a] * comp.subs(pt0)) ** 2
contr_beta = sp.cancel(contr_beta)
print(f" Σ (β^a ∇_a R')² at centroid = {sp.nsimplify(contr_beta)} "
f"({'drift direction carries obstruction' if contr_beta != 0 else 'obstruction ⟂ drift'})")
return 0
def _iter4(t, idx4):
for idx in idx4:
yield idx, t[idx[0]][idx[1]][idx[2]][idx[3]]
if __name__ == "__main__":
sys.exit(main())