import csv
import logging
import math
import os
from argparse import ArgumentParser
from concurrent.futures import ProcessPoolExecutor
from typing import List, Set, Tuple
LOGGER = logging.getLogger(__name__)
NON_ROOT_PRIMES = (2, 3, 5, 7)
PRIME_PERIOD = math.prod(NON_ROOT_PRIMES) PRIME_ROOTS = [
x for x in range(2, PRIME_PERIOD + 2) if x % 2 and x % 3 and x % 5 and x % 7
] NUM_ROOTS = len(PRIME_ROOTS)
TWIN_PRIME_ROOTS = [
i
for i, x in enumerate(PRIME_ROOTS)
if i < NUM_ROOTS - 1 and x + 2 == PRIME_ROOTS[i + 1]
]
PRIME_TRIPLETS_ROOTS = [
i
for i, x in enumerate(PRIME_ROOTS)
if i < NUM_ROOTS - 1
and i > 0
and (
(x + 2 == PRIME_ROOTS[i + 1] and x - 4 == PRIME_ROOTS[i - 1])
or (x + 4 == PRIME_ROOTS[i + 1] and x - 2 == PRIME_ROOTS[i - 1])
)
]
def _find_inv_prime_root_vec(root_idx: int):
r = PRIME_ROOTS[root_idx]
rows = []
for q in PRIME_ROOTS:
for h_candidate in PRIME_ROOTS:
if ((q * h_candidate - r) % PRIME_PERIOD) == 0:
q_hat = h_candidate
break
else:
raise ValueError(f"No root found for q={q} and r={r}")
rows.append(q_hat)
return rows
INV_PRIME_ROOT_TABLE = [_find_inv_prime_root_vec(i) for i in range(NUM_ROOTS)]
def get_cyclic_composite_vec(root_index: int) -> List[int]:
items = []
r = PRIME_ROOTS[root_index]
q_hat_vec = INV_PRIME_ROOT_TABLE[root_index]
for j, q in enumerate(PRIME_ROOTS):
l = 1 + ((q * q_hat_vec[j] - r) // PRIME_PERIOD)
items.append(l)
return items
CYCLIC_COMPOSITE_TABLE = [get_cyclic_composite_vec(i) for i in range(NUM_ROOTS)]
def _find_inverse_composite_roots_in_cycle(
composites: List[int],
val: int,
i: int,
q_hat: int,
start_cycle: int,
end_cycle: int,
):
i_multiplier = i * PRIME_PERIOD
j_min = max(((start_cycle - val - 1) // (q_hat + i_multiplier)), 0)
j_max = 1 + ((end_cycle + 1 - val) // (q_hat + i_multiplier))
for j, q_hat_multiple in enumerate(range(j_min * q_hat, j_max * q_hat, q_hat)):
new_val = val + q_hat_multiple + ((j + j_min) * i_multiplier)
if new_val > start_cycle:
composites[new_val - 1 - start_cycle] = 1
def _find_composite_roots_in_cycles(
composites: List[int],
val: int,
q: int,
q_hat: int,
start_cycle: int,
end_cycle: int,
):
i_max = 1 + ((end_cycle + 1 - val) // q)
for i in range(i_max):
if val > start_cycle:
composites[val - 1 - start_cycle] = 1
_find_inverse_composite_roots_in_cycle(
composites, val, i, q_hat, start_cycle, end_cycle
)
val += q
def find_composites_in_cycles(
root_idx: int, start_cycle: int, end_cycle: int
) -> List[int]:
l = CYCLIC_COMPOSITE_TABLE[root_idx]
q_hat_vec = INV_PRIME_ROOT_TABLE[root_idx]
composites = [0] * (1 + end_cycle - start_cycle)
for i in range(NUM_ROOTS):
_find_composite_roots_in_cycles(
composites, l[i], PRIME_ROOTS[i], q_hat_vec[i], start_cycle, end_cycle
)
return composites
def find_non_composites_in_cycles(
root_idx: int, start_cycle: int, end_cycle: int
) -> List[int]:
composites = find_composites_in_cycles(root_idx, start_cycle, end_cycle)
return [start_cycle + i for i, x in enumerate(composites) if x == 0]
def get_primes_in_range(b_start: int, b_end: int) -> List[int]:
start_cycle: int = b_start // PRIME_PERIOD
end_cycle: int = b_end // PRIME_PERIOD
futures = []
with ProcessPoolExecutor() as pool:
for i in range(NUM_ROOTS):
futures.append(
pool.submit(find_non_composites_in_cycles, i, start_cycle, end_cycle)
)
if start_cycle:
primes = []
else:
primes = list(NON_ROOT_PRIMES)
for i, future in enumerate(futures):
prime_indexes = future.result()
primes.extend(
[PRIME_ROOTS[i] + (PRIME_PERIOD * page) for page in prime_indexes]
)
return [x for x in sorted(primes) if x >= b_start and x <= b_end]
def get_twin_primes_in_range(b_start: int, b_end: int) -> List[Tuple[int, int]]:
start_cycle: int = b_start // PRIME_PERIOD
end_cycle: int = b_end // PRIME_PERIOD
futures = {}
with ProcessPoolExecutor() as pool:
for i in TWIN_PRIME_ROOTS:
for n in range(2):
futures[i + n] = pool.submit(
find_non_composites_in_cycles, i + n, start_cycle, end_cycle
)
if start_cycle:
twin_primes = []
else:
twin_primes = [
tuple(NON_ROOT_PRIMES[1:2]), tuple(NON_ROOT_PRIMES[2:3]), ]
prime_indexes = {}
for i, future in futures.items():
prime_indexes[i] = set(future.result())
for i in TWIN_PRIME_ROOTS:
twin_indices = prime_indexes[i].intersection(prime_indexes[i + 1])
twin_primes.extend(
[
(
PRIME_ROOTS[i] + (PRIME_PERIOD * page),
PRIME_ROOTS[i + 1] + (PRIME_PERIOD * page),
)
for page in twin_indices
]
)
return [x for x in sorted(twin_primes)]
def get_primes_triplets_in_range(
b_start: int, b_end: int
) -> List[Tuple[int, int, int]]:
start_cycle: int = b_start // PRIME_PERIOD
end_cycle: int = b_end // PRIME_PERIOD
futures = {}
with ProcessPoolExecutor() as pool:
for i in PRIME_TRIPLETS_ROOTS:
for n in range(3):
futures[i - 1 + n] = pool.submit(
find_non_composites_in_cycles, i - 1 + n, start_cycle, end_cycle
)
if start_cycle:
prime_triplets = []
else:
prime_triplets = [
tuple([NON_ROOT_PRIMES[2], NON_ROOT_PRIMES[3], 11]), tuple([NON_ROOT_PRIMES[3], 11, 13]), ]
prime_indexes = {}
for i, future in futures.items():
prime_indexes[i] = set(future.result())
for i in PRIME_TRIPLETS_ROOTS:
triple_indices = (
prime_indexes[i]
.intersection(prime_indexes[i + 1])
.intersection(prime_indexes[i - 1])
)
prime_triplets.extend(
[
(
PRIME_ROOTS[i - 1] + (PRIME_PERIOD * page),
PRIME_ROOTS[i] + (PRIME_PERIOD * page),
PRIME_ROOTS[i + 1] + (PRIME_PERIOD * page),
)
for page in triple_indices
]
)
return [x for x in sorted(prime_triplets)]
def get_separated_primes_in_range(
num_cycles: int, b_start: int, b_end: int
) -> List[Tuple[int, int]]:
if num_cycles == 0:
primes = get_primes_in_range(b_start, b_end)
isolated_prime = None
last_prime = primes[0]
for i, prime in enumerate(primes[1:]):
if isolated_prime is None or (prime - last_prime) > isolated_prime[1]:
isolated_prime = ((last_prime, prime), prime - last_prime)
last_prime = prime
return [isolated_prime[0]] if isolated_prime else []
start_cycle: int = b_start // PRIME_PERIOD
end_cycle: int = b_end // PRIME_PERIOD
futures = []
with ProcessPoolExecutor() as pool:
for i in range(NUM_ROOTS):
futures.append(
pool.submit(find_non_composites_in_cycles, i, start_cycle, end_cycle)
)
prime_indices = {}
for i, future in enumerate(futures):
prime_indices[i] = set(future.result())
common_indices = set()
for indices in prime_indices.values():
common_indices.update(indices)
isolated_primes = []
last_index = None
for idx, index in enumerate(sorted(common_indices)):
if idx > 0 and index - last_index > num_cycles:
a = None
for i in reversed(range(NUM_ROOTS)):
if last_index in prime_indices[i]:
a = PRIME_ROOTS[i] + (PRIME_PERIOD * last_index)
break
else:
raise ValueError(
"No last index found in prime indices which is unexpected"
)
b = None
for i in range(NUM_ROOTS):
if index in prime_indices[i]:
b = PRIME_ROOTS[i] + (PRIME_PERIOD * index)
break
else:
raise ValueError("No index found in prime indices which is unexpected")
isolated_primes.append((a, b))
last_index = index
return isolated_primes
def periodic_table_primes(start_cycle: int, end_cycle: int) -> List[List[int]]:
b_start = PRIME_ROOTS[0] * start_cycle
b_end = PRIME_ROOTS[-1] * end_cycle
primes = set(get_primes_in_range(b_start, b_end))
cols = []
for x in PRIME_ROOTS:
rows = []
for cycle in range(start_cycle, end_cycle):
item = x + (PRIME_PERIOD * cycle)
if item in primes:
rows.append(item)
else:
rows.append(None)
cols.append(rows)
return cols
def periodic_table_primes_as_csv(
fname: str, start_cycle: int, end_cycle: int
) -> List[List[int]]:
table = periodic_table_primes(start_cycle, end_cycle)
with open(fname, "w") as fd:
writer = csv.writer(fd)
writer.writerow(["Cycle/root"] + list(range(start_cycle, end_cycle)))
for i, row in enumerate(table):
writer.writerow([PRIME_ROOTS[i]] + row)
return table
def get_prime_root(prime: int) -> Tuple[int, int]:
if prime in NON_ROOT_PRIMES:
return prime, 0
root = prime % PRIME_PERIOD
if root not in PRIME_ROOTS:
root = PRIME_PERIOD - root
if root not in PRIME_ROOTS:
raise ValueError(f"Number does not appear to be prime")
cycle_num = (prime - root) // PRIME_PERIOD
return (root, cycle_num)
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
BIG_PRIME_LIST = f"{BASE_DIR}/all_primes.txt"
class VerifyPrimes:
def _read_prime_list(self, fname):
primes = []
with open(fname) as fd:
for line in fd:
clean_line = line.strip()
if not clean_line:
continue
if clean_line[0].isdigit():
tokens = [int(x) for x in clean_line.split(" ") if x]
primes.extend(tokens)
return primes
def _get_primes_in_range(self, r_min: int, r_max: int):
big_prime_list = self._read_prime_list(BIG_PRIME_LIST)
if r_min > max(big_prime_list):
return None
r_max = min(r_max, max(big_prime_list))
return {x for x in big_prime_list if x >= r_min and x <= r_max}
def verify_primes(self, primes: Set[int]):
exp = self._get_primes_in_range(min(primes), max(primes))
if not exp:
print("Skipping verify as nothing to check against..")
return
max_exp = max(exp)
if max_exp < max(primes):
print(f"Cropping primes to fit test data <= {max_exp}")
primes = {x for x in primes if x <= max_exp}
false_positives = primes - exp
missing_primes = exp - primes
if false_positives:
raise ValueError(f"Found the following were not primes: {false_positives}")
if missing_primes:
raise ValueError(f"Found the following were missing: {missing_primes}")
print(f"Verified {len(primes)} primes")
def _get_prime_from_parts(parts) -> Tuple[int, str]:
if isinstance(parts, list):
prime = 0
name_parts = []
for part in parts:
if isinstance(part, tuple):
val = int(part[0] ** part[1])
sign = "+"
if len(part) > 2:
val = -val
sign = "-"
prime += val
if name_parts:
name_parts.append(sign)
name_parts.append(f"{part[0]}^{part[1]}")
else:
prime += part
if name_parts:
name_parts.append("-" if prime < 0 else "+")
name_parts.append(str(abs(part)))
name = " ".join(name_parts)
else:
prime = parts
name = str(prime)
return prime, name
def test_get_prime_root():
res = []
for parts in (
2,
5,
7,
[(2, 136279841), -1],
[(2, 82589933), -1],
[(2, 77232917), -1],
[(2, 74207281), -1],
[(2, 57885161), -1],
[(2, 43112609), -1],
[(2, 42643801), -1],
[(516693, 2097152), (516693, 1048576, -1), 1],
[(465859, 2097152), (465859, 1048576, -1), 1],
[(2, 37156667), -1],
):
prime, name = _get_prime_from_parts(parts)
root, index = get_prime_root(prime)
info = {"name": name, "prime": prime, "root": root, "index": index}
if prime > int(10**10):
print(
f"Prime {info['name']} has root {info['root']} with an index of ~10^({math.log10(info['index']):.3f})"
)
elif info["index"]:
print(
f"Prime {info['prime']} has root {info['root']} with index of {info['index']}"
)
else:
print(f"Prime {info['prime']} has no roots")
res.append(info)
def test_get_primes_in_range():
b_start = int(200e6)
b_end = b_start + int(1e6)
primes = get_primes_in_range(b_start, b_end)
checker = VerifyPrimes()
checker.verify_primes(set(primes))
print(f"In range {b_start:,} to {b_end:,} there are {len(primes)} primes")
def test_periodic_table_primes():
start_cycle = 96
end_cycle = 144
fname = "out.csv"
ptp_table = periodic_table_primes_as_csv(fname, start_cycle, end_cycle)
print(
f"In PFP({start_cycle}, {end_cycle}) there are {len(ptp_table)} rows and {len(ptp_table[0])} cols"
)
def test_get_twin_primes_in_range():
b_start = int(0)
b_end = int(70e6)
prime_twins = get_twin_primes_in_range(b_start, b_end)
print(f"In range {b_start:,} to {b_end:,} there are {len(prime_twins)} twin primes")
def test_get_prime_triplets_in_range():
b_start = int(0)
b_end = int(1e6)
prime_triplets = get_primes_triplets_in_range(b_start, b_end)
print(
f"In range {b_start:,} to {b_end:,} there are {len(prime_triplets)} prime triplets"
)
def test_get_separated_primes_in_range():
num_cycles = 1
b_start = int(350_000_000)
b_end = int(795_000_000)
isolated_primes = get_separated_primes_in_range(num_cycles, b_start, b_end)
print(isolated_primes)
print(
f"In range {b_start:,} to {b_end:,} there are {len(isolated_primes)} isolated primes with a minimum gap of {PRIME_PERIOD * num_cycles}"
)
largest_gap = None
for i, (a, b) in enumerate(isolated_primes):
if largest_gap is None or (b - a) > largest_gap[0]:
largest_gap = (b - a, i)
print(
f"The largest gap found was {largest_gap[0]} found on pair {isolated_primes[largest_gap[1]]}"
)
def test_known_large_prime():
b_start = 416_608_695_820
b_end = 416_608_695_822
primes = get_primes_in_range(b_start, b_end)
print(f"In range {b_start:,} to {b_end:,} there are {primes} primes")
def main():
parser = ArgumentParser()
parser.add_argument("start", nargs="?", type=int)
parser.add_argument("end", nargs="?", type=int)
parser.add_argument("--print", "-p", action="store_true")
args = parser.parse_args()
if not args.start:
args.start = 0
if not args.end:
args.end = args.start
primes = get_primes_in_range(args.start, args.end)
if args.print:
print(primes)
print(f"Found {len(primes):,} primes in range {args.start:,} to {args.end:,}")
if __name__ == "__main__":
main()