| """ |
| 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 |
| 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" |
|
|
|
|
| |
| |
| |
|
|
| 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() |
|
|
|
|
| |
| |
| |
|
|
| 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) |
|
|
| V = M_avg.shape[0] |
| claimed = [False] * V |
| pairs = [] |
|
|
| |
| |
| 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() |
|
|
| for cos_val, i, j in candidates: |
| if claimed[i] or claimed[j]: |
| continue |
| |
| 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: |
| |
| 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: |
| |
| representatives.append(unit[i]) |
| break |
| else: |
| continue |
| |
|
|
| |
| representatives = [] |
| for i, j in pairs: |
| |
| |
| merged = unit[i] - unit[j] |
| merged = merged / max(np.linalg.norm(merged), 1e-12) |
| |
| 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() |
| |
| 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) |
|
|
|
|
| |
| |
| |
|
|
| 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) |
| |
| |
| 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): |
| |
| x = rng.randn(n_axes, D) |
| x = x / np.linalg.norm(x, axis=1, keepdims=True) |
| |
| 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}") |
|
|
| |
| 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}") |
|
|
| |
| 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_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}") |
|
|
| |
| 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' |
|
|
| |
| 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)") |
|
|
| |
| |
| |
| 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(), |
| } |
|
|
|
|
| |
| |
| |
|
|
| def plot_projective(M_avg, axes, pairs, unpaired, results, output_path): |
| fig = plt.figure(figsize=(18, 12)) |
|
|
| |
| 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) |
|
|
| |
| 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') |
|
|
| |
| 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) |
| |
| 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) |
| |
| 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 Γ)') |
|
|
| |
| 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) |
| |
| 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Β²') |
|
|
| |
| 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) |
|
|
| |
| 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) |
|
|
| |
| 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']}") |
|
|
| |
| ax6 = fig.add_subplot(2, 3, 6) |
| ax6.axis('off') |
|
|
| |
| 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() |
|
|
|
|
| |
| |
| |
|
|
| 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") |
|
|
| |
| results = test_axis_distribution(axes, "G-Cand projective axes") |
|
|
| |
| 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}") |
|
|
| |
| 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() |