Wiki
Research notes on every problem, and a library of the papers behind them. Built from the open erdos repository.
erdos_699_bft_prime_bootstrap.py
py
"""Finite prime-interval bootstrap for BFT coefficient-gcd lower bounds.
All certification arithmetic is integral. Log-prime weights are bounded
using a directed-interval lookup table, then summed as fixed-point integers.
The finite sieve stops at 10**8 by default; retained BFT and Dusart theta
bounds cover larger arguments. This enumerates no binomial rows.
"""
from __future__ import annotations
import argparse
from fractions import Fraction
import hashlib
import json
from math import isqrt
from pathlib import Path
import time
import numpy as np
from mpmath import iv
from erdos_699_bft_dusart_gcd import gcd_exponent_lower
SCALE = 1 << 32
BUCKETS = 1 << 14
def ceil_div(a: int, b: int) -> int:
return -(-a // b)
class PrimeLogIntervals:
def __init__(self, limit: int = 10**8):
self.limit = limit
iv.dps = 60
lo = np.empty(BUCKETS + 1, dtype=np.uint64)
hi = np.empty(BUCKETS + 1, dtype=np.uint64)
for k in range(BUCKETS, 2 * BUCKETS + 1):
value = iv.log(iv.mpf(k) / BUCKETS) * SCALE
lo[k - BUCKETS] = int(value.a)
hi[k - BUCKETS] = int(value.b) + 1
self.log_lo, self.log_hi = lo, hi
self.log2_lo, self.log2_hi = int(lo[-1]), int(hi[-1])
sieve = np.ones(limit + 1, dtype=np.bool_)
sieve[:2] = False
for p in range(2, isqrt(limit) + 1):
if sieve[p]:
sieve[p * p::p] = False
primes = np.flatnonzero(sieve).astype(np.int64)
del sieve
assert int(primes[-1]) * SCALE < 2**64
powers = np.array([1 << k for k in range(limit.bit_length() + 1)],
dtype=np.int64)
exponents = np.searchsorted(powers, primes, side="right") - 1
buckets = (primes << 14) >> exponents
offsets = (buckets - BUCKETS).astype(np.int64)
weights_lo = exponents.astype(np.uint64) * self.log2_lo + lo[offsets]
weights_hi = exponents.astype(np.uint64) * self.log2_hi + hi[offsets + 1]
# Each weight is between 0 and SCALE*log(limit)+1. This coarse
# bound proves that both cumulative sums fit in uint64.
assert len(primes) * (limit.bit_length() * SCALE + 2) < 2**64
self.primes = primes
self.prefix_lo = np.r_[np.uint64(0), np.cumsum(weights_lo, dtype=np.uint64)]
self.prefix_hi = np.r_[np.uint64(0), np.cumsum(weights_hi, dtype=np.uint64)]
self.metadata = {
"sieve_limit": limit, "prime_count": len(primes),
"fixed_point_scale": SCALE, "logarithm_buckets": BUCKETS,
"prime_array_sha256": hashlib.sha256(primes.tobytes()).hexdigest(),
"lower_prefix_sha256": hashlib.sha256(self.prefix_lo.tobytes()).hexdigest(),
"upper_prefix_sha256": hashlib.sha256(self.prefix_hi.tobytes()).hexdigest(),
"log_table_lower_sha256": hashlib.sha256(lo.tobytes()).hexdigest(),
"log_table_upper_sha256": hashlib.sha256(hi.tobytes()).hexdigest(),
}
def log_lower(self, x: int) -> int:
b = x.bit_length() - 1
k = (x * BUCKETS) >> b
return b * self.log2_lo + int(self.log_lo[k - BUCKETS])
def theta_bounds(self, x: int) -> tuple[int, int]:
if x <= self.limit:
k = int(np.searchsorted(self.primes, x, side="right"))
return int(self.prefix_lo[k]), int(self.prefix_hi[k])
assert 1 <= x <= 10**11
# BFT Lemma 5.4: x - 2.072*sqrt(x) < theta(x) < x.
root = isqrt(x * SCALE**2)
if root * root < x * SCALE**2:
root += 1
error = ceil_div(259 * root, 125)
# Dusart (2010), Theorem 5.2, absolute theta errors eta*x/log(x)^k.
log_x = self.log_lower(x)
for power, eta_num, eta_den, threshold in (
(3, 1, 1, 89967803),
(2, 1, 20, 122568683),
(3, 39, 50, 158822621),
(1, 1, 1000, 908994923),
(2, 1, 100, 7713133853),
):
if x >= threshold:
error = min(error, ceil_div(
eta_num * x * SCALE**(power + 1),
eta_den * log_x**power))
return max(0, x * SCALE - error), x * SCALE
def interval_sum_lower(self, uppers: np.ndarray, lowers: np.ndarray) -> int:
keep = uppers > lowers
uppers, lowers = uppers[keep], lowers[keep]
assert len(uppers) == 0 or int(uppers.max()) * SCALE < 2**64
exact = uppers <= self.limit
iu = np.searchsorted(self.primes, uppers[exact], side="right")
il = np.searchsorted(self.primes, lowers[exact], side="right")
# Using the same LOWER prefix at both ends directly sums lower
# bounds for just the primes in the interval. There is no subtraction
# of two unrelated lower estimates here.
total = int(np.sum(self.prefix_lo[iu] - self.prefix_lo[il], dtype=np.uint64))
for upper, lower in zip(uppers[~exact], lowers[~exact]):
hi_lower, _ = self.theta_bounds(int(upper))
_, lo_upper = self.theta_bounds(int(lower))
total += max(0, hi_lower - lo_upper)
return total
def prime_intervals(c: int, d: int, terms: int):
ts, numerators, denominators = [], [], []
h = c + d
for t in range(1, terms + 1):
k, r = divmod(d * t, h)
if 2 * r <= h:
continue
a, b = d, k + 1
e, f = c - d, t - 2 * k - 1
assert b > 0 and f > 0
if e * b > a * f:
a, b = e, f
ts.append(t)
numerators.append(a)
denominators.append(b)
return (np.array(ts, dtype=np.int64),
np.array(numerators, dtype=np.int64),
np.array(denominators, dtype=np.int64))
def bootstrap(row: dict, theta: PrimeLogIntervals, lower_start: int = 1000,
terms: int = 4000) -> dict:
c, d = row["seed"][-2:]
analytic = row["gcd_certificate"]
upper_end = analytic["m0"] - 1
target = Fraction(analytic["ell_lower"])
regenerated = gcd_exponent_lower(
c, d, terms=analytic["terms"], m0=analytic["m0"],
scale=analytic["rounding_denominator"],
root_scale=analytic["root_scale"])
assert Fraction(regenerated["ell_lower"]) == target
ts, vn, vd = prime_intervals(c, d, terms)
digest = hashlib.sha256()
leaves = visited = 0
min_margin = None
unsupported = None
stack = [(lower_start, upper_end)]
covered_right = upper_end
started = time.monotonic()
while stack:
left, right = stack.pop()
visited += 1
valid = 2 * ts <= left
upper_args = ((c + d) * left - 2) // ts[valid]
lower_args = vn[valid] * right // vd[valid]
lower_sum = theta.interval_sum_lower(upper_args, lower_args)
margin_num = (lower_sum * target.denominator
- d * right * SCALE * target.numerator)
if margin_num >= 0:
assert right == covered_right
covered_right = left - 1
leaves += 1
if leaves % 20000 == 0:
print(json.dumps({"pair": row["pair"], "leaves": leaves, "left": left, "elapsed_seconds": round(time.monotonic() - started, 1)}), flush=True)
margin = Fraction(margin_num, d * right * SCALE * target.denominator)
min_margin = margin if min_margin is None else min(min_margin, margin)
digest.update(f"{left},{right},{lower_sum}\n".encode())
elif left == right:
unsupported = left
assert covered_right == left
break
else:
middle = (left + right) // 2
# Right-first traversal lets us stop at the largest unsupported
# singleton. Every larger integer has already been certified.
stack.append((left, middle))
stack.append((middle + 1, right))
effective_start = unsupported + 1 if unsupported is not None else lower_start
assert covered_right == effective_start - 1
result = {
"pair": row["pair"], "seed": row["seed"],
"targets_by_prime": row["targets_by_prime"],
"ell_lower": str(target), "finite_interval_start": effective_start,
"finite_interval_end": upper_end,
"analytic_gcd_certificate": analytic,
"terms": terms, "certified_intervals": leaves, "visited_nodes": visited,
"largest_unsupported_singleton": unsupported,
"minimum_margin": str(min_margin),
"partition_sha256": digest.hexdigest(),
"elapsed_seconds": round(time.monotonic() - started, 3),
"conclusion": "G(c,d,dm-delta) >= exp(d*m*ell) for every m >= finite_interval_start and delta in {0,1}",
}
print(json.dumps({key: result[key] for key in
("pair", "finite_interval_start", "certified_intervals",
"visited_nodes", "elapsed_seconds")}), flush=True)
return result
def main():
parser = argparse.ArgumentParser()
parser.add_argument("input", type=Path)
parser.add_argument("--output", type=Path)
parser.add_argument("--pairs", default="")
parser.add_argument("--sieve-limit", type=int, default=10**8)
parser.add_argument("--lower-start", type=int, default=1000)
args = parser.parse_args()
rows = json.loads(args.input.read_text())
if args.pairs:
keep = {tuple(map(int, pair.split(','))) for pair in args.pairs.split(';')}
rows = [row for row in rows if tuple(row["pair"]) in keep]
theta = PrimeLogIntervals(args.sieve_limit)
print(json.dumps(theta.metadata), flush=True)
output = {
"input_file": args.input.name,
"prime_log_certificate": theta.metadata,
"records": [],
}
for row in rows:
output["records"].append(bootstrap(row, theta, args.lower_start))
if args.output:
args.output.write_text(json.dumps(output, indent=2) + "\n")
if __name__ == "__main__":
main()
Graph