mirror of
https://github.com/allaunthefox/SilverSight.git
synced 2026-07-30 17:16:16 +00:00
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>
375 lines
15 KiB
Python
375 lines
15 KiB
Python
#!/usr/bin/env python3
|
||
"""semisymmetry_discriminator.py — Milestone §0: covariant semi-symmetry test.
|
||
|
||
Places the drift-perturbed Fisher–Rao 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 Δ_{m−1} = {p ∈ ℝ^m_{>0} : Σp = 1} in reduced
|
||
coordinates p₁..p_{m−1} (p_m = 1 − Σ) carries the Fisher–Rao metric
|
||
|
||
g_ij = δ_ij / p_i + 1 / p_m (positive-definite, (m−1, 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(m−1). 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, m−2) with the TIME-LIKE direction = the
|
||
drift direction. This is the concrete geometrization of "the
|
||
Kelvin/Rossby directionality supplies the (1, m−2) 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 (Kobayashi–Nomizu):
|
||
|
||
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):
|
||
"""Fisher–Rao on Δ_{m−1} 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 = m−1); 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 Fisher–Rao 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 (m−1, 0) positive-definite, holonomy SO(m−1)")
|
||
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())
|