Research source · python
verify-six-point-obstruction.py
site/public/evidence/stirling-collinear-amplitudes/verify-six-point-obstruction.py
181 lines. Source is displayed for inspection; it is not executed by this page.
File fingerprint
SHA-256: 8a74eb3ac1ac0ff235260d47fcd0f46a5bef3fff4e813eb43487f3548230fe7c
#!/usr/bin/env python3
"""Exact six-point audit of the proposed braid-arrangement filtration bridge.
Requires SymPy. Every calculation is over Q or Q[x,y,z].
"""
from itertools import permutations
import sympy as sp
def inversion_sign(word):
inversions = sum(
word[i] > word[j]
for i in range(len(word))
for j in range(i + 1, len(word))
)
return -1 if inversions % 2 else 1
def main():
# Five-point positive control. With (s12,s13,s23)=(2,3,-5), the hard
# invariants are (s1P,s2P,s3P)=(-5,3,2). The natural diagonal turns the
# degree-one row into the unique sum-zero chamber function.
G5 = sp.Matrix([[2, -3]])
R5 = sp.Matrix([[4, 9]])
D5 = sp.diag(sp.Rational(1, 2), sp.Rational(1, 3))
assert G5 * D5 == sp.Matrix([[1, -1]])
assert G5.col_join(R5).rank() == 2
# A valid generic five-hard-particle specialization. The six pairwise
# invariants sum to zero, so P=-(p1+p2+p3+p4) is massless.
s12, s13, s14, s23, s24 = map(sp.Rational, (2, 3, 5, 7, 11))
s34 = -(s12 + s13 + s14 + s23 + s24)
sij = {
(1, 2): s12,
(1, 3): s13,
(1, 4): s14,
(2, 3): s23,
(2, 4): s24,
(3, 4): s34,
}
def s_pair(a, b):
return sij[tuple(sorted((a, b)))]
p = {a: -sum(s_pair(a, b) for b in range(1, 5) if b != a) for a in range(1, 5)}
def u(a, b, c):
return 2 * s_pair(b, c) + 2 * p[c] + p[b]
def v(a, b, c):
return u(a, b, c) + 2 * s_pair(a, c)
# Dong--Stieberger (5.11) and the two degree-one rows in (5.12), in
# column order 234,243,324,342,423,432. The irrelevant factor 1/2 in
# R_6 is retained.
G = sp.Matrix(
[
[p[4], -p[3], 0, 0, -p[3], p[2]],
[0, 0, p[4], -p[2], p[3], -p[2]],
]
)
R = sp.Rational(1, 2) * sp.Matrix(
[
[p[4] ** 2, -p[3] * u(2, 3, 4), 0, 0, -p[3] * v(2, 3, 4), -p[2] ** 2],
[0, 0, p[4] ** 2, -p[2] * u(3, 2, 4), -p[3] ** 2, -p[2] * v(3, 2, 4)],
[-p[4] * u(2, 4, 3), p[3] ** 2, -p[4] * v(2, 4, 3), -p[2] ** 2, 0, 0],
]
)
F2 = G.col_join(R)
chambers = list(permutations((2, 3, 4)))
pairs = ((2, 3), (2, 4), (3, 4))
one = sp.ones(6, 1)
heavisides = [
sp.Matrix([int(word.index(a) < word.index(b)) for word in chambers])
for a, b in pairs
]
Q = sp.Matrix.hstack(one, *heavisides)
assert s34 == -28
assert [p[i] for i in range(1, 5)] == [-10, -20, 18, 12]
assert F2.rank() == 5
kernel = F2.nullspace()
assert len(kernel) == 1
d = kernel[0]
assert list(d) == [
sp.Rational(10, 13),
sp.Rational(20, 39),
sp.Rational(-10, 7),
sp.Rational(6, 7),
sp.Rational(-10, 9),
sp.Rational(1),
]
diagonal_defect = G * sp.diag(*d) * Q
assert list(diagonal_defect[:, 0]) == [0, 0]
assert list(diagonal_defect[:, 1]) == [20, -20]
assert diagonal_defect != sp.zeros(2, 4)
# The type-A2 Varchenko matrix: entry (sigma,tau) is the product of the
# weights of the hyperplanes separating the two chambers.
x, y, z = sp.symbols("x y z")
weights = dict(zip(pairs, (x, y, z)))
def side(word, pair):
a, b = pair
return word.index(a) < word.index(b)
V = sp.Matrix(
6,
6,
lambda i, j: sp.prod(
weights[pair]
for pair in pairs
if side(chambers[i], pair) != side(chambers[j], pair)
),
)
# Already the first EYM row would have to annihilate 1,h23,h24,h34.
first_row_equations = [sp.expand(e) for e in list(G[0, :] * V * Q)]
assert list(sp.groebner(first_row_equations, x, y, z, order="lex")) == [1]
# A transparent elimination certificate for the same contradiction.
# Treat xy,x,y,1 as four linear unknowns for fixed z.
M = sp.Matrix(
[
[-22 * z - 13, -18 * z - 13, -13 * z - 18, -13 * z - 22],
[-10 * z - 10, -10, -3 * z - 18, -3 * z - 12],
[-19 * z - 10, 6 - 9 * z, -19 * z - 9, -3 * z - 3],
[6 - 28 * z, 6 - 18 * z, -19 * z, 6 - 19 * z],
]
)
det_expected = -64 * (z - 1) * (z + 1) * (189 * z**2 - 608 * z + 189)
assert sp.factor(M.det() - det_expected) == 0
adj_column = M.adjugate()[:, 3]
segre_defect = sp.factor(adj_column[0] * adj_column[3] - adj_column[1] * adj_column[2])
quartic = 7551 * z**4 + 29650 * z**3 - 70178 * z**2 + 29650 * z + 7551
assert sp.factor(segre_defect + 64 * (z + 1) ** 2 * quartic) == 0
assert sp.gcd(quartic, (z - 1) * (189 * z**2 - 608 * z + 189)) == 1
special_solution = sp.linsolve(
(M.subs(z, -1)[:, :3], -M.subs(z, -1)[:, 3]),
sp.symbols("A B C"),
)
assert special_solution == sp.FiniteSet((sp.Rational(-1), sp.Rational(9, 5), sp.Rational(-9, 5)))
# Check the only canonical one-dimensional character on either side.
sign = sp.diag(*(inversion_sign(word) for word in chambers))
identity = sp.eye(6)
twist_certificates = {}
for left_name, left in (("plain", identity), ("sign", sign)):
for right_name, right in (("plain", identity), ("sign", sign)):
equations = [sp.expand(e) for e in list(G * left * V * right * Q)]
basis = list(sp.groebner(equations, x, y, z, order="lex"))
twist_certificates[(left_name, right_name)] = basis
assert basis == [1]
determinant_factors = [
p[1], p[2], p[3], p[4], s12 + s34, s13 + s24, s14 + s23
]
assert all(value != 0 for value in determinant_factors)
print("five-point positive control G5*D5 =", (G5 * D5).tolist())
print("hard-particle invariants:", [s12, s13, s14, s23, s24, s34])
print("s_iP:", [p[i] for i in range(1, 5)])
print("G6 =", G.tolist())
print("R6 =", R.tolist())
print("rank([G6;R6]) =", F2.rank())
print("right kernel =", list(d))
print("diagonal Heaviside defect G6*diag(d)*[1,h23,h24,h34] =", diagonal_defect.tolist())
print("Varchenko first-row ideal Groebner basis =", list(sp.groebner(first_row_equations, x, y, z, order="lex")))
print("linearized determinant =", sp.factor(M.det()))
print("Segre defect =", segre_defect)
print("z=-1 linear solution (xy,x,y) =", special_solution)
print("sign-twist Groebner certificates =", twist_certificates)
print("six-point reconstruction factors =", determinant_factors)
print("all exact six-point obstruction checks passed")
if __name__ == "__main__":
main()