Skip to content

Instantly share code, notes, and snippets.

@mikmart
Last active December 2, 2021 23:53
Show Gist options
  • Save mikmart/12d4132e717961846f48c4e76ec6d955 to your computer and use it in GitHub Desktop.
Save mikmart/12d4132e717961846f48c4e76ec6d955 to your computer and use it in GitHub Desktop.
Frequent strong liars among witness numbers (with top 100 results up to 115,471)
witness lies
4096 246
256 171
1296 140
729 115
16 114
6561 108
1024 98
625 96
64 90
15625 88
81 85
1568 81
20736 79
10000 78
11449 77
900 76
4913 76
529 75
2116 75
6106 75
7991 75
2304 74
2401 74
4624 73
1451 72
6068 72
1156 71
3446 71
7225 71
1369 70
1444 69
1728 69
2187 69
676 68
1207 68
1600 68
3481 68
5808 68
7151 68
300 67
1363 67
4924 67
14884 67
512 66
675 66
5043 66
7396 66
8836 66
32 65
324 65
400 65
1936 65
9299 65
11093 65
14641 65
1177 64
4225 64
28561 64
125 63
361 63
2888 63
3194 63
3974 63
3449 63
5476 63
7132 63
14258 63
19988 63
144 62
254 62
563 62
1101 62
3364 62
4232 62
5776 62
1322 61
1303 61
2382 61
2601 61
3532 61
3025 61
3721 61
12996 61
318 60
857 60
1432 60
3844 60
4754 60
7569 60
6859 60
8765 60
10201 60
18769 60
32768 60
36 59
243 59
972 59
1918 59
3656 59
4024 59
4562 59
5660 59
7056 59
import csv
import logging
import time
from argparse import ArgumentParser
from typing import List, Optional, Tuple
from collections import Counter
class Suspect:
""" An integer accused of being prime. """
__slots__ = ["n", "s", "d"]
def __init__(self, n: int):
s, d = prepare_suspect(n)
self.n = n
self.s = s
self.d = d
def prepare_suspect(n: int) -> Tuple[int, int]:
""" Find integers s and d such that n = 2^s * d + 1 and d is odd. """
s = 1
d, r = divmod(n, 2)
if not r:
raise ValueError("`n` must be odd.")
while True:
q, r = divmod(d, 2)
if r:
break
else:
s += 1
d = q
return s, d
def is_prime(s: Suspect, witnesses: Optional[List[int]] = None) -> bool:
""" Check if a suspect is prime based on a set of witnesses. """
if not witnesses:
witnesses = call_star_witnesses(s.n)
return all(witness_statement(w, s) == True for w in witnesses)
def witness_statement(witness: int, suspect: Suspect) -> bool:
""" Do any Miller-Rabin congruence relations hold? """
a = witness
n = suspect.n
s = suspect.s
d = suspect.d
x = pow(a, d, n)
if x in {1, n - 1}:
return True
while (s := s - 1):
x = pow(x, 2, n)
if x == (n - 1):
return True
return False
MAX_SUSPECT = 1_122_004_669_633
def call_star_witnesses(n: int):
"""
Find sets of witnesses whose agreement is sufficient to declare primality.
Presented by Matt Parker on Numberphile: https://youtu.be/_MscGSN5J6o
"""
if n < 1_373_653:
return [2, 3]
elif n < 25_326_001:
return [2, 3, 5]
elif n < MAX_SUSPECT:
return [2, 13, 23, 1_662_803]
else:
raise ValueError(f"Don't know star witnesses for x >= {MAX_SUSPECT}.")
if __name__ == "__main__":
parser = ArgumentParser()
parser.add_argument("n", type=int, nargs="?", default=100)
parser.add_argument("--top", type=int, default=5)
parser.add_argument("--verbose", action="store_true", default=False)
parser.add_argument("--output", type=str, default="")
args = parser.parse_args()
logging.basicConfig(level=logging.DEBUG if args.verbose else logging.INFO)
start = lap_start = time.perf_counter()
lies: Counter[int] = Counter()
try:
# Test all "suspects" up to given `n`
for n in range(5, args.n, 2):
s = Suspect(n)
# Get true verdict from star witnesses
star_witnesses = call_star_witnesses(n)
truth = is_prime(s, witnesses=star_witnesses)
if truth:
logging.debug(f"{n} is prime")
else:
logging.debug(f"{n} is not prime")
# Get statements from all potential witnesses and count lies
potential_witnesses = range(2, n)
for w in potential_witnesses:
if witness_statement(w, s) != truth:
lies[w] += 1
# Output some progress info every now and again
if (n + 1) % 1000 == 0:
lap_stop = time.perf_counter()
logging.info(f"Done {n + 1}/{args.n} ... ({lap_stop - lap_start:.2f}s)")
lap_start = lap_stop
except KeyboardInterrupt:
pass # Finish reporting statistics before exiting
stop = time.perf_counter()
logging.info(f"Done ({stop - start:.2f}s total)")
logging.info(f"The top {args.top} liars up to {n} are:")
logging.info(lies.most_common(args.top))
if args.output:
with open(args.output, "w", newline="") as f:
w = csv.DictWriter(f, fieldnames=["witness", "lies"])
w.writeheader()
for witness, lies in lies.most_common():
w.writerow({ "witness" : witness, "lies" : lies })
logging.info(f"Full results written to '{args.output}'.")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment