rulerpack 0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rulerpack/__init__.py +38 -0
- rulerpack/build.py +56 -0
- rulerpack/cli.py +68 -0
- rulerpack/count.py +292 -0
- rulerpack/experiments.py +133 -0
- rulerpack/marks.npy +0 -0
- rulerpack/marks.py +237 -0
- rulerpack/pack.py +111 -0
- rulerpack/primes.py +120 -0
- rulerpack/rulers.py +106 -0
- rulerpack/window.py +647 -0
- rulerpack-0.2.1.dist-info/METADATA +372 -0
- rulerpack-0.2.1.dist-info/RECORD +17 -0
- rulerpack-0.2.1.dist-info/WHEEL +5 -0
- rulerpack-0.2.1.dist-info/entry_points.txt +2 -0
- rulerpack-0.2.1.dist-info/licenses/LICENSE +21 -0
- rulerpack-0.2.1.dist-info/top_level.txt +1 -0
rulerpack/__init__.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""rulerpack: XOR rulers over the primes, and the pack that rides on them.
|
|
2
|
+
|
|
3
|
+
>>> import rulerpack as rp
|
|
4
|
+
>>> N = rp.pack(78501, 1000039)
|
|
5
|
+
>>> rp.unpack(N)
|
|
6
|
+
(78501, 1000039)
|
|
7
|
+
|
|
8
|
+
Families of ruler, each a Mersenne ladder scaled by a power of two:
|
|
9
|
+
|
|
10
|
+
rp.RP(i) (4i) ^ (4i+7) 7, 15, 7, 31, ... the original
|
|
11
|
+
rp.ruler(i, L, c) ((Li) ^ (Li+cL+1)) ^ 1 the general form
|
|
12
|
+
rp.R257(i) L=4, c=64 256 * ladder(i >> 6)
|
|
13
|
+
rp.R513(i) L=256, c=2 512 * ladder(i >> 1)
|
|
14
|
+
|
|
15
|
+
Pack family (L, c, M): K = 2p ^ ruler, X = 2p ^ Li, N = K*M + X.
|
|
16
|
+
Decode is mod, div, two XORs. No search, no primality, no table.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from .primes import primes_first, primes2, seeds, allowed, leftovers, isprime, wheel_pass, wheel_report
|
|
20
|
+
from .rulers import v2, ladder, RP, rp_xor1, ruler, R257, R513, key, topo, R896, carry, slide512, unslide512, slide_repeats
|
|
21
|
+
from .pack import (Family, F257, F513, DEFAULT, encode, decode, pack, unpack,
|
|
22
|
+
encode_array, decode_array)
|
|
23
|
+
from .build import build_kx, sweep
|
|
24
|
+
from .marks import Marks, LiveMarks
|
|
25
|
+
from .window import window, window_deep, residues_mod, any_prime_after, worker_pool, close_pools, ranked_window, RANK_KEYS, xor_additive, xor_additive_bits, gap_profile, squeeze_order, seeds_for, seed_depth, window_width, next_prime_after, next_prime_after_parallel, jfilter, primes_between, indexed_after, hinted_next, hint_of, hinted_family, hint_from_x, primes_from_x, hints_from_x, hint_walk
|
|
26
|
+
from . import experiments
|
|
27
|
+
from .count import primepi_sieve, phi, primepi_legendre, primepi_meissel, primepi, index_of, li, li_inverse, riemann_r, riemann_r_inverse, nth_prime, primepi_primecount, primepi_fastest
|
|
28
|
+
|
|
29
|
+
__version__ = "0.2.1"
|
|
30
|
+
__all__ = [
|
|
31
|
+
"experiments",
|
|
32
|
+
"primes_first", "primes2", "seeds", "allowed", "leftovers", "isprime", "wheel_pass", "wheel_report",
|
|
33
|
+
"v2", "ladder", "RP", "rp_xor1", "ruler", "R257", "R513", "key", "topo", "R896", "carry", "slide512", "unslide512", "slide_repeats",
|
|
34
|
+
"Family", "F257", "F513", "DEFAULT", "encode", "decode", "pack", "unpack",
|
|
35
|
+
"encode_array", "decode_array",
|
|
36
|
+
"build_kx", "sweep", "Marks", "LiveMarks", "window", "window_deep", "residues_mod", "any_prime_after", "worker_pool", "close_pools", "ranked_window", "RANK_KEYS", "xor_additive", "xor_additive_bits", "gap_profile", "squeeze_order", "seeds_for", "seed_depth", "window_width", "next_prime_after", "next_prime_after_parallel", "jfilter", "primes_between", "indexed_after", "hinted_next", "hint_of", "hinted_family", "hint_from_x", "primes_from_x", "hints_from_x", "hint_walk",
|
|
37
|
+
"primepi_sieve", "phi", "primepi_legendre", "primepi_meissel", "primepi", "index_of", "li", "li_inverse", "riemann_r", "riemann_r_inverse", "nth_prime", "primepi_primecount", "primepi_fastest",
|
|
38
|
+
]
|
rulerpack/build.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Sequential builders and the window sweep."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from .primes import allowed
|
|
6
|
+
from .rulers import RP
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def build_kx(n):
|
|
10
|
+
"""Build (L, K, X) step by step from the seed rule, with RP and 4i.
|
|
11
|
+
K = L ^ RP ^ 1, X = L ^ 4i. Asserts K ^ X == RP ^ 1 ^ 4i at every step."""
|
|
12
|
+
L, K, X = [], [], []
|
|
13
|
+
h = 1
|
|
14
|
+
for i in range(n):
|
|
15
|
+
if i == 0:
|
|
16
|
+
c = 4
|
|
17
|
+
else:
|
|
18
|
+
while True:
|
|
19
|
+
h += 2
|
|
20
|
+
if allowed(h):
|
|
21
|
+
break
|
|
22
|
+
c = 2 * h
|
|
23
|
+
r = RP(i)
|
|
24
|
+
ar = 4 * i
|
|
25
|
+
k = c ^ r ^ 1
|
|
26
|
+
x = c ^ ar
|
|
27
|
+
assert k ^ x == r ^ 1 ^ ar
|
|
28
|
+
L.append(c); K.append(k); X.append(x)
|
|
29
|
+
return (np.array(L, dtype=np.int64), np.array(K, dtype=np.int64),
|
|
30
|
+
np.array(X, dtype=np.int64))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def sweep(P, step=None, use_sieve=True, prime_test=None):
|
|
34
|
+
"""Walk odd candidates past the seed rule. With step, also require
|
|
35
|
+
h % step == P[i] % step (lookup mode). With prime_test (e.g. rp.isprime),
|
|
36
|
+
a candidate that fails it is rejected ("nah, next one") and counted.
|
|
37
|
+
|
|
38
|
+
Returns (index, built, true, rejected) at the first wrong-index hit,
|
|
39
|
+
or (None, rejected) if every prime in P is reproduced."""
|
|
40
|
+
h = 1
|
|
41
|
+
rejected = 0
|
|
42
|
+
for i in range(1, len(P)):
|
|
43
|
+
true = int(P[i])
|
|
44
|
+
while True:
|
|
45
|
+
h += 2
|
|
46
|
+
if not allowed(h, use_sieve):
|
|
47
|
+
continue
|
|
48
|
+
if step and h % step != true % step:
|
|
49
|
+
continue
|
|
50
|
+
if prime_test is not None and not prime_test(h):
|
|
51
|
+
rejected += 1
|
|
52
|
+
continue
|
|
53
|
+
break
|
|
54
|
+
if h != true:
|
|
55
|
+
return i, h, true, rejected
|
|
56
|
+
return None, rejected
|
rulerpack/cli.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""rulerpack command line.
|
|
2
|
+
|
|
3
|
+
rulerpack 78501 1000039 pack a pair
|
|
4
|
+
rulerpack 8592468642374094 unpack N
|
|
5
|
+
rulerpack --check 1000000 round-trip the first n primes
|
|
6
|
+
rulerpack --ladder 32 show the ladder and both rulers
|
|
7
|
+
rulerpack --between lo hi every prime in [lo, hi), sieve started at lo
|
|
8
|
+
rulerpack --next N the closest prime above N
|
|
9
|
+
options: --ruler 513|257 --shift S
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import sys
|
|
13
|
+
|
|
14
|
+
from .pack import Family, F513, F257
|
|
15
|
+
from .rulers import ladder, R257, R513, RP
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def main(argv=None):
|
|
19
|
+
argv = list(sys.argv[1:] if argv is None else argv)
|
|
20
|
+
fam = F513
|
|
21
|
+
if "--ruler" in argv:
|
|
22
|
+
k = argv.index("--ruler")
|
|
23
|
+
fam = {"513": F513, "257": F257}[argv[k + 1]]
|
|
24
|
+
del argv[k:k + 2]
|
|
25
|
+
if "--shift" in argv:
|
|
26
|
+
k = argv.index("--shift")
|
|
27
|
+
fam = Family(fam.L, fam.c, int(argv[k + 1]))
|
|
28
|
+
del argv[k:k + 2]
|
|
29
|
+
if not argv:
|
|
30
|
+
print(__doc__)
|
|
31
|
+
return 0
|
|
32
|
+
if argv[0] == "--check":
|
|
33
|
+
n = int(argv[1]) if len(argv) > 1 else 100_000
|
|
34
|
+
ok, last = fam.check(n)
|
|
35
|
+
print(f"{n:,} primes, last {last:,}, L={fam.L} c={fam.c} M=2^{fam.shift}: "
|
|
36
|
+
f"round-trip {'OK' if ok else 'FAILED'}")
|
|
37
|
+
return 0 if ok else 1
|
|
38
|
+
if argv[0] == "--between":
|
|
39
|
+
from .window import primes_between
|
|
40
|
+
P = primes_between(int(argv[1]), int(argv[2]))
|
|
41
|
+
print("\n".join(str(int(p)) for p in P))
|
|
42
|
+
return 0
|
|
43
|
+
if argv[0] == "--next":
|
|
44
|
+
from .window import next_prime_after
|
|
45
|
+
print(next_prime_after(int(argv[1])))
|
|
46
|
+
return 0
|
|
47
|
+
if argv[0] == "--ladder":
|
|
48
|
+
n = int(argv[1]) if len(argv) > 1 else 32
|
|
49
|
+
print(f"{'i':>4} {'ladder':>7} {'RP':>5} {'R257':>7} {'R513':>7}")
|
|
50
|
+
for i in range(n):
|
|
51
|
+
print(f"{i:>4} {ladder(i):>7} {RP(i):>5} {R257(i):>7} {R513(i):>7}")
|
|
52
|
+
return 0
|
|
53
|
+
if len(argv) == 2:
|
|
54
|
+
i, p = int(argv[0]), int(argv[1])
|
|
55
|
+
K, X = fam.encode(i, p)
|
|
56
|
+
N = fam.pack(i, p)
|
|
57
|
+
print(f"i={i} p={p} (L={fam.L} c={fam.c} M=2^{fam.shift})")
|
|
58
|
+
print(f"K = {K}\nX = {X}\nN = {N} ({N.bit_length()} bits)")
|
|
59
|
+
print(f"unpack(N) = {fam.unpack(N)}")
|
|
60
|
+
return 0
|
|
61
|
+
N = int(argv[0])
|
|
62
|
+
i, p = fam.unpack(N)
|
|
63
|
+
print(f"N -> i={i} p={p} (L={fam.L} c={fam.c} M=2^{fam.shift})")
|
|
64
|
+
return 0
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
if __name__ == "__main__":
|
|
68
|
+
sys.exit(main())
|
rulerpack/count.py
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
"""Prime counting, the forward map: prime in, index out.
|
|
2
|
+
|
|
3
|
+
Each method is its own function so they can be compared.
|
|
4
|
+
|
|
5
|
+
primepi_sieve(x) count by sieving to x O(x)
|
|
6
|
+
phi(x, a) Legendre: how many n <= x survive the first a primes
|
|
7
|
+
primepi_legendre(x) pi(x) = phi(x, a) + a - 1, a = pi(sqrt x)
|
|
8
|
+
primepi_meissel(x) Legendre with a = pi(cbrt x) and a correction sum
|
|
9
|
+
primepi(x) the fastest exact one here (meissel)
|
|
10
|
+
index_of(p) zero-based index of prime p, = primepi(p) - 1
|
|
11
|
+
|
|
12
|
+
All exact. Pure integers plus one numpy sieve table.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import sys
|
|
16
|
+
from math import isqrt
|
|
17
|
+
|
|
18
|
+
import numpy as np
|
|
19
|
+
|
|
20
|
+
from .primes import isprime
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _icbrt(x):
|
|
24
|
+
r = int(round(x ** (1 / 3)))
|
|
25
|
+
while r ** 3 > x:
|
|
26
|
+
r -= 1
|
|
27
|
+
while (r + 1) ** 3 <= x:
|
|
28
|
+
r += 1
|
|
29
|
+
return r
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _sieve_table(limit):
|
|
33
|
+
"""(primes, pi) with pi[n] = number of primes <= n, for n <= limit."""
|
|
34
|
+
s = np.ones(limit + 1, dtype=bool)
|
|
35
|
+
s[:2] = False
|
|
36
|
+
for q in range(2, isqrt(limit) + 1):
|
|
37
|
+
if s[q]:
|
|
38
|
+
s[q * q::q] = False
|
|
39
|
+
pi = np.cumsum(s, dtype=np.int64)
|
|
40
|
+
return np.flatnonzero(s).astype(np.int64), pi
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def primepi_sieve(x):
|
|
44
|
+
"""Count primes <= x by sieving. The slow, obviously-right reference."""
|
|
45
|
+
x = int(x)
|
|
46
|
+
if x < 2:
|
|
47
|
+
return 0
|
|
48
|
+
return int(_sieve_table(x)[1][x])
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# Wheel for small a: phi(x, a) is periodic mod the product of the first a primes.
|
|
52
|
+
_WHEEL_A = 7 # 2*3*5*7*11*13*17 = 510510
|
|
53
|
+
_WPRIMES = [2, 3, 5, 7, 11, 13, 17]
|
|
54
|
+
_WMOD = 510510
|
|
55
|
+
_wheel_mask = np.ones(_WMOD, dtype=bool)
|
|
56
|
+
for _q in _WPRIMES:
|
|
57
|
+
_wheel_mask[::_q] = False
|
|
58
|
+
_wheel_mask[0] = False
|
|
59
|
+
_WHEEL = np.concatenate(([0], np.cumsum(_wheel_mask, dtype=np.int64)))
|
|
60
|
+
_WHEEL_COUNT = int(_WHEEL[_WMOD]) # survivors per period (96 * 1430... = 92160)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _phi(x, a, primes):
|
|
64
|
+
if a <= _WHEEL_A:
|
|
65
|
+
# count n in [1, x] coprime to the first a primes
|
|
66
|
+
if a == _WHEEL_A:
|
|
67
|
+
return (x // _WMOD) * _WHEEL_COUNT + int(_WHEEL[x % _WMOD + 1] - _WHEEL[1]) + int(_wheel_mask[0])
|
|
68
|
+
if a == 0:
|
|
69
|
+
return x
|
|
70
|
+
if a == 1:
|
|
71
|
+
return (x + 1) // 2
|
|
72
|
+
if a == 2:
|
|
73
|
+
return (x // 6) * 2 + (1 if x % 6 >= 1 else 0) + (1 if x % 6 >= 5 else 0)
|
|
74
|
+
raise ValueError("_phi handles a in {0, 1, 2, 7} only")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _make_phi(primes, pi=None):
|
|
78
|
+
"""Legendre recursion phi(x,a) = phi(x,a-1) - phi(x//p_a, a-1) with a memo.
|
|
79
|
+
|
|
80
|
+
With a pi table: when p_a^2 > x every survivor of the first a primes is 1 or a
|
|
81
|
+
prime, so phi(x, a) = pi(x) - a + 1 is read off instead of recursed."""
|
|
82
|
+
P = [int(p) for p in primes]
|
|
83
|
+
memo = {}
|
|
84
|
+
need = len(P) + 1000 # recursion is one frame per prime
|
|
85
|
+
if sys.getrecursionlimit() < need:
|
|
86
|
+
sys.setrecursionlimit(need)
|
|
87
|
+
limit = len(pi) - 1 if pi is not None else -1
|
|
88
|
+
pil = pi.tolist() if pi is not None else None
|
|
89
|
+
|
|
90
|
+
def phi(x, a):
|
|
91
|
+
if a <= 2 or (a == _WHEEL_A):
|
|
92
|
+
return _phi(x, a, P)
|
|
93
|
+
pa = P[a - 1]
|
|
94
|
+
if x < pa:
|
|
95
|
+
return 1
|
|
96
|
+
if x <= limit and pa * pa > x:
|
|
97
|
+
return pil[x] - a + 1
|
|
98
|
+
key = (x, a)
|
|
99
|
+
v = memo.get(key)
|
|
100
|
+
if v is None:
|
|
101
|
+
v = phi(x, a - 1) - phi(x // pa, a - 1)
|
|
102
|
+
memo[key] = v
|
|
103
|
+
return v
|
|
104
|
+
return phi
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def phi(x, a):
|
|
108
|
+
"""Legendre's phi: how many n in [1, x] have no prime factor among the first a primes."""
|
|
109
|
+
x, a = int(x), int(a)
|
|
110
|
+
primes, _ = _sieve_table(max(a * (int(np.log(a + 2)) + 3), 20))
|
|
111
|
+
while len(primes) < a:
|
|
112
|
+
primes, _ = _sieve_table(len(primes) * 4 + 100)
|
|
113
|
+
return _make_phi(primes)(x, a)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def primepi_legendre(x):
|
|
117
|
+
"""pi(x) = phi(x, a) + a - 1 with a = pi(sqrt x). Table only to sqrt x."""
|
|
118
|
+
x = int(x)
|
|
119
|
+
if x < 2:
|
|
120
|
+
return 0
|
|
121
|
+
r = isqrt(x)
|
|
122
|
+
primes, pi = _sieve_table(r)
|
|
123
|
+
a = int(pi[r])
|
|
124
|
+
return _make_phi(primes, pi)(x, a) + a - 1
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def primepi_meissel(x):
|
|
128
|
+
"""Meissel: a = pi(cbrt x), b = pi(sqrt x).
|
|
129
|
+
pi(x) = phi(x, a) + a - 1 - sum_{a < i <= b} (pi(x / p_i) - i + 1).
|
|
130
|
+
Needs pi() up to x^(2/3), from one sieve table."""
|
|
131
|
+
x = int(x)
|
|
132
|
+
if x < 2:
|
|
133
|
+
return 0
|
|
134
|
+
c = _icbrt(x)
|
|
135
|
+
r = isqrt(x)
|
|
136
|
+
primes, pi = _sieve_table(max(x // max(c, 1), r) + 1)
|
|
137
|
+
a = int(pi[c])
|
|
138
|
+
b = int(pi[r])
|
|
139
|
+
ph = _make_phi(primes, pi)
|
|
140
|
+
total = ph(x, a) + a - 1
|
|
141
|
+
idx = np.arange(a + 1, b + 1, dtype=np.int64) # 1-based i, prime p_i = primes[i-1]
|
|
142
|
+
total -= int(np.sum(pi[x // primes[a:b]] - idx + 1))
|
|
143
|
+
return total
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
primepi = primepi_meissel
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def index_of(p):
|
|
150
|
+
"""Zero-based index of prime p in the prime list; raises if p is not prime."""
|
|
151
|
+
p = int(p)
|
|
152
|
+
if not isprime(p):
|
|
153
|
+
raise ValueError(f"{p} is not prime")
|
|
154
|
+
return primepi(p) - 1
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# ── the inverse: index in, prime out ─────────────────────────────────────────
|
|
158
|
+
|
|
159
|
+
def li(x):
|
|
160
|
+
"""Logarithmic integral li(x), the classic estimate of pi(x). Series by Ramanujan."""
|
|
161
|
+
from math import log, sqrt, exp, factorial
|
|
162
|
+
x = float(x)
|
|
163
|
+
if x < 2:
|
|
164
|
+
return 0.0
|
|
165
|
+
g = 0.5772156649015329
|
|
166
|
+
lx = log(x)
|
|
167
|
+
total = 0.0
|
|
168
|
+
inner = 0.0
|
|
169
|
+
for n in range(1, 200):
|
|
170
|
+
inner += 1 / (2 * ((n - 1) // 2) + 1) if n % 2 == 1 else 0
|
|
171
|
+
k = (n - 1) // 2
|
|
172
|
+
s = sum(1 / (2 * j + 1) for j in range(0, k + 1))
|
|
173
|
+
term = ((-1) ** (n - 1)) * lx ** n / (2 ** (n - 1) * factorial(n)) * s
|
|
174
|
+
total += term
|
|
175
|
+
if abs(term) < 1e-17 * abs(total) and n > 10:
|
|
176
|
+
break
|
|
177
|
+
return g + log(lx) + sqrt(x) * total
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def li_inverse(y):
|
|
181
|
+
"""x with li(x) = y, by Newton on li; derivative is 1/ln x."""
|
|
182
|
+
from math import log
|
|
183
|
+
y = float(y)
|
|
184
|
+
if y < 2:
|
|
185
|
+
return 2.0
|
|
186
|
+
x = y * log(y) if y > 3 else 3.0
|
|
187
|
+
for _ in range(60):
|
|
188
|
+
step = (li(x) - y) * log(x)
|
|
189
|
+
x -= step
|
|
190
|
+
if abs(step) < 0.5:
|
|
191
|
+
break
|
|
192
|
+
return x
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
_MOBIUS = [0, 1, -1, -1, 0, -1, 1, -1, 0, 0, 1, -1, 0, -1, 1, 1, 0, -1, 0, -1, 0, 1, 1, -1, 0,
|
|
196
|
+
0, 1, 0, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, 1, 1, 0, -1, -1, -1, 0, 0, 1, -1, 0, 0,
|
|
197
|
+
0, 1, 0, -1, 0, 1, 0, 1, 1, -1, 0]
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def riemann_r(x):
|
|
201
|
+
"""Riemann's R(x) = sum mu(n)/n * li(x^(1/n)). li minus its sqrt(x) bias; much closer to pi(x)."""
|
|
202
|
+
x = float(x)
|
|
203
|
+
s = 0.0
|
|
204
|
+
for n in range(1, len(_MOBIUS)):
|
|
205
|
+
m = _MOBIUS[n]
|
|
206
|
+
if m == 0:
|
|
207
|
+
continue
|
|
208
|
+
v = x ** (1.0 / n)
|
|
209
|
+
if v < 2:
|
|
210
|
+
break
|
|
211
|
+
s += m / n * li(v)
|
|
212
|
+
return s
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def riemann_r_inverse(y):
|
|
216
|
+
"""x with R(x) = y, Newton from the li inverse."""
|
|
217
|
+
from math import log
|
|
218
|
+
y = float(y)
|
|
219
|
+
if y < 2:
|
|
220
|
+
return 2.0
|
|
221
|
+
x = li_inverse(y)
|
|
222
|
+
for _ in range(50):
|
|
223
|
+
step = (riemann_r(x) - y) * log(x)
|
|
224
|
+
x -= step
|
|
225
|
+
if abs(step) < 0.5:
|
|
226
|
+
break
|
|
227
|
+
return x
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _walk(x, k):
|
|
231
|
+
"""From integer x, move k primes up (k > 0) or down (k < 0). Returns the prime landed on.
|
|
232
|
+
k == 0 means the largest prime <= x."""
|
|
233
|
+
from .primes import allowed
|
|
234
|
+
n = int(x)
|
|
235
|
+
if k >= 0:
|
|
236
|
+
if n % 2 == 0:
|
|
237
|
+
n -= 1
|
|
238
|
+
# position on the largest prime <= x first
|
|
239
|
+
while not (n >= 2 and isprime(n)):
|
|
240
|
+
n -= 1 if n > 3 else 0
|
|
241
|
+
if n < 3:
|
|
242
|
+
n = 2
|
|
243
|
+
break
|
|
244
|
+
for _ in range(k):
|
|
245
|
+
n += 1 if n == 2 else 2
|
|
246
|
+
while not isprime(n):
|
|
247
|
+
n += 2
|
|
248
|
+
return n
|
|
249
|
+
n = _walk(x, 0)
|
|
250
|
+
for _ in range(-k):
|
|
251
|
+
n -= 1 if n == 3 else 2
|
|
252
|
+
while not isprime(n):
|
|
253
|
+
n -= 2
|
|
254
|
+
return n
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def nth_prime(i, report=False, guess=None):
|
|
258
|
+
"""The prime at zero-based index i, exact.
|
|
259
|
+
|
|
260
|
+
guess(y) proposes x (default riemann_r_inverse; li_inverse for comparison),
|
|
261
|
+
primepi pins pi(x) exactly, the walk closes the gap with isprime, up or down.
|
|
262
|
+
report=True also returns (guess, pi(guess), gap walked)."""
|
|
263
|
+
guess = riemann_r_inverse if guess is None else guess
|
|
264
|
+
i = int(i)
|
|
265
|
+
if i < 0:
|
|
266
|
+
raise ValueError("index must be >= 0")
|
|
267
|
+
if i < 6:
|
|
268
|
+
return [2, 3, 5, 7, 11, 13][i] if not report else ([2, 3, 5, 7, 11, 13][i], None, None, 0)
|
|
269
|
+
x = int(guess(i + 1))
|
|
270
|
+
px = primepi(x) # number of primes <= x
|
|
271
|
+
gap = (i + 1) - px # how many primes past x the target is
|
|
272
|
+
p = _walk(x, gap)
|
|
273
|
+
return (p, x, px, gap) if report else p
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def primepi_primecount(x):
|
|
277
|
+
"""pi(x) by Kim Walisch's primecount through its shared library, if installed
|
|
278
|
+
(Ubuntu: `sudo apt install primecount`). Raises OSError when it is not."""
|
|
279
|
+
import ctypes, ctypes.util
|
|
280
|
+
name = ctypes.util.find_library("primecount") or "libprimecount.so.8"
|
|
281
|
+
lib = ctypes.CDLL(name)
|
|
282
|
+
lib.primecount_pi.restype = ctypes.c_int64
|
|
283
|
+
lib.primecount_pi.argtypes = [ctypes.c_int64]
|
|
284
|
+
return int(lib.primecount_pi(int(x)))
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def primepi_fastest(x):
|
|
288
|
+
"""primecount when present, else our Meissel."""
|
|
289
|
+
try:
|
|
290
|
+
return primepi_primecount(x)
|
|
291
|
+
except OSError:
|
|
292
|
+
return primepi_meissel(x)
|
rulerpack/experiments.py
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""rulerpack.experiments: things that were fun in the session and might be more.
|
|
2
|
+
|
|
3
|
+
Nothing here is used by the rest of the library. Each function reproduces an IPython moment
|
|
4
|
+
exactly and names what it saw, so it can be picked up again later.
|
|
5
|
+
|
|
6
|
+
import rulerpack.experiments as rx
|
|
7
|
+
|
|
8
|
+
Experiment 1, the step-10 index (In [475]-[478]):
|
|
9
|
+
|
|
10
|
+
idx = rx.stride_index(primes) # np.arange(2, len(primes), 10)
|
|
11
|
+
rx.stripe(primes) # (idx ^ 2p) % 4 : 2, 0, 2, 0, ... after the first
|
|
12
|
+
rx.jumps(primes) # where idx ^ 2p leaps by more than 300
|
|
13
|
+
rx.block_crossings(primes) # where 2p crosses a multiple of 256
|
|
14
|
+
rx.last_before_blocks(primes) # 127, 251, 383, 509, ... the last prime under each 128k
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import numpy as np
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def stride_index(primes, start=2, step=10):
|
|
21
|
+
"""`np.arange(start, len(primes), step)`: every tenth index from the third prime."""
|
|
22
|
+
return np.arange(start, len(primes), step, dtype=np.int64)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def picked(primes, start=2, step=10):
|
|
26
|
+
"""The primes the stride index picks, `primes[stride_index(primes)]`."""
|
|
27
|
+
primes = np.asarray(primes, dtype=np.int64)
|
|
28
|
+
return primes[stride_index(primes, start, step)]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def xor_index(primes, start=2, step=10):
|
|
32
|
+
"""`idx ^ (2p)` over the first len(idx) primes: the array of In [477].
|
|
33
|
+
|
|
34
|
+
XOR cancels the top bits the two sides share, so the values sit small and leap only
|
|
35
|
+
when one side crosses a power of two or a multiple of 256."""
|
|
36
|
+
primes = np.asarray(primes, dtype=np.int64)
|
|
37
|
+
idx = stride_index(primes, start, step)
|
|
38
|
+
return idx ^ (primes[: len(idx)] * 2)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def stripe(primes, start=2, step=10):
|
|
42
|
+
"""`(idx ^ 2p) % 4`: 2, then 0, 2, 0, 2, ... forever.
|
|
43
|
+
|
|
44
|
+
Bit 1 of idx alternates because the step is 2 mod 4; 2p is 2 mod 4 for every odd p; the
|
|
45
|
+
one exception is the first entry, where p = 2 and 2p = 4. That is the whole stripe."""
|
|
46
|
+
return xor_index(primes, start, step) % 4
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def jumps(primes, start=2, step=10, more_than=300):
|
|
50
|
+
"""Indices of the entries where `idx ^ 2p` has just leapt by more than `more_than`.
|
|
51
|
+
|
|
52
|
+
Two kinds: 2p crossing a power of two (1024, 2048, 4096, 8192 at indices 97, 172, 309,
|
|
53
|
+
564 on the first 1000 primes) and idx crossing a multiple of 256, every 25.6 entries."""
|
|
54
|
+
x = xor_index(primes, start, step)
|
|
55
|
+
return np.flatnonzero(np.abs(np.diff(x)) > more_than) + 1
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def block_crossings(primes, block=256):
|
|
59
|
+
"""`np.flatnonzero(np.diff((2p) >> 8))`: the indices where 2p steps into the next block.
|
|
60
|
+
|
|
61
|
+
With block 256 that is p crossing a multiple of 128. The spacing is the count of primes
|
|
62
|
+
in each 128-wide block, which thins as the prime number theorem says."""
|
|
63
|
+
primes = np.asarray(primes, dtype=np.int64)
|
|
64
|
+
return np.flatnonzero(np.diff((primes * 2) // block))
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def last_before_blocks(primes, width=128):
|
|
68
|
+
"""The last prime under each multiple of `width`: 127, 251, 383, 509, 631, ...
|
|
69
|
+
|
|
70
|
+
The largest primes under powers of two show up here on the dot: 127, 1021, 2039, 4093,
|
|
71
|
+
and 8191 when the list reaches it."""
|
|
72
|
+
primes = np.asarray(primes, dtype=np.int64)
|
|
73
|
+
return primes[block_crossings(primes, 2 * width)]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def gaps_to_blocks(primes, width=128):
|
|
77
|
+
"""How far each `last_before_blocks` prime sits under its boundary. Always odd."""
|
|
78
|
+
last = last_before_blocks(primes, width)
|
|
79
|
+
return width * (np.arange(len(last)) + 1) - last
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# ── experiment 1, continued: the layers under the stripe (In [481]) ───────────
|
|
83
|
+
|
|
84
|
+
def layers(primes, moduli=(4, 8, 16, 32), start=2, step=10, n=40):
|
|
85
|
+
"""`z % m` for each modulus, z = idx ^ 2p: one more bit of the primes per row.
|
|
86
|
+
|
|
87
|
+
(a ^ b) % m == (a % m) ^ (b % m) for a power of two, so each row is the stripe with one
|
|
88
|
+
more layer on top, and the layers underneath never change. At % 8 the new bit is whether
|
|
89
|
+
the prime is 1 or 3 mod 4; at % 16 its residue mod 8; and so on."""
|
|
90
|
+
z = xor_index(primes, start, step)[:n]
|
|
91
|
+
return {m: z % m for m in moduli}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def bit_layers(primes, bits=(1, 2, 3, 4), start=2, step=10, n=40):
|
|
95
|
+
"""`(z >> bit) & 1` for each bit: the layers on their own."""
|
|
96
|
+
z = xor_index(primes, start, step)[:n]
|
|
97
|
+
return {b: (z >> b) & 1 for b in bits}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def recover(primes, m=32, start=2, step=10):
|
|
101
|
+
"""Peel the index back off a layer: `((z % m) ^ (idx % m)) >> 1 == p % (m // 2)`.
|
|
102
|
+
|
|
103
|
+
XOR is its own inverse, so no layer hides anything: the `% 32` row carries the primes'
|
|
104
|
+
residues mod 16, reversibly mixed with the index. The same fact as `rp.unslide512`."""
|
|
105
|
+
primes = np.asarray(primes, dtype=np.int64)
|
|
106
|
+
idx = stride_index(primes, start, step)
|
|
107
|
+
z = idx ^ (primes[: len(idx)] * 2)
|
|
108
|
+
return ((z % m) ^ (idx % m)) >> 1
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def side_by_side(primes, m=32, start=2, step=10, n=40):
|
|
112
|
+
"""Columns `idx % m`, `2p % m`, `z % m`: where each repeated value in a layer comes from.
|
|
113
|
+
|
|
114
|
+
The runs of 28, 14, 28, 14 are the index stepping through 2, 12, 22, 32 mod 32 against
|
|
115
|
+
primes that happen to sit in the same residue class; read the rows and the run is traced."""
|
|
116
|
+
primes = np.asarray(primes, dtype=np.int64)
|
|
117
|
+
idx = stride_index(primes, start, step)[:n]
|
|
118
|
+
p2 = primes[:n] * 2
|
|
119
|
+
return np.column_stack((idx % m, p2 % m, (idx ^ p2) % m))
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def stripe_control(n=1000, start=2, step=10):
|
|
123
|
+
"""The stripe over odd composites instead of primes: 0, 2, 0, 2, ... with no exception.
|
|
124
|
+
|
|
125
|
+
The primes' version opens with an extra 2 because the first prime is 2; the composites
|
|
126
|
+
have no even member, so the alternation starts clean.
|
|
127
|
+
|
|
128
|
+
The stripe is a fact about bit 1 of an index stepping by 10 and of twice an odd number.
|
|
129
|
+
Any odd sequence gives it, which is the control that says it is not about primes."""
|
|
130
|
+
odds = np.arange(9, 9 + 2 * n, 2, dtype=np.int64)
|
|
131
|
+
comps = odds[np.array([any(o % q == 0 for q in (3, 5, 7)) for o in odds])][:n]
|
|
132
|
+
idx = np.arange(start, len(comps) * step, step, dtype=np.int64)[: len(comps)]
|
|
133
|
+
return (idx ^ (comps[: len(idx)] * 2)) % 4
|
rulerpack/marks.npy
ADDED
|
Binary file
|