""" Self-Play Conjecture Generator for Zeta Zeros =============================================== Inspired by STP (Self-play Theorem Prover, arXiv:2502.00212) and Bourbaki (arXiv:2507.02726) Key idea: A dual-role system where: - CONJECTURER generates hypotheses about zero properties - PROVER tests them numerically with 100k zeros - Feedback loop: verified conjectures strengthen the conjecturer; falsified ones teach it what NOT to propose This creates its own curriculum of "barely verifiable" conjectures. """ import numpy as np from typing import Dict, List, Tuple, Callable import random from scipy import stats as sp_stats class Conjecture: def __init__(self, statement: str, test_func: Callable, category: str): self.statement = statement self.test_func = test_func self.category = category self.verification_result = None self.confidence = 0.0 class SelfPlayConjectureEngine: """ Generates, tests, and refines conjectures about zeta zeros. """ def __init__(self, zeros: List[float]): self.zeros = np.array(zeros) self.spacings = np.diff(self.zeros) self.normalized = self.spacings / np.mean(self.spacings) self.results = {} self.verified_conjectures = [] self.falsified_conjectures = [] def _generate_conjectures(self, n_conjectures: int = 20) -> List[Conjecture]: """Generate candidate conjectures based on observed patterns.""" conjectures = [] # Conjecture 1: Spacing bounds max_s = np.max(self.normalized) conjectures.append(Conjecture( f"All normalized spacings s_n satisfy s_n > 0.01 (observed min={np.min(self.normalized):.5f})", lambda: np.all(self.normalized > 0.01), "spacing_bounds" )) # Conjecture 2: Mean spacing trend windows = [100, 1000, 10000, 50000, 100000] means = [np.mean(self.normalized[:w]) for w in windows if w <= len(self.normalized)] conjectures.append(Conjecture( f"Mean normalized spacing converges to 1.0 (current: {means[-1]:.6f})", lambda: abs(means[-1] - 1.0) < 0.01, "mean_convergence" )) # Conjecture 3: Variance bound (GUE prediction: ~0.178) var = np.var(self.normalized) conjectures.append(Conjecture( f"Variance of normalized spacings < 0.2 (GUE prediction: ~0.178, observed: {var:.6f})", lambda: var < 0.2, "variance" )) # Conjecture 4: Arithmetic progression avoidance def test_ap(): n_test = min(5000, len(self.zeros)) ap_count = 0 for i in range(n_test - 2): d1 = self.zeros[i+1] - self.zeros[i] d2 = self.zeros[i+2] - self.zeros[i+1] if abs(d1 - d2) < 0.001: ap_count += 1 return ap_count < 10 conjectures.append(Conjecture( "No arithmetic progressions among zeros (< 10 near-AP in first 5000)", test_ap, "arithmetic_progressions" )) # Conjecture 5: Repulsion (GUE level repulsion) small_count = np.sum(self.normalized < 0.1) conjectures.append(Conjecture( f"Level repulsion: fewer than 5% of spacings < 0.1 (observed: {small_count/len(self.normalized):.4%})", lambda: small_count / len(self.normalized) < 0.05, "level_repulsion" )) # Conjecture 6: Skewness (GUE: ~0.09) skew = sp_stats.skew(self.normalized) conjectures.append(Conjecture( f"Skewness of spacings matches GUE (~0.09, observed: {skew:.4f})", lambda: abs(skew - 0.09) < 0.05, "skewness" )) # Conjecture 7: Ratio of consecutive spacings ratios = self.normalized[1:] / self.normalized[:-1] conjectures.append(Conjecture( f"Max consecutive spacing ratio < 10 (observed: {np.max(ratios):.2f})", lambda: np.max(ratios) < 10, "ratio_bound" )) # Conjecture 8: Long-range correlation decay def test_correlation_decay(): max_lag = 100 autocorrs = [] for lag in range(1, max_lag + 1): if lag < len(self.normalized): c = np.corrcoef(self.normalized[:-lag], self.normalized[lag:])[0, 1] autocorrs.append(c) return all(abs(c) < 0.1 for c in autocorrs) conjectures.append(Conjecture( "Long-range correlations decay to < 0.1 for all lags > 0", test_correlation_decay, "correlation" )) # Conjecture 9: Sum of spacings exactness total = np.sum(self.spacings) predicted = self.zeros[-1] - self.zeros[0] conjectures.append(Conjecture( f"Sum of spacings equals γ_N - γ_1 exactly (error: {abs(total - predicted):.10f})", lambda: abs(total - predicted) < 1e-6, "telescoping" )) # Conjecture 10: Even-odd symmetry in spacing distribution n_half = len(self.normalized) // 2 first_half = self.normalized[:n_half] second_half = self.normalized[n_half:] conjectures.append(Conjecture( "First-half and second-half spacing distributions are statistically similar (KS test)", lambda: sp_stats.ks_2samp(first_half, second_half)[1] > 0.01, "symmetry" )) return conjectures[:n_conjectures] def run_self_play(self, n_rounds: int = 3) -> Dict: """ Run self-play: generate conjectures, test, learn from results. """ print(f" [SelfPlay] Running {n_rounds} rounds of conjecture generation...") all_verified = [] all_falsified = [] round_results = [] for round_idx in range(n_rounds): conjectures = self._generate_conjectures() verified = 0 falsified = 0 results = [] for c in conjectures: try: result = c.test_func() c.verification_result = result c.confidence = 1.0 if result else 0.0 if result: verified += 1 all_verified.append(c) else: falsified += 1 all_falsified.append(c) results.append({ 'statement': c.statement, 'verified': result, 'category': c.category, }) except Exception as e: results.append({ 'statement': c.statement, 'verified': False, 'error': str(e), 'category': c.category, }) round_results.append({ 'round': round_idx + 1, 'verified': verified, 'falsified': falsified, 'results': results, }) print(f" Round {round_idx + 1}: {verified} verified, {falsified} falsified") # Compute statistics categories = {} for c in all_verified + all_falsified: cat = c.category if cat not in categories: categories[cat] = {'verified': 0, 'falsified': 0} if c.verification_result: categories[cat]['verified'] += 1 else: categories[cat]['falsified'] += 1 self.results = { 'strategy': 'self_play_conjecture_generator', 'n_rounds': n_rounds, 'total_verified': len(all_verified), 'total_falsified': len(all_falsified), 'verification_rate': len(all_verified) / max(len(all_verified) + len(all_falsified), 1), 'round_results': round_results, 'category_breakdown': categories, 'verified_conjectures': [c.statement for c in all_verified], 'falsified_conjectures': [c.statement for c in all_falsified], } return self.results def summary(self) -> str: r = self.results s = f"Self-Play Conjecture Engine\n{'='*50}\n" s += f"Rounds: {r['n_rounds']}\n" s += f"Total verified: {r['total_verified']}, falsified: {r['total_falsified']}\n" s += f"Verification rate: {r['verification_rate']:.1%}\n" s += "\nVerified conjectures:\n" for c in r['verified_conjectures'][:5]: s += f" ✓ {c[:80]}\n" if r['falsified_conjectures']: s += "\nFalsified conjectures (learn from these):\n" for c in r['falsified_conjectures'][:3]: s += f" ✗ {c[:80]}\n" return s