Upload problem_solvers/ramanujan_machine.py
Browse files
problem_solvers/ramanujan_machine.py
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'''
|
| 2 |
+
Ramanujan Machine-style Continued Fraction Search
|
| 3 |
+
====================================================
|
| 4 |
+
Inspired by arXiv:2308.11829 (Elimelech et al.)
|
| 5 |
+
Key insight: polynomial continued fractions converging to mathematical constants
|
| 6 |
+
exhibit "factorial reduction" — gcd(p_n, q_n) grows super-exponentially.
|
| 7 |
+
This property acts as a FILTER that dramatically reduces the search space.
|
| 8 |
+
|
| 9 |
+
We adapt this to search for continued fractions converging to zeta-related values:
|
| 10 |
+
- ζ(2) = π²/6
|
| 11 |
+
- ζ(3) (Apéry's constant)
|
| 12 |
+
- Values at zero ordinates: ζ(1/2 + iγ_n)
|
| 13 |
+
- Li's criterion values λ_n
|
| 14 |
+
|
| 15 |
+
The "conservative matrix field" structure from the paper suggests that formulas
|
| 16 |
+
for related constants share matrix structures. We search for polynomial continued
|
| 17 |
+
fractions and test if they converge to known zeta values.
|
| 18 |
+
'''
|
| 19 |
+
|
| 20 |
+
import numpy as np
|
| 21 |
+
from typing import Dict, List, Tuple
|
| 22 |
+
from dataclasses import dataclass
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
@dataclass
|
| 26 |
+
class ContinuedFractionFormula:
|
| 27 |
+
'''A discovered continued fraction formula.'''
|
| 28 |
+
target_constant: str
|
| 29 |
+
a_coeffs: List[int] # partial denominators (polynomial in n)
|
| 30 |
+
b_coeffs: List[int] # partial numerators (polynomial in n)
|
| 31 |
+
convergence_value: float
|
| 32 |
+
error: float
|
| 33 |
+
convergence_rate: float
|
| 34 |
+
factorial_reduction_score: float
|
| 35 |
+
depth: int
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
class RamanujanMachineSearcher:
|
| 39 |
+
'''
|
| 40 |
+
Search for polynomial continued fractions converging to zeta values.
|
| 41 |
+
|
| 42 |
+
A polynomial continued fraction has the form:
|
| 43 |
+
a_0 + b_1/(a_1 + b_2/(a_2 + ...))
|
| 44 |
+
where a_n = polynomial in n, b_n = polynomial in n.
|
| 45 |
+
|
| 46 |
+
The "factorial reduction" heuristic: for formulas converging to constants,
|
| 47 |
+
gcd(p_n, q_n) is unusually large, causing the reduced convergents to grow
|
| 48 |
+
much slower than (n!)^d.
|
| 49 |
+
'''
|
| 50 |
+
|
| 51 |
+
def __init__(self, max_degree: int = 2, max_coeff: int = 5, max_depth: int = 20):
|
| 52 |
+
self.max_degree = max_degree
|
| 53 |
+
self.max_coeff = max_coeff
|
| 54 |
+
self.max_depth = max_depth
|
| 55 |
+
self.results = []
|
| 56 |
+
|
| 57 |
+
def _eval_polynomial(self, coeffs: List[int], n: int) -> int:
|
| 58 |
+
'''Evaluate polynomial Σ coeffs[k] * n^k.'''
|
| 59 |
+
return sum(c * (n ** k) for k, c in enumerate(coeffs))
|
| 60 |
+
|
| 61 |
+
def _compute_convergents(self, a_coeffs: List[int], b_coeffs: List[int],
|
| 62 |
+
depth: int) -> Tuple[List[int], List[int], List[int]]:
|
| 63 |
+
'''
|
| 64 |
+
Compute convergents p_n/q_n for polynomial continued fraction.
|
| 65 |
+
Returns (p_list, q_list, gcd_list).
|
| 66 |
+
'''
|
| 67 |
+
p = [0] * (depth + 2)
|
| 68 |
+
q = [0] * (depth + 2)
|
| 69 |
+
g = [0] * (depth + 2)
|
| 70 |
+
|
| 71 |
+
# Initial conditions
|
| 72 |
+
p[-1] = 1 # p_{-1} = 1
|
| 73 |
+
p[0] = self._eval_polynomial(a_coeffs, 0) # p_0 = a_0
|
| 74 |
+
q[-1] = 0 # q_{-1} = 0
|
| 75 |
+
q[0] = 1 # q_0 = 1
|
| 76 |
+
|
| 77 |
+
for n in range(1, depth + 1):
|
| 78 |
+
a_n = self._eval_polynomial(a_coeffs, n)
|
| 79 |
+
b_n = self._eval_polynomial(b_coeffs, n)
|
| 80 |
+
p[n] = a_n * p[n-1] + b_n * p[n-2]
|
| 81 |
+
q[n] = a_n * q[n-1] + b_n * q[n-2]
|
| 82 |
+
g[n] = self._gcd(abs(p[n]), abs(q[n]))
|
| 83 |
+
|
| 84 |
+
return p[1:depth+1], q[1:depth+1], g[1:depth+1]
|
| 85 |
+
|
| 86 |
+
def _gcd(self, a: int, b: int) -> int:
|
| 87 |
+
while b:
|
| 88 |
+
a, b = b, a % b
|
| 89 |
+
return a
|
| 90 |
+
|
| 91 |
+
def _factorial_reduction_score(self, p: List[int], q: List[int],
|
| 92 |
+
g: List[int], depth: int) -> float:
|
| 93 |
+
'''
|
| 94 |
+
Measure how much gcd reduces the growth of convergents.
|
| 95 |
+
For "good" formulas, reduced p_n/g_n and q_n/g_n grow like s^n
|
| 96 |
+
(exponential), not like (n!)^d (super-exponential).
|
| 97 |
+
|
| 98 |
+
Score = average log-growth-rate of reduced convergents.
|
| 99 |
+
Lower = stronger factorial reduction.
|
| 100 |
+
'''
|
| 101 |
+
if depth < 5:
|
| 102 |
+
return float('inf')
|
| 103 |
+
|
| 104 |
+
growth_rates = []
|
| 105 |
+
for n in range(3, depth):
|
| 106 |
+
if g[n] > 1 and p[n] != 0 and q[n] != 0:
|
| 107 |
+
reduced_p = abs(p[n]) // g[n]
|
| 108 |
+
reduced_q = abs(q[n]) // g[n]
|
| 109 |
+
if reduced_p > 0 and reduced_q > 0:
|
| 110 |
+
rate = (np.log(float(reduced_p)) + np.log(float(reduced_q))) / (2 * n)
|
| 111 |
+
growth_rates.append(rate)
|
| 112 |
+
|
| 113 |
+
if len(growth_rates) < 3:
|
| 114 |
+
return float('inf')
|
| 115 |
+
|
| 116 |
+
return float(np.mean(growth_rates))
|
| 117 |
+
|
| 118 |
+
def _test_convergence(self, a_coeffs: List[int], b_coeffs: List[int],
|
| 119 |
+
targets: Dict[str, float], depth: int = 20) -> List[ContinuedFractionFormula]:
|
| 120 |
+
'''Test if the continued fraction converges to any target constant.'''
|
| 121 |
+
p, q, g = self._compute_convergents(a_coeffs, b_coeffs, depth)
|
| 122 |
+
|
| 123 |
+
if q[-1] == 0 or p[-1] == 0:
|
| 124 |
+
return []
|
| 125 |
+
|
| 126 |
+
frac_value = p[-1] / q[-1]
|
| 127 |
+
frac_score = self._factorial_reduction_score(p, q, g, depth)
|
| 128 |
+
|
| 129 |
+
# Compute convergence rate: how fast do convergents stabilize?
|
| 130 |
+
if depth >= 10:
|
| 131 |
+
late_values = [p[n] / q[n] for n in range(depth-5, depth) if q[n] != 0]
|
| 132 |
+
if len(late_values) >= 3:
|
| 133 |
+
convergence_rate = float(np.std(late_values))
|
| 134 |
+
else:
|
| 135 |
+
convergence_rate = 1.0
|
| 136 |
+
else:
|
| 137 |
+
convergence_rate = 1.0
|
| 138 |
+
|
| 139 |
+
found = []
|
| 140 |
+
for name, target in targets.items():
|
| 141 |
+
if target == 0:
|
| 142 |
+
continue
|
| 143 |
+
error = abs(frac_value - target) / abs(target)
|
| 144 |
+
if error < 0.01: # Within 1%
|
| 145 |
+
found.append(ContinuedFractionFormula(
|
| 146 |
+
target_constant=name,
|
| 147 |
+
a_coeffs=a_coeffs,
|
| 148 |
+
b_coeffs=b_coeffs,
|
| 149 |
+
convergence_value=frac_value,
|
| 150 |
+
error=error,
|
| 151 |
+
convergence_rate=convergence_rate,
|
| 152 |
+
factorial_reduction_score=frac_score,
|
| 153 |
+
depth=depth
|
| 154 |
+
))
|
| 155 |
+
|
| 156 |
+
return found
|
| 157 |
+
|
| 158 |
+
def search(self, targets: Dict[str, float] = None, n_candidates: int = 5000) -> Dict:
|
| 159 |
+
'''
|
| 160 |
+
Search for polynomial continued fractions converging to targets.
|
| 161 |
+
|
| 162 |
+
Default targets include zeta values and related constants.
|
| 163 |
+
'''
|
| 164 |
+
if targets is None:
|
| 165 |
+
targets = {
|
| 166 |
+
'zeta_2': np.pi**2 / 6,
|
| 167 |
+
'zeta_3': 1.202056903159594,
|
| 168 |
+
'zeta_4': np.pi**4 / 90,
|
| 169 |
+
'catalan': 0.915965594177219,
|
| 170 |
+
'euler_mascheroni': 0.5772156649015329,
|
| 171 |
+
'golden_ratio': (1 + np.sqrt(5)) / 2,
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
print(f" [RamanujanMachine] Searching {n_candidates} candidates...")
|
| 175 |
+
found_formulas = []
|
| 176 |
+
best_scores = []
|
| 177 |
+
|
| 178 |
+
# Generate candidates: simple polynomial forms
|
| 179 |
+
# a_n = a0 + a1*n + a2*n^2, b_n = b0 + b1*n + b2*n^2
|
| 180 |
+
count = 0
|
| 181 |
+
for a0 in range(-self.max_coeff, self.max_coeff + 1):
|
| 182 |
+
for a1 in range(-self.max_coeff, self.max_coeff + 1):
|
| 183 |
+
for b0 in range(1, self.max_coeff + 1): # b0 > 0 for convergence
|
| 184 |
+
for b1 in range(-self.max_coeff, self.max_coeff + 1):
|
| 185 |
+
if count >= n_candidates:
|
| 186 |
+
break
|
| 187 |
+
a_coeffs = [a0, a1]
|
| 188 |
+
b_coeffs = [b0, b1]
|
| 189 |
+
|
| 190 |
+
formulas = self._test_convergence(a_coeffs, b_coeffs, targets)
|
| 191 |
+
if formulas:
|
| 192 |
+
found_formulas.extend(formulas)
|
| 193 |
+
|
| 194 |
+
# Also compute score for all candidates
|
| 195 |
+
p, q, g = self._compute_convergents(a_coeffs, b_coeffs, 15)
|
| 196 |
+
score = self._factorial_reduction_score(p, q, g, 15)
|
| 197 |
+
if score < 5.0: # Interesting reduction
|
| 198 |
+
best_scores.append((score, a_coeffs, b_coeffs))
|
| 199 |
+
|
| 200 |
+
count += 1
|
| 201 |
+
if count >= n_candidates:
|
| 202 |
+
break
|
| 203 |
+
if count >= n_candidates:
|
| 204 |
+
break
|
| 205 |
+
if count >= n_candidates:
|
| 206 |
+
break
|
| 207 |
+
|
| 208 |
+
# Sort by error
|
| 209 |
+
found_formulas.sort(key=lambda f: f.error)
|
| 210 |
+
|
| 211 |
+
self.results = {
|
| 212 |
+
'strategy': 'ramanujan_machine_continued_fractions',
|
| 213 |
+
'n_candidates': count,
|
| 214 |
+
'n_found': len(found_formulas),
|
| 215 |
+
'found_formulas': [
|
| 216 |
+
{
|
| 217 |
+
'target': f.target_constant,
|
| 218 |
+
'a_coeffs': f.a_coeffs,
|
| 219 |
+
'b_coeffs': f.b_coeffs,
|
| 220 |
+
'value': f.convergence_value,
|
| 221 |
+
'error': f.error,
|
| 222 |
+
'rate': f.convergence_rate,
|
| 223 |
+
'frac_score': f.factorial_reduction_score,
|
| 224 |
+
}
|
| 225 |
+
for f in found_formulas[:10]
|
| 226 |
+
],
|
| 227 |
+
'best_factorial_reduction': sorted(best_scores, key=lambda x: x[0])[:5],
|
| 228 |
+
}
|
| 229 |
+
return self.results
|
| 230 |
+
|
| 231 |
+
def summary(self) -> str:
|
| 232 |
+
r = self.results
|
| 233 |
+
s = f"Ramanujan Machine Search\n{'='*50}\n"
|
| 234 |
+
s += f"Candidates: {r['n_candidates']:,}\n"
|
| 235 |
+
s += f"Formulas found: {r['n_found']}\n"
|
| 236 |
+
for f in r['found_formulas'][:5]:
|
| 237 |
+
s += f" → {f['target']}: a={f['a_coeffs']}, b={f['b_coeffs']}, "
|
| 238 |
+
s += f"error={f['error']:.6f}, value={f['value']:.8f}\n"
|
| 239 |
+
if r['best_factorial_reduction']:
|
| 240 |
+
s += f"Best factorial reduction score: {r['best_factorial_reduction'][0][0]:.4f}\n"
|
| 241 |
+
return s
|