| """ |
| 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 = [] |
|
|
| |
| 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" |
| )) |
|
|
| |
| 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" |
| )) |
|
|
| |
| 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" |
| )) |
|
|
| |
| 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" |
| )) |
|
|
| |
| 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" |
| )) |
|
|
| |
| 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" |
| )) |
|
|
| |
| 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" |
| )) |
|
|
| |
| 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" |
| )) |
|
|
| |
| 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" |
| )) |
|
|
| |
| 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") |
|
|
| |
| 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 |
|
|