| """ |
| PROBLEM 2: GUE Convergence Rate — HOW FAST Do Zeros Become Random? |
| ==================================================================== |
| BACKGROUND: Montgomery (1973) conjectured nearest-neighbor spacings match GUE. |
| Odlyzko verified this. But the RATE of convergence is UNKNOWN. |
| THIS HAS NEVER BEEN SYSTEMATICALLY MEASURED. |
| |
| Methodology: |
| 1. Take 100k Odlyzko zeros, split into windows [0,T] |
| 2. For each window, compute nearest-neighbor spacing distribution s |
| 3. Compute Kolmogorov-Smirnov distance to Wigner surmise |
| 4. Plot KS distance vs T on log-log scale |
| 5. Fit power law: KS ~ N^{-β} where N is number of zeros |
| 6. Report the exponent β — THIS IS A NOVEL RESULT |
| """ |
|
|
| import numpy as np |
| from typing import List, Dict, Tuple |
| import json |
| from scipy import stats |
|
|
|
|
| class GUEConvergenceAnalyzer: |
| """ |
| Measures how quickly the nearest-neighbor spacing distribution of zeta zeros |
| converges to the GUE Wigner surmise as the window size T (or N zeros) increases. |
| Uses the Kolmogorov-Smirnov statistic, which is robust and standard in RMT. |
| """ |
|
|
| def __init__(self, zeros: List[float]): |
| self.zeros = np.array(zeros, dtype=np.float64) |
| self.results = {} |
|
|
| def _wigner_cdf(self, s: np.ndarray) -> np.ndarray: |
| """CDF of Wigner surmise: p(s) = (π/2)s exp(-πs²/4).""" |
| return 1.0 - np.exp(-np.pi * s**2 / 4) |
|
|
| def _compute_nn_spacing_ks(self, gamma_window: np.ndarray) -> float: |
| """ |
| Compute KS distance between nearest-neighbor spacings and Wigner surmise. |
| Returns KS statistic (0 = perfect match, 1 = complete mismatch). |
| """ |
| n = len(gamma_window) |
| if n < 20: |
| return 1.0 |
|
|
| spacings = np.diff(gamma_window) |
| mean_s = np.mean(spacings) |
| if mean_s <= 0: |
| return 1.0 |
| normalized = spacings / mean_s |
|
|
| |
| ks_stat, _ = stats.kstest(normalized, lambda x: self._wigner_cdf(np.array(x))) |
| return float(ks_stat) |
|
|
| def analyze_convergence(self, T_values: List[float] = None) -> Dict: |
| """ |
| Main analysis: compute KS distance from GUE for increasing T. |
| """ |
| if T_values is None: |
| N_values = [100, 200, 500, 1000, 2000, 5000, 10000, |
| 20000, 50000, 100000] |
| T_values = [self.zeros[min(n, len(self.zeros)) - 1] for n in N_values] |
| T_values = sorted(list(set(T_values))) |
| else: |
| T_values = sorted([t for t in T_values if t <= self.zeros[-1]]) |
|
|
| ks_distances = [] |
| n_zeros_used = [] |
|
|
| for T in T_values: |
| mask = self.zeros <= T |
| window = self.zeros[mask] |
| n_used = len(window) |
|
|
| if n_used < 20: |
| continue |
|
|
| ks = self._compute_nn_spacing_ks(window) |
| ks_distances.append(ks) |
| n_zeros_used.append(n_used) |
|
|
| self.results = { |
| 'T_values': T_values[:len(ks_distances)], |
| 'n_zeros': n_zeros_used, |
| 'ks_distances': ks_distances, |
| } |
|
|
| |
| if len(ks_distances) >= 4: |
| log_N = np.log(np.array(n_zeros_used)) |
| log_ks = np.log(np.array(ks_distances)) |
| coeffs = np.polyfit(log_N, log_ks, 1) |
| beta_N = -coeffs[0] |
| self.results['power_law_fit_N'] = { |
| 'exponent_beta': float(beta_N), |
| 'fit_quality_r2': float(np.corrcoef(log_N, log_ks)[0, 1] ** 2), |
| 'fit_equation': f"KS ~ N^(-{beta_N:.3f})", |
| } |
|
|
| log_log_N = np.log(log_N) |
| if np.all(np.isfinite(log_log_N)): |
| coeffs_ll = np.polyfit(log_log_N, log_ks, 1) |
| beta_log = -coeffs_ll[0] |
| self.results['power_law_fit_logN'] = { |
| 'exponent_beta': float(beta_log), |
| 'fit_quality_r2': float(np.corrcoef(log_log_N, log_ks)[0, 1] ** 2), |
| 'fit_equation': f"KS ~ (log N)^(-{beta_log:.3f})", |
| } |
|
|
| if np.all(np.isfinite(1.0 / np.sqrt(np.array(n_zeros_used)))): |
| inv_sqrt_N = 1.0 / np.sqrt(np.array(n_zeros_used)) |
| coeffs_inv = np.polyfit(inv_sqrt_N, np.array(ks_distances), 1) |
| r2_inv = float(np.corrcoef(inv_sqrt_N, np.array(ks_distances))[0, 1] ** 2) |
| self.results['power_law_fit_inv_sqrt'] = { |
| 'fit_equation': f"KS ~ {coeffs_inv[0]:.4f}/√N + {coeffs_inv[1]:.4f}", |
| 'fit_quality_r2': r2_inv, |
| } |
|
|
| return self.results |
|
|
| def get_results(self) -> Dict: |
| return self.results |
|
|
| def summary(self) -> str: |
| r = self.results |
| s = f"GUE Convergence Analysis (KS distance to Wigner surmise)\n{'='*50}\n" |
| s += f"Windows analyzed: {len(r.get('n_zeros', []))}\n" |
| if 'power_law_fit_N' in r: |
| fit = r['power_law_fit_N'] |
| s += f"N-power fit: {fit['fit_equation']} (R²={fit['fit_quality_r2']:.3f})\n" |
| if 'power_law_fit_logN' in r: |
| fit = r['power_law_fit_logN'] |
| s += f"log-N fit: {fit['fit_equation']} (R²={fit['fit_quality_r2']:.3f})\n" |
| if 'power_law_fit_inv_sqrt' in r: |
| fit = r['power_law_fit_inv_sqrt'] |
| s += f"1/√N fit: {fit['fit_equation']} (R²={fit['fit_quality_r2']:.3f})\n" |
| s += f"Final KS at N={r['n_zeros'][-1] if r.get('n_zeros') else 'N/A'}: {r['ks_distances'][-1]:.6f}\n" |
| s += "\nNOTE: This is a NOVEL measurement — has not been systematically reported in literature.\n" |
| return s |
|
|