geolip-svae-implicit-solver-experiments / 11_test_claim_probe.py
AbstractPhil's picture
Create 11_test_claim_probe.py
f4ce5fa verified
"""
implicit_solver/A0_projective_reprobe.py
=========================================
Test claim 3: G-Cand is actually a 14-axis ℝPΒ² solver, not a 32-point SΒ² solver.
Method
------
1. Load G-Cand (Q-rank09, V=32, D=3) β€” already trained sphere-solver.
2. Collect M tensor as before β€” 512 samples Γ— 32 rows Γ— 3 dims.
3. Identify antipodal pairs in the canonical M_avg arrangement:
row i and row j form a pair if cos(M_avg[i], M_avg[j]) < -0.9
4. Collapse: for each pair, pick canonical representative (the one with
positive first nonzero coordinate). Yields up to 16 axis representatives.
5. Re-run v2 probe metrics under projective geometry:
- Pairwise angles wrapped to [0, Ο€/2] via ΞΈ β†’ min(ΞΈ, Ο€ - ΞΈ)
- Uniform ℝPΒ² baseline: pairwise angles peak at Ο€/4 (not Ο€/2)
- Cluster, stability, antipodal-of-antipodal (testing if axes themselves
have further antipodal structure within ℝPΒ²)
Predicted outcomes
------------------
A. CLEAN PROJECTIVE: 14 axes uniformly cover ℝPΒ², pairwise angles peak at
Ο€/4, no further antipodal collapse.
β†’ G-Cand is a clean 14-axis ℝPΒ² solver. Sphere-norm was the wrong
reading. The true geometry is projective.
B. STILL DEGENERATE: 14 axes show further structure (clustering, secondary
antipodal pairs, non-uniform).
β†’ G-Cand is structured beyond simple ℝPΒ² uniform. Some other geometry
applies, or the antipodal collapse hypothesis is incomplete.
C. ANTI-PROJECTIVE: 14 axes are NOT uniformly distributed on ℝPΒ²; they
show strong clustering or aligned-direction patterns.
β†’ The "spindle collapse" was real but isn't ℝPΒ² either. The geometry is
something more degenerate (line, plane subset, etc.)
Cost
----
Same trained checkpoint, different probe math. ~10 seconds.
Output
------
/content/implicit_solver_reports/A0_projective_reprobe.json
/content/implicit_solver_reports/A0_projective_reprobe.png
"""
import json
import math
from pathlib import Path
import numpy as np
import torch
import torch.nn.functional as F
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D # noqa
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
CKPT_DIR = Path("/content/phaseQ_reports")
RANK09_CKPT = CKPT_DIR / "Q_rank09_h64_V32_D3_dp0_nx0_adam" / "epoch_1_checkpoint.pt"
OUTPUT_DIR = Path("/content/implicit_solver_reports")
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
OUTPUT_PLOT = OUTPUT_DIR / "A0_projective_reprobe.png"
OUTPUT_JSON = OUTPUT_DIR / "A0_projective_reprobe.json"
# ════════════════════════════════════════════════════════════════════
# Loading
# ════════════════════════════════════════════════════════════════════
def load_g_cand():
cfgs = get_phaseQ_configs()
cfg_dict = next(c for c in cfgs if 'rank09' in c['variant'])
cfg = build_run_config(cfg_dict)
overrides = cfg_dict['overrides']
model = PatchSVAE_F_Ablation(
matrix_v=cfg.matrix_v, D=cfg.D, patch_size=cfg.patch_size,
hidden=cfg.hidden, depth=cfg.depth,
n_cross_layers=cfg.n_cross_layers, n_heads=cfg.n_heads,
max_alpha=overrides.get('max_alpha', cfg.max_alpha),
alpha_init=cfg.alpha_init,
activation=overrides.get('activation', 'gelu'),
row_norm=overrides.get('row_norm', 'sphere'),
svd_mode=overrides.get('svd', 'fp64'),
linear_readout=overrides.get('linear_readout', False),
match_params=overrides.get('match_params', True),
init_scheme=overrides.get('init', 'orthogonal'),
)
ckpt = torch.load(RANK09_CKPT, map_location='cpu', weights_only=False)
state_dict = (
ckpt.get('model_state')
or ckpt.get('model_state_dict')
or ckpt.get('state_dict')
or ckpt
)
model.load_state_dict(state_dict)
model.eval()
return model, cfg
def collect_per_sample_M(model, cfg, n_batches=8, batch_size=64):
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = model.to(device)
ds = OmegaNoiseDataset(
size=n_batches * batch_size, img_size=cfg.img_size,
allowed_types=[0])
loader = torch.utils.data.DataLoader(ds, batch_size=batch_size, shuffle=False)
all_M = []
with torch.no_grad():
for imgs, _ in loader:
imgs = imgs.to(device)
out = model(imgs)
M_patch0 = out['svd']['M'][:, 0]
all_M.append(M_patch0.cpu())
return torch.cat(all_M, dim=0).numpy()
# ════════════════════════════════════════════════════════════════════
# Antipodal pair identification + projective collapse
# ════════════════════════════════════════════════════════════════════
def identify_antipodal_pairs(M_avg, threshold=-0.9):
"""For each row, find its antipodal partner (cos < threshold).
Returns (pairs, unpaired):
pairs: list of (i, j) tuples where i < j and rows i, j are antipodal
unpaired: list of row indices with no antipodal partner
Greedy matching: each row pairs with its strongest antipodal candidate
that hasn't been claimed yet.
"""
norms = np.linalg.norm(M_avg, axis=1, keepdims=True)
unit = M_avg / np.clip(norms, 1e-12, None)
cosines = unit @ unit.T
np.fill_diagonal(cosines, 1.0) # exclude self
V = M_avg.shape[0]
claimed = [False] * V
pairs = []
# Sort rows by their strongest antipodal candidate (most negative cos)
# Greedy claim β€” strongest pairings get priority
candidates = []
for i in range(V):
best_j = int(cosines[i].argmin())
best_cos = float(cosines[i, best_j])
if best_cos < threshold:
candidates.append((best_cos, i, best_j))
candidates.sort() # most negative first
for cos_val, i, j in candidates:
if claimed[i] or claimed[j]:
continue
# Verify symmetry: j's strongest is also i (or close enough)
if cosines[j].argmin() == i or cosines[j, i] < threshold:
pairs.append((min(i, j), max(i, j)))
claimed[i] = True
claimed[j] = True
unpaired = [i for i in range(V) if not claimed[i]]
return pairs, unpaired
def collapse_to_axes(M_avg, pairs, unpaired):
"""Pick canonical representative for each pair: the row with positive
first nonzero coordinate. Unpaired rows stay as-is.
Returns axes [n_axes, D] where n_axes = len(pairs) + len(unpaired)."""
norms = np.linalg.norm(M_avg, axis=1, keepdims=True)
unit = M_avg / np.clip(norms, 1e-12, None)
representatives = []
for i, j in pairs:
# Pick the row whose first nonzero coordinate is positive
for r in [unit[i], unit[j]]:
for k in range(r.shape[0]):
if abs(r[k]) > 1e-6:
chosen = r if r[k] > 0 else -r
representatives.append(chosen)
break
else:
# All zeros (shouldn't happen on sphere) β€” pick row i
representatives.append(unit[i])
break
else:
continue
# We added one rep; continue to next pair
# Actually the above structure is wrong β€” let me redo cleanly:
representatives = []
for i, j in pairs:
# Average of row_i and -row_j (since they're antipodal, this enhances
# the shared axis direction)
merged = unit[i] - unit[j]
merged = merged / max(np.linalg.norm(merged), 1e-12)
# Canonicalize sign: first nonzero coord positive
for k in range(merged.shape[0]):
if abs(merged[k]) > 1e-6:
if merged[k] < 0:
merged = -merged
break
representatives.append(merged)
for i in unpaired:
r = unit[i].copy()
# Same canonical sign convention
for k in range(r.shape[0]):
if abs(r[k]) > 1e-6:
if r[k] < 0:
r = -r
break
representatives.append(r)
return np.array(representatives)
# ════════════════════════════════════════════════════════════════════
# Projective metrics
# ════════════════════════════════════════════════════════════════════
def projective_pairwise_angles(axes):
"""Angles between axes on ℝP^(D-1). Each axis is a line through origin,
so angle between two axes is min(ΞΈ, Ο€-ΞΈ) ∈ [0, Ο€/2]."""
n = axes.shape[0]
cosines = axes @ axes.T
cosines = np.clip(cosines, -1, 1)
# On ℝP^(D-1): two axes are equivalent under sign flip
# so the "true" angle is the smaller of ΞΈ and Ο€-ΞΈ
raw_angles = np.arccos(cosines)
proj_angles = np.minimum(raw_angles, np.pi - raw_angles)
triu = np.triu_indices(n, k=1)
return proj_angles[triu]
def uniform_rp_pairwise_angle_baseline(D, n_axes, n_trials=10):
"""Predicted pairwise-angle distribution for n_axes uniformly placed
on ℝP^(D-1). Sample uniformly on S^(D-1), antipodally identify, compute
pairwise angles."""
rng = np.random.RandomState(0)
means = []
for _ in range(n_trials):
# Sample n_axes uniformly on S^(D-1)
x = rng.randn(n_axes, D)
x = x / np.linalg.norm(x, axis=1, keepdims=True)
# Canonicalize to upper hemisphere (positive first coord)
for k in range(D):
sign = np.sign(x[:, k])
sign[sign == 0] = 1
mask = (x[:, k] != 0) & (np.all(x[:, :k] == 0, axis=1) if k > 0 else np.ones(n_axes, dtype=bool))
x[mask] = x[mask] * sign[mask, None]
if np.all(x[:, k] != 0):
break
angles = projective_pairwise_angles(x)
means.append(angles.mean())
return float(np.mean(means))
def test_axis_distribution(axes, label):
"""Run all probe metrics on the projective axes."""
D = axes.shape[1]
n = axes.shape[0]
print(f"\n[{label}]")
print(f" Axes shape: {axes.shape}")
# Pairwise angles in projective space
proj_angles = projective_pairwise_angles(axes)
print(f" Projective pairwise angles (radians, max possible Ο€/2={math.pi/2:.3f}):")
print(f" mean: {proj_angles.mean():.3f}")
print(f" median: {np.median(proj_angles):.3f}")
print(f" min: {proj_angles.min():.3f}")
print(f" max: {proj_angles.max():.3f}")
# Predicted uniform baseline for ℝP^(D-1)
uniform_baseline = uniform_rp_pairwise_angle_baseline(D, n)
deviation = proj_angles.mean() - uniform_baseline
print(f" Uniform ℝP^{D-1} baseline (n={n}): {uniform_baseline:.3f}")
print(f" Deviation: {deviation:+.3f} "
f"({'CLOSE TO UNIFORM' if abs(deviation) < 0.05 else 'NON-UNIFORM'})")
# Fraction at small angles (axis clustering)
fraction_clustered = (proj_angles < 0.3).mean()
fraction_perp = ((proj_angles > math.pi/4 - 0.15) &
(proj_angles < math.pi/4 + 0.15)).mean()
print(f" Fraction near-zero (axes parallel): {fraction_clustered:.3f}")
print(f" Fraction near Ο€/4 (uniform peak): {fraction_perp:.3f}")
# Cluster analysis on the axes themselves (not on the original M)
sils = []
for k in range(2, min(8, n)):
try:
km = KMeans(n_clusters=k, n_init=10, random_state=42)
labels = km.fit_predict(axes)
if len(set(labels)) >= 2:
sils.append((k, silhouette_score(axes, labels)))
except Exception:
pass
if sils:
best_k, best_sil = max(sils, key=lambda x: x[1])
print(f" Best cluster k={best_k}, silhouette={best_sil:.3f}")
cluster_verdict = (
'STRONG (real clusters)' if best_sil > 0.5 else
'WEAK (some structure)' if best_sil > 0.3 else
'NONE (continuous distribution)'
)
print(f" Cluster verdict: {cluster_verdict}")
else:
best_k, best_sil = None, None
cluster_verdict = 'N/A'
# Effective rank of the axis matrix
sv = np.linalg.svd(axes, compute_uv=False)
sv_norm = sv / sv.sum()
erank = math.exp(-(sv_norm * np.log(sv_norm + 1e-12)).sum())
print(f" Effective rank: {erank:.2f} of {D} possible "
f"({erank/D*100:.0f}% utilization)")
# Test for SECONDARY antipodal structure within the axes
# If axes still show antipodal pairs, the geometry is more degenerate
# than ℝP^(D-1) β€” possibly ℝP^(D-1) / β„€β‚‚ or projection to even lower dim
cos_axes = axes @ axes.T
np.fill_diagonal(cos_axes, 1.0)
most_anti = cos_axes.min(axis=1)
secondary_anti = (most_anti < -0.9).sum() // 2
print(f" Secondary antipodal pairs (axes paired again): "
f"{secondary_anti}/{n//2}")
return {
'n_axes': int(n),
'D': int(D),
'proj_angle_mean': float(proj_angles.mean()),
'proj_angle_median': float(np.median(proj_angles)),
'proj_angle_min': float(proj_angles.min()),
'proj_angle_max': float(proj_angles.max()),
'uniform_baseline': uniform_baseline,
'deviation_from_uniform': float(deviation),
'fraction_clustered': float(fraction_clustered),
'fraction_near_pi_over_4': float(fraction_perp),
'best_cluster_k': best_k,
'best_silhouette': best_sil,
'cluster_verdict': cluster_verdict,
'effective_rank': float(erank),
'utilization': float(erank / D),
'secondary_antipodal_pairs': int(secondary_anti),
'proj_angles_subset': proj_angles[:200].tolist(),
}
# ════════════════════════════════════════════════════════════════════
# Plotting
# ════════════════════════════════════════════════════════════════════
def plot_projective(M_avg, axes, pairs, unpaired, results, output_path):
fig = plt.figure(figsize=(18, 12))
# Panel 1: Original M_avg on SΒ² with pairings highlighted
ax1 = fig.add_subplot(2, 3, 1, projection='3d')
norms = np.linalg.norm(M_avg, axis=1, keepdims=True)
unit = M_avg / np.clip(norms, 1e-12, None)
# Wireframe sphere
u = np.linspace(0, 2*np.pi, 20)
v = np.linspace(0, np.pi, 20)
x_s = np.outer(np.cos(u), np.sin(v))
y_s = np.outer(np.sin(u), np.sin(v))
z_s = np.outer(np.ones_like(u), np.cos(v))
ax1.plot_wireframe(x_s, y_s, z_s, alpha=0.1, color='gray')
# Color paired rows by pair index
pair_colors = plt.cm.tab20(np.linspace(0, 1, max(len(pairs), 1)))
for k, (i, j) in enumerate(pairs):
color = pair_colors[k]
ax1.scatter(unit[i, 0], unit[i, 1], unit[i, 2],
c=[color], s=80, edgecolors='black', linewidths=0.5)
ax1.scatter(unit[j, 0], unit[j, 1], unit[j, 2],
c=[color], s=80, edgecolors='black', linewidths=0.5)
# Line connecting the antipodal pair
ax1.plot([unit[i, 0], unit[j, 0]],
[unit[i, 1], unit[j, 1]],
[unit[i, 2], unit[j, 2]],
color=color, alpha=0.3, linewidth=0.8)
# Unpaired rows in red
for i in unpaired:
ax1.scatter(unit[i, 0], unit[i, 1], unit[i, 2],
c='red', marker='x', s=100, linewidths=2)
ax1.set_title(f'Original M_avg on SΒ²\n'
f'{len(pairs)} antipodal pairs (colored), '
f'{len(unpaired)} unpaired (red Γ—)')
# Panel 2: Collapsed axes on upper hemisphere (canonical reps)
ax2 = fig.add_subplot(2, 3, 2, projection='3d')
ax2.plot_wireframe(x_s, y_s, z_s, alpha=0.1, color='gray')
for k, ax in enumerate(axes):
ax2.scatter(ax[0], ax[1], ax[2], c=[plt.cm.tab20(k % 20)],
s=120, edgecolors='black', linewidths=0.5)
# Draw line through origin to show it's an AXIS not a point
ax2.plot([-ax[0], ax[0]], [-ax[1], ax[1]], [-ax[2], ax[2]],
color=plt.cm.tab20(k % 20), alpha=0.4, linewidth=1.0)
ax2.set_title(f'Collapsed axes (n={axes.shape[0]})\n'
f'Each line through origin = one axis on ℝPΒ²')
# Panel 3: Projective angle distribution vs uniform baseline
ax3 = fig.add_subplot(2, 3, 3)
proj_angles = results['proj_angles_subset']
ax3.hist(proj_angles, bins=30, density=True, alpha=0.7,
color='steelblue', label='G-Cand projective')
ax3.axvline(results['uniform_baseline'], color='red', linestyle='--',
label=f"uniform ℝPΒ² baseline ({results['uniform_baseline']:.3f})")
ax3.axvline(math.pi/4, color='green', linestyle=':',
label=f'Ο€/4 = {math.pi/4:.3f}')
ax3.set_xlabel('Projective pairwise angle (radians, max Ο€/2)')
ax3.set_ylabel('Density')
ax3.set_title(f'Projective angle distribution\n'
f"deviation: {results['deviation_from_uniform']:+.3f}")
ax3.legend(fontsize=8)
# Panel 4: Cluster silhouette across k
ax4 = fig.add_subplot(2, 3, 4)
if results['best_cluster_k'] is not None:
ks_sils = []
for k in range(2, min(8, axes.shape[0])):
try:
km = KMeans(n_clusters=k, n_init=10, random_state=42)
labels = km.fit_predict(axes)
if len(set(labels)) >= 2:
ks_sils.append((k, silhouette_score(axes, labels)))
except Exception:
pass
if ks_sils:
ks, sils = zip(*ks_sils)
ax4.plot(ks, sils, 'o-', color='purple', markersize=8)
ax4.axhline(0.5, color='red', linestyle='--', alpha=0.5,
label='strong cluster')
ax4.axhline(0.3, color='orange', linestyle='--', alpha=0.5,
label='weak cluster')
ax4.set_xlabel('k (number of clusters)')
ax4.set_ylabel('silhouette score')
ax4.set_title(f"Axis clustering\n"
f"verdict: {results['cluster_verdict']}")
ax4.legend(fontsize=8)
ax4.grid(alpha=0.3)
# Panel 5: Effective rank bar
ax5 = fig.add_subplot(2, 3, 5)
sv = np.linalg.svd(axes, compute_uv=False)
ax5.bar([f'Οƒ{i+1}' for i in range(len(sv))], sv,
color=['red', 'orange', 'yellow'][:len(sv)])
ax5.set_ylabel('Singular value')
ax5.set_title(f"Singular values of axis matrix\n"
f"effective rank: {results['effective_rank']:.2f} "
f"of {results['D']}")
# Panel 6: Composite verdict
ax6 = fig.add_subplot(2, 3, 6)
ax6.axis('off')
# Decide composite verdict
is_uniform = abs(results['deviation_from_uniform']) < 0.05
is_clustered = (results['best_silhouette'] or 0) > 0.5
has_secondary_antipodal = results['secondary_antipodal_pairs'] >= 3
full_rank = results['utilization'] > 0.95
if is_uniform and not is_clustered and not has_secondary_antipodal and full_rank:
verdict = "βœ“ CLEAN ℝPΒ² SOLVER"
explanation = (
"G-Cand was a 14-axis projective-space solver all along.\n"
"Sphere-norm was the wrong reading β€” the true geometry\n"
"is uniform on ℝPΒ². Claim 3 SUPPORTED."
)
color = 'lightgreen'
elif is_uniform and not is_clustered and full_rank:
verdict = "βœ“ MOSTLY ℝPΒ², minor irregularities"
explanation = (
"Axes are roughly uniform on ℝPΒ² with some structure.\n"
"Claim 3 PARTIALLY SUPPORTED β€” projective interpretation\n"
"is the right space, but distribution isn't perfectly uniform."
)
color = 'palegreen'
elif is_clustered:
verdict = "βœ— STRUCTURED on ℝPΒ²"
explanation = (
"Axes show genuine cluster structure on ℝPΒ².\n"
"Not uniform, not random β€” something more specific.\n"
"May be a polytope on ℝPΒ² (less common) or other geometry."
)
color = 'lightyellow'
elif has_secondary_antipodal:
verdict = "βœ— FURTHER COLLAPSE"
explanation = (
"Even after antipodal collapse, axes show NEW antipodal pairs.\n"
"Geometry is more degenerate than ℝPΒ² β€” possibly lens space,\n"
"or D-effective lower than D=3."
)
color = 'mistyrose'
elif not full_rank:
verdict = "βœ— DEGENERATE β€” sub-rank"
explanation = (
"Axes don't span the full D=3 space.\n"
"Effective rank < 3 means rows live on a 2D plane or 1D line\n"
"in 3D space. Spindle hypothesis dimension-collapsed."
)
color = 'lightcoral'
else:
verdict = "? UNCLEAR"
explanation = (
"Mixed signals β€” re-examine the metrics individually.\n"
"Antipodal hypothesis neither confirmed nor cleanly refuted."
)
color = 'lightgray'
ax6.text(0.5, 0.85, verdict, ha='center', va='top',
fontsize=20, fontweight='bold',
bbox=dict(boxstyle='round', facecolor=color, alpha=0.8))
ax6.text(0.05, 0.55, explanation, ha='left', va='top', fontsize=11,
wrap=True, family='monospace')
metrics_summary = (
f"\n\nKey metrics:\n"
f" axes: {results['n_axes']}\n"
f" proj angle mean: {results['proj_angle_mean']:.3f}\n"
f" uniform baseline: {results['uniform_baseline']:.3f}\n"
f" deviation: {results['deviation_from_uniform']:+.3f}\n"
f" best cluster silhouette: {results['best_silhouette'] or 0:.3f}\n"
f" effective rank: {results['effective_rank']:.2f}/{results['D']}\n"
f" secondary antipodal: {results['secondary_antipodal_pairs']}"
)
ax6.text(0.05, 0.30, metrics_summary, ha='left', va='top',
fontsize=10, family='monospace')
plt.tight_layout()
plt.savefig(output_path, dpi=120, bbox_inches='tight')
plt.show()
# ════════════════════════════════════════════════════════════════════
# Main
# ════════════════════════════════════════════════════════════════════
def main():
print("=" * 70)
print("Projective re-probe of G-Cand (Q-rank09, V=32, D=3)")
print("Testing claim 3: trained sphere-solver is actually a ℝPΒ² solver")
print("=" * 70)
print("\nLoading G-Cand checkpoint...")
model, cfg = load_g_cand()
print(f" V={cfg.matrix_v}, D={cfg.D}, "
f"params={sum(p.numel() for p in model.parameters()):,}")
print("\nCollecting M tensor (512 gaussian samples)...")
all_M = collect_per_sample_M(model, cfg)
M_avg = all_M.mean(axis=0)
print(f" M_avg shape: {M_avg.shape}")
print("\nIdentifying antipodal pairs (cos < -0.9)...")
pairs, unpaired = identify_antipodal_pairs(M_avg, threshold=-0.9)
print(f" Found {len(pairs)} antipodal pairs")
print(f" Unpaired rows: {len(unpaired)}")
print(f" Total accounted: {2*len(pairs) + len(unpaired)} of {M_avg.shape[0]}")
print("\nCollapsing to projective axes...")
axes = collapse_to_axes(M_avg, pairs, unpaired)
print(f" Axes: {axes.shape[0]} representatives in {axes.shape[1]}-D")
# ── Run probe metrics under projective interpretation ──
results = test_axis_distribution(axes, "G-Cand projective axes")
# ── Save ──
output_data = {
'config': {
'variant': 'Q_rank09_h64_V32_D3_dp0_nx0_adam',
'V': cfg.matrix_v,
'D': cfg.D,
},
'antipodal_pairs_found': len(pairs),
'unpaired_rows': len(unpaired),
'total_axes': axes.shape[0],
'projective_metrics': results,
'pairs': [list(p) for p in pairs],
'unpaired': unpaired,
}
with open(OUTPUT_JSON, 'w') as f:
json.dump(output_data, f, indent=2, default=str)
print(f"\nSaved: {OUTPUT_JSON}")
plot_projective(M_avg, axes, pairs, unpaired, results, OUTPUT_PLOT)
print(f"Saved: {OUTPUT_PLOT}")
# ── Headline conclusion ──
print("\n" + "=" * 70)
print("CONCLUSION")
print("=" * 70)
is_uniform = abs(results['deviation_from_uniform']) < 0.05
is_clustered = (results['best_silhouette'] or 0) > 0.5
has_secondary_antipodal = results['secondary_antipodal_pairs'] >= 3
full_rank = results['utilization'] > 0.95
if is_uniform and not is_clustered and not has_secondary_antipodal and full_rank:
print("\nβœ“ CLAIM 3 SUPPORTED:")
print(" The 14 axes are uniformly distributed on ℝPΒ² with no")
print(" further collapse. G-Cand is a 14-axis projective solver.")
print(" The 'sphere-norm V=32 D=3' was a mislabeling of 14 axes.\n")
print(" IMPLICATION: For inference, project trained sphere outputs")
print(" to ℝP^(D-1) and read as axes, not points. The polygonal")
print(" geometry is implicit in the trained sphere-solver.")
elif is_clustered:
print("\nβœ— CLAIM 3 PARTIALLY REFUTED:")
print(" Axes have cluster structure on ℝPΒ² β€” they are not")
print(" uniformly distributed. Either the projective space isn't")
print(" the right reading, or the clusters reveal a finer polytope")
print(" structure (e.g., axes prefer specific directions on ℝPΒ²).")
elif has_secondary_antipodal:
print("\nβœ— CLAIM 3 REFUTED β€” geometry collapses further:")
print(" Axes show NEW antipodal pairs after the first collapse.")
print(" G-Cand has more degenerate geometry than ℝPΒ² β€” possibly")
print(" effective dimension < 3.")
elif not full_rank:
print("\nβœ— CLAIM 3 REFUTED β€” dimension collapse:")
print(" Effective rank of the axes is below 3. The trained model")
print(" used less than the full D=3 space.")
else:
print("\n? CLAIM 3 PARTIALLY SUPPORTED:")
print(" Axes are full-rank and don't show secondary collapse,")
print(" but distribution deviates from uniform ℝPΒ² baseline.")
print(" Some structure beyond simple uniform projection.")
return output_data
if __name__ == '__main__':
results = main()