eisenstein_visibility.py
7.4 kB · python · 281 lines
1import math2import random3from decimal import Decimal, getcontext4from fractions import Fraction56import numpy as np78PREC = 609EULER_TERMS = 10010BERNOULLI = [11 Fraction(1, 6),12 Fraction(-1, 30),13 Fraction(1, 42),14 Fraction(-1, 30),15 Fraction(5, 66),16 Fraction(-691, 2730),17 Fraction(7, 6),18 Fraction(-3617, 510),19 Fraction(43867, 798),20 Fraction(-174611, 330),21]2223PAIRS = 20000024CUTOFF = 400000025SIDE = 300026TRIALS = 40000027RADIUS = 50028SEED = 20260725293031def atan_inv(x):32 base = Decimal(x)33 term = Decimal(1) / base34 total = term35 square = base * base36 eps = Decimal(10) ** -5837 k = 138 sign = -139 while True:40 term = term / square41 add = Decimal(sign) * term / Decimal(2 * k + 1)42 if abs(add) < eps:43 break44 total += add45 sign = -sign46 k += 147 return total484950def machin_pi():51 return 16 * atan_inv(5) - 4 * atan_inv(239)525354def trigamma(x):55 total = Fraction(0)56 for n in range(EULER_TERMS):57 shifted = n + x58 total += 1 / (shifted * shifted)59 m = EULER_TERMS + x60 total += 1 / m61 total += Fraction(1, 2) / (m * m)62 for index, bern in enumerate(BERNOULLI):63 total += bern / m ** (2 * (index + 1) + 1)64 return total656667def dec(value):68 return Decimal(value.numerator) / Decimal(value.denominator)697071def contfrac(value, terms):72 out = []73 x = value74 for _ in range(terms):75 a = int(x)76 out.append(a)77 frac = x - a78 if frac == 0:79 break80 x = Decimal(1) / frac81 return out828384def denominators(quotients):85 out = []86 previous, current = 0, 187 for index, a in enumerate(quotients):88 if index == 0:89 out.append(current)90 continue91 previous, current = current, a * current + previous92 out.append(current)93 return out949596def cut(value, width):97 return str(value)[:width]9899100def precision_block():101 getcontext().prec = PREC102 pi = machin_pi()103 pi2 = pi * pi104 psi13 = trigamma(Fraction(1, 3))105 psi23 = trigamma(Fraction(2, 3))106 psi14 = trigamma(Fraction(1, 4))107 psi34 = trigamma(Fraction(3, 4))108 l3 = dec(psi13 - psi23) / 9109 catalan = dec(psi14 - psi34) / 16110 z2 = pi2 / 6111 zk3 = z2 * l3112 zki = z2 * catalan113 sqrt3 = Decimal(3).sqrt()114 reflection = dec(psi13 + psi23) - 4 * pi2 / 3115 cl2 = l3 * 3 * sqrt3 / 4116 r1 = l3 * sqrt3 / pi2117 r2 = l3 / pi2118 print("pi =", cut(pi, 45))119 print("zeta(2) =", cut(z2, 45))120 print("6/pi^2 =", cut(6 / pi2, 45))121 print("L(2,chi_-3) =", cut(l3, 45))122 print("Catalan G =", cut(catalan, 45))123 print("zeta_K3(2) =", cut(zk3, 45))124 print("1/zeta_K3(2) =", cut(1 / zk3, 45))125 print("zeta_Qi(2) =", cut(zki, 45))126 print("1/zeta_Qi(2) =", cut(1 / zki, 45))127 print("psi1(1/3)+psi1(2/3)-4pi^2/3 =", reflection)128 print("Cl2(pi/3) implied =", cut(cl2, 30))129 for label, value, terms in (130 ("L3*sqrt3/pi^2", r1, 22),131 ("L3/pi^2 ", r2, 22),132 ("G/pi^2 ", catalan / pi2, 22),133 ("zeta(2)/pi^2 ", z2 / pi2, 8),134 ):135 quotients = contfrac(value, terms)136 print(f"{label} = {cut(value, 30)} CF: {quotients}")137 print(f"{label} last convergent denominator: {denominators(quotients)[-1]}")138 return float(zk3), float(zki), float(1 / zk3)139140141def dirichlet(pairs):142 m = np.arange(pairs, dtype=np.float64)143 odd = 3 * m + 1144 even = 3 * m + 2145 return math.fsum(1.0 / (odd * odd) - 1.0 / (even * even))146147148def lattice_radius(cutoff):149 return int(2.2 * math.sqrt(cutoff))150151152def lattice_sums(cutoff):153 r = lattice_radius(cutoff)154 b = np.arange(-r, r + 1, dtype=np.int64)155 hexagonal = 0.0156 square = 0.0157 for a in range(-r, r + 1):158 hexnorm = a * a - a * b + b * b159 sqnorm = a * a + b * b160 take = hexnorm[(hexnorm > 0) & (hexnorm <= cutoff)].astype(np.float64)161 hexagonal += float(np.sum(1.0 / (take * take)))162 take = sqnorm[(sqnorm > 0) & (sqnorm <= cutoff)].astype(np.float64)163 square += float(np.sum(1.0 / (take * take)))164 return hexagonal / 6.0, square / 4.0165166167def visible_pairs(side):168 axis = np.arange(1, side + 1, dtype=np.int64)169 count = 0170 for a in range(1, side + 1):171 count += int(np.count_nonzero(np.gcd(a, axis) == 1))172 return count173174175def emul(z, w):176 a, b = z177 c, d = w178 return (a * c - b * d, a * d + b * c - b * d)179180181def enorm(z):182 a, b = z183 return a * a - a * b + b * b184185186def edivmod(z, w):187 c, d = w188 n = enorm(w)189 p = emul(z, (c - d, -d))190 q = (round(p[0] / n), round(p[1] / n))191 s = emul(q, w)192 return q, (z[0] - s[0], z[1] - s[1])193194195def egcd(z, w):196 while enorm(w) != 0:197 z, w = w, edivmod(z, w)[1]198 return z199200201def seeded_sieve(trials, radius):202 rng = random.Random(SEED)203 coprime = 0204 for _ in range(trials):205 z = (rng.randint(-radius, radius), rng.randint(-radius, radius))206 w = (rng.randint(-radius, radius), rng.randint(-radius, radius))207 if enorm(z) == 0 or enorm(w) == 0:208 continue209 if enorm(egcd(z, w)) == 1:210 coprime += 1211 return coprime212213214def phi(n):215 r, m, p = n, n, 2216 while p * p <= m:217 if m % p == 0:218 r -= r // p219 while m % p == 0:220 m //= p221 p += 1222 if m > 1:223 r -= r // m224 return r225226227def fresh(base, level):228 return sum(1 for k in range(1, base**level + 1) if k % base)229230231def sieve_block(zk3, zki, inv_zk3):232 print(f"L(2,chi_-3) direct Dirichlet sum ({PAIRS} pairs) = {dirichlet(PAIRS):.12f}")233 hexagonal, square = lattice_sums(CUTOFF)234 hextail = math.pi / (3 * math.sqrt(3)) / CUTOFF235 sqtail = math.pi / 4 / CUTOFF236 print(237 f"zeta_K3(2) lattice sum (norm<={CUTOFF}) = {hexagonal:.10f}"238 f" (+tail~{hextail:.2e} -> {hexagonal + hextail:.10f})"239 )240 print(f"zeta_K3(2) expected zeta(2)*L = {zk3:.10f}")241 print(242 f"zeta_Qi(2) lattice sum (norm<={CUTOFF}) = {square:.10f}"243 f" (+tail~{sqtail:.2e} -> {square + sqtail:.10f})"244 )245 print(f"zeta_Qi(2) expected zeta(2)*G = {zki:.10f}")246 seen = visible_pairs(SIDE)247 print(248 f"square-lattice visible density N={SIDE}: {seen / SIDE**2:.6f}"249 f" vs 6/pi^2 = {6 / math.pi**2:.6f}"250 )251 coprime = seeded_sieve(TRIALS, RADIUS)252 print(253 f"Eisenstein sieve: {coprime}/{TRIALS} coprime = {coprime / TRIALS:.5f}"254 f" vs 1/zeta_K3(2) = {inv_zk3:.5f}"255 )256 assert enorm(emul((3, 2), (1, 5))) == enorm((3, 2)) * enorm((1, 5))257 print(f"gcd(6,9) in Z[omega] has norm {enorm(egcd((6, 0), (9, 0)))} (expect 9 = N(3))")258 for base in (2, 3, 5):259 for level in range(1, 5):260 new = fresh(base, level)261 assert new == base**level - base ** (level - 1) == phi(base**level)262 print(263 "fresh nodes per level = b^L - b^(L-1) = phi(b^L)"264 " for b=2,3,5, L=1..4: OK; fraction (b-1)/b"265 )266 print(267 f"b=6,L=2: new={fresh(6, 2)}, b^L-b^(L-1)={6**2 - 6}, phi(b^L)={phi(36)}"268 " (phi identity needs prime b)"269 )270271272def main():273 print(f"domain: decimal prec {PREC}, Euler-Maclaurin {EULER_TERMS} terms through B_20")274 print(f"domain: Dirichlet {PAIRS} pairs, lattice norm <= {CUTOFF}"275 f" (radius {lattice_radius(CUTOFF)}), census [1,{SIDE}]^2,"276 f" sieve {TRIALS} pairs in [-{RADIUS},{RADIUS}]^2")277 zk3, zki, inv_zk3 = precision_block()278 sieve_block(zk3, zki, inv_zk3)279280281main()