mirror of
https://github.com/allaunthefox/SilverSight.git
synced 2026-07-30 17:16:16 +00:00
Adversarial review findings and fixes: - CRITICAL: v3 hash() -> deterministic_seed (SHA-256) for reproducibility - HIGH: EPS 0.05 -> 1e-5 (5 orders too wide) - HIGH: L-corridor jump at t=15->16 (dist 1.0) — smoothed rotation at (0.5,0.5) - HIGH: gerver_like self-intersecting — arcs meet at shared endpoint - MEDIUM: hammersley gap at arc junction — fixed endpoint alignment - MEDIUM/HIGH: gerver_like/hammersley now accept q parameter - LOW: reflect_y -> negate_y rename, dedup rounding consistency - de Grey 1581-vertex graph constructs correctly (verified: 1581 vertices, 7877 edges) - Hoffman bound: λ_max=12.09, λ_min=-7.74, χ≥3 (weak bound, expected) Build: lake build CoreFormalism.CRTSidon (3297 jobs, 0 errors)
237 lines
7.4 KiB
Python
237 lines
7.4 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
hn_hoffman_bound.py — Hadwiger-Nelson Hoffman bound via spectral method.
|
||
|
||
Computes χ(G) ≥ 1 - λ_max / λ_min for a unit-distance graph G.
|
||
|
||
Built-in graphs:
|
||
- Moser spindle (7 vertices, χ=4)
|
||
- de Grey 1581-vertex graph (χ=5) using coordinates from arXiv:1804.02385v3 §5.1
|
||
|
||
Usage:
|
||
python3 hn_hoffman_bound.py [--graph moser|degrey]
|
||
"""
|
||
import sys, json, math, hashlib, time
|
||
from pathlib import Path
|
||
|
||
HERE = Path(__file__).resolve().parent
|
||
OUT_DIR = HERE.parent / ".openresearch" / "artifacts"
|
||
OUT_DIR.mkdir(parents=True, exist_ok=True)
|
||
|
||
s3 = math.sqrt(3)
|
||
s11 = math.sqrt(11)
|
||
s33 = math.sqrt(33)
|
||
s12 = 2 * s3
|
||
|
||
# ── Coordinate set S from de Grey §5.1 ────────────────────────────
|
||
|
||
S_COORDS = [
|
||
(0, 0),
|
||
(1/3, 0),
|
||
(1, 0),
|
||
(2, 0),
|
||
((s33 - 3)/6, 0),
|
||
(1/2, 1/s12),
|
||
(1, 1/s3),
|
||
(3/2, s3/2),
|
||
(7/6, s11/6),
|
||
(1/6, (s12 - s11)/6),
|
||
(5/6, (s12 - s11)/6),
|
||
(2/3, (s11 - s3)/6),
|
||
(2/3, (3*s3 - s11)/6),
|
||
(s33/6, 1/s12),
|
||
((s33 + 3)/6, 1/s3),
|
||
((s33 + 1)/6, (3*s3 - s11)/6),
|
||
((s33 - 1)/6, (3*s3 - s11)/6),
|
||
((s33 + 1)/6, (s11 - s3)/6),
|
||
((s33 - 1)/6, (s11 - s3)/6),
|
||
((s33 - 2)/6, (2*s3 - s11)/6),
|
||
((s33 - 4)/6, (2*s3 - s11)/6),
|
||
((s33 + 13)/12, (s11 - s3)/12),
|
||
((s33 + 11)/12, (s3 + s11)/12),
|
||
((s33 + 9)/12, (s11 - s3)/4),
|
||
((s33 + 9)/12, (3*s3 + s11)/12),
|
||
((s33 + 7)/12, (s3 + s11)/12),
|
||
((s33 + 7)/12, (3*s3 - s11)/12),
|
||
((s33 + 5)/12, (5*s3 - s11)/12),
|
||
((s33 + 5)/12, (s11 - s3)/12),
|
||
((s33 + 3)/12, (3*s11 - 5*s3)/12),
|
||
((s33 + 3)/12, (s3 + s11)/12),
|
||
((s33 + 3)/12, (3*s3 - s11)/12),
|
||
((s33 + 1)/12, (s11 - s3)/12),
|
||
((s33 - 1)/12, (3*s3 - s11)/12),
|
||
((s33 - 3)/12, (s11 - s3)/12),
|
||
((15 - s33)/12, (s11 - s3)/4),
|
||
((15 - s33)/12, (7*s3 - 3*s11)/12),
|
||
((13 - s33)/12, (3*s3 - s11)/12),
|
||
((11 - s33)/12, (s11 - s3)/12),
|
||
]
|
||
|
||
def rotate(pt, angle):
|
||
c, s = math.cos(angle), math.sin(angle)
|
||
return (pt[0]*c - pt[1]*s, pt[0]*s + pt[1]*c)
|
||
|
||
def translate(pt, dx, dy):
|
||
return (pt[0]+dx, pt[1]+dy)
|
||
|
||
def negate_y(pt):
|
||
return (pt[0], -pt[1])
|
||
|
||
def is_unit(p, q, eps=1e-9):
|
||
d2 = (p[0]-q[0])**2 + (p[1]-q[1])**2
|
||
return abs(d2 - 1.0) < eps
|
||
|
||
def build_adjacency(points, eps=1e-9):
|
||
n = len(points)
|
||
adj = [[] for _ in range(n)]
|
||
for i in range(n):
|
||
pi = points[i]
|
||
for j in range(i+1, n):
|
||
if is_unit(pi, points[j], eps):
|
||
adj[i].append(j)
|
||
adj[j].append(i)
|
||
return adj
|
||
|
||
def remove_duplicates(points, eps=1e-9):
|
||
uniq = []
|
||
for p in points:
|
||
if not any(abs(p[0]-q[0])<eps and abs(p[1]-q[1])<eps for q in uniq):
|
||
uniq.append(p)
|
||
return uniq
|
||
|
||
def build_degrey_1581():
|
||
"""Construct de Grey's 1581-vertex graph per §5.1."""
|
||
# Step 1: S_a — rotate S by 60°, reflect y
|
||
sa = set()
|
||
angle60 = math.pi / 3
|
||
angle2 = 2 * math.asin(1/4)
|
||
angle_y = math.pi/2 + math.asin(1/8)
|
||
|
||
for x, y in S_COORDS:
|
||
for k in range(6):
|
||
pt = rotate((x, y), k * angle60)
|
||
sa.add((round(pt[0], 12), round(pt[1], 12)))
|
||
pt_r = negate_y(pt)
|
||
sa.add((round(pt_r[0], 12), round(pt_r[1], 12)))
|
||
|
||
sa_list = list(sa)
|
||
|
||
# Step 3: S_b = S_a rotated by 2*arcsin(1/4)
|
||
sb_list = [(round(rotate(p, angle2)[0], 12),
|
||
round(rotate(p, angle2)[1], 12)) for p in sa_list]
|
||
|
||
# Step 4: Y = union minus (1/3, 0) and (-1/3, 0)
|
||
y_set = set(sa_list) | set(sb_list)
|
||
y_set.discard((round(1/3, 12), 0.0))
|
||
y_set.discard((round(-1/3, 12), 0.0))
|
||
y_list = list(y_set)
|
||
|
||
# Step 5-6: Rotate Y about (-2, 0)
|
||
ya = [translate(rotate(translate(p, 2, 0), angle_y), -2, 0) for p in y_list]
|
||
yb = [translate(rotate(translate(p, 2, 0), math.pi - angle_y), -2, 0) for p in y_list]
|
||
ya = [(round(x, 12), round(y, 12)) for x, y in ya]
|
||
yb = [(round(x, 12), round(y, 12)) for x, y in yb]
|
||
|
||
# Step 7: G = Y_a ∪ Y_b
|
||
all_pts = remove_duplicates(ya + yb)
|
||
|
||
print(f" Total vertices before dedup: {len(ya)+len(yb)}")
|
||
print(f" After dedup: {len(all_pts)}")
|
||
|
||
# Build adjacency
|
||
adj = build_adjacency(all_pts)
|
||
n_edges = sum(len(nbrs) for nbrs in adj) // 2
|
||
|
||
return adj, all_pts, f"De Grey G ({len(all_pts)} vertices, {n_edges} edges)"
|
||
|
||
# ── Moser spindle ──────────────────────────────────────────────────
|
||
|
||
def moser_spindle():
|
||
s3 = math.sqrt(3)
|
||
pts = [(0,0), (1,0), (0.5,s3/2), (-0.5,s3/2), (-1,0), (-0.5,-s3/2), (0.5,-s3/2)]
|
||
adj = build_adjacency(pts)
|
||
n_edges = sum(len(nbrs) for nbrs in adj) // 2
|
||
return adj, pts, f"Moser spindle (7 vertices, {n_edges} edges)"
|
||
|
||
# ── Hoffman bound ──────────────────────────────────────────────────
|
||
|
||
def hoffman_bound(adj):
|
||
try:
|
||
import numpy as np
|
||
except ImportError:
|
||
print("ERROR: numpy required")
|
||
sys.exit(1)
|
||
|
||
n = len(adj)
|
||
A = np.zeros((n, n), dtype=np.float64)
|
||
for i in range(n):
|
||
for j in adj[i]:
|
||
A[i, j] = 1.0
|
||
|
||
eigenvals = np.linalg.eigvalsh(A)
|
||
lambda_max = eigenvals[-1]
|
||
lambda_min = eigenvals[0]
|
||
hb = 1.0 - lambda_max / lambda_min if lambda_min < 0 else float('inf')
|
||
|
||
return float(lambda_max), float(lambda_min), float(hb)
|
||
|
||
# ── Main ───────────────────────────────────────────────────────────
|
||
|
||
def main():
|
||
import argparse
|
||
parser = argparse.ArgumentParser()
|
||
parser.add_argument("--graph", choices=["moser", "degrey"], default="degrey")
|
||
args = parser.parse_args()
|
||
|
||
t0 = time.time()
|
||
|
||
if args.graph == "moser":
|
||
adj, pts, desc = moser_spindle()
|
||
else:
|
||
adj, pts, desc = build_degrey_1581()
|
||
|
||
n_vertices = len(adj)
|
||
n_edges = sum(len(nbrs) for nbrs in adj) // 2
|
||
|
||
print(f"Graph: {desc}")
|
||
if n_vertices < 3:
|
||
print(" Too few vertices.")
|
||
return
|
||
|
||
lambda_max, lambda_min, hb = hoffman_bound(adj)
|
||
chi_lower = math.ceil(hb) if math.isfinite(hb) else None
|
||
|
||
print(f"\nSpectral analysis:")
|
||
print(f" λ_max = {lambda_max:.6f}")
|
||
print(f" λ_min = {lambda_min:.6f}")
|
||
print(f" λ_max/|λ_min| = {lambda_max / abs(lambda_min):.6f}")
|
||
if chi_lower:
|
||
print(f"\n Hoffman bound: χ ≥ {hb:.6f}")
|
||
print(f" Rounded up: χ ≥ {chi_lower}")
|
||
|
||
elapsed = time.time() - t0
|
||
result = {
|
||
"experiment": "hn_hoffman_bound",
|
||
"graph": args.graph,
|
||
"description": desc,
|
||
"n_vertices": n_vertices,
|
||
"n_edges": n_edges,
|
||
"lambda_max": round(lambda_max, 12),
|
||
"lambda_min": round(lambda_min, 12),
|
||
"hoffman_bound": round(hb, 12) if math.isfinite(hb) else None,
|
||
"chi_lower_bound": chi_lower,
|
||
"elapsed_s": round(elapsed, 2),
|
||
"timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
||
}
|
||
content = json.dumps(result, indent=2)
|
||
result["sha256"] = hashlib.sha256(content.encode()).hexdigest()
|
||
|
||
out_path = OUT_DIR / "hn_hoffman_bound.json"
|
||
with open(out_path, "w") as f:
|
||
json.dump(result, f, indent=2)
|
||
print(f"\nResults → {out_path}")
|
||
print(f"SHA-256: {result['sha256']}")
|
||
print(f"Elapsed: {elapsed:.2f}s")
|
||
|
||
if __name__ == "__main__":
|
||
main()
|