rulerpack 0.2.1__tar.gz
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-0.2.1/LICENSE +21 -0
- rulerpack-0.2.1/PKG-INFO +372 -0
- rulerpack-0.2.1/README.md +356 -0
- rulerpack-0.2.1/pyproject.toml +27 -0
- rulerpack-0.2.1/setup.cfg +4 -0
- rulerpack-0.2.1/src/rulerpack/__init__.py +38 -0
- rulerpack-0.2.1/src/rulerpack/build.py +56 -0
- rulerpack-0.2.1/src/rulerpack/cli.py +68 -0
- rulerpack-0.2.1/src/rulerpack/count.py +292 -0
- rulerpack-0.2.1/src/rulerpack/experiments.py +133 -0
- rulerpack-0.2.1/src/rulerpack/marks.npy +0 -0
- rulerpack-0.2.1/src/rulerpack/marks.py +237 -0
- rulerpack-0.2.1/src/rulerpack/pack.py +111 -0
- rulerpack-0.2.1/src/rulerpack/primes.py +120 -0
- rulerpack-0.2.1/src/rulerpack/rulers.py +106 -0
- rulerpack-0.2.1/src/rulerpack/window.py +647 -0
- rulerpack-0.2.1/src/rulerpack.egg-info/PKG-INFO +372 -0
- rulerpack-0.2.1/src/rulerpack.egg-info/SOURCES.txt +21 -0
- rulerpack-0.2.1/src/rulerpack.egg-info/dependency_links.txt +1 -0
- rulerpack-0.2.1/src/rulerpack.egg-info/entry_points.txt +2 -0
- rulerpack-0.2.1/src/rulerpack.egg-info/requires.txt +7 -0
- rulerpack-0.2.1/src/rulerpack.egg-info/top_level.txt +1 -0
- rulerpack-0.2.1/tests/test_rulerpack.py +491 -0
rulerpack-0.2.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lars Rocha
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
rulerpack-0.2.1/PKG-INFO
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rulerpack
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: Lars Rocha's XOR rulers: pack a prime and its index into one integer and recover both with mod and XOR.
|
|
5
|
+
Author: Lars Rocha
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: numpy>=1.24
|
|
11
|
+
Provides-Extra: test
|
|
12
|
+
Requires-Dist: pytest; extra == "test"
|
|
13
|
+
Provides-Extra: fast
|
|
14
|
+
Requires-Dist: gmpy2; extra == "fast"
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# rulerpack
|
|
18
|
+
|
|
19
|
+
XOR rulers over the primes, and the pack that rides on them.
|
|
20
|
+
Created by Lars Rocha. Python implementation with Claude assisting.
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
import rulerpack as rp
|
|
24
|
+
|
|
25
|
+
N = rp.pack(78501, 1000039) # 8592468642374094, one integer
|
|
26
|
+
rp.unpack(N) # (78501, 1000039), the index and the prime back
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Decoding is mod, div and two XORs. No search, no primality test, no table.
|
|
30
|
+
|
|
31
|
+
## Credit
|
|
32
|
+
|
|
33
|
+
The ideas in this library are Lars Rocha's. Claude assisted with the
|
|
34
|
+
implementation and the measurements; that does not imply ownership.
|
|
35
|
+
|
|
36
|
+
| idea | what it became here |
|
|
37
|
+
|---|---|
|
|
38
|
+
| XOR the doubled prime against a ruler and against the index lattice | the pack: `K ^ X = L(i+c)`, one integer opens to the pair in four ops, no collisions |
|
|
39
|
+
| sieve the leftovers with the 3..131 seeds | `allowed`, `window`; taken to the square root it is `primes_between`, no primality test |
|
|
40
|
+
| anchor at a number and calculate the residues there | Eratosthenes started anywhere: one division per seed, then strike-outs |
|
|
41
|
+
| the prime closest to the anchor | survivors in distance order; `next_prime_after` is the first |
|
|
42
|
+
| the survivors are a list, not a stream | `next_prime_after_parallel`: certify on every core, 4x gmpy2's `next_prime` at 12,000 bits |
|
|
43
|
+
| count once, then increment | `indexed_after`: next n primes with exact indices and packs; thousands of times faster than calling primecount's `nth_prime` per index, about 5x slower than primesieve's list mode, which returns no indices |
|
|
44
|
+
| remember the counts | `Marks` and `LiveMarks`: each checkpoint a packed N |
|
|
45
|
+
| the seed rule at the cube root | its rejections are Meissel's correction term, measured equal |
|
|
46
|
+
|
|
47
|
+
The segmented sieve, Legendre's phi, Meissel's formula, Miller-Rabin and the
|
|
48
|
+
li / R estimates are classical and credited to their authors. primecount is
|
|
49
|
+
Kim Walisch's and is used as an optional counter.
|
|
50
|
+
|
|
51
|
+
## The ruler
|
|
52
|
+
|
|
53
|
+
Every ruler here is a Mersenne ladder, `m ^ (m+1)`, scaled by a power of two.
|
|
54
|
+
Drawn as tick marks it is a tape measure: every block gets a tick, and the
|
|
55
|
+
tick is taller at every power of two.
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
0 | 1
|
|
59
|
+
1 || 3
|
|
60
|
+
2 | 1
|
|
61
|
+
3 ||| 7
|
|
62
|
+
4 | 1
|
|
63
|
+
5 || 3
|
|
64
|
+
6 | 1
|
|
65
|
+
7 |||| 15
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
| name | formula | equals |
|
|
69
|
+
|---|---|---|
|
|
70
|
+
| `rp.RP(i)` | `(4i) ^ (4i+7)` | `2^(v2(i+1)+3) - 1`, the original 7, 15, 7, 31, ... |
|
|
71
|
+
| `rp.ruler(i, L, c)` | `((Li) ^ (Li+cL+1)) ^ 1` | `L * (i ^ (i+c))` |
|
|
72
|
+
| `rp.R257(i)` | L=4, c=64 | `256 * ladder(i >> 6)` |
|
|
73
|
+
| `rp.R513(i)` | L=256, c=2 | `512 * ladder(i >> 1)` |
|
|
74
|
+
| `rp.R896(i)` | `(896 + 512i) ^ (512i)` | `384 + 512 * ladder(i)`: 896, 1920, 896, 3968, ... |
|
|
75
|
+
| `rp.carry(i)` | `ladder(i) ^ ladder(i+1)` | 2, 6, 6, 2, 2, 14, 14, 2, ... the ruler's carry |
|
|
76
|
+
|
|
77
|
+
**The 512 slide.** `rp.slide512(primes)` is the session's `((p*512) ^ 512) ^ R896(i)`;
|
|
78
|
+
every entry is `384 + 512 * ((p ^ 1) ^ ladder(i))`, so the array is 384 mod 512 and
|
|
79
|
+
128 mod 256 for any input at all, and `rp.unslide512(B, i)` reads the primes straight
|
|
80
|
+
back. `rp.slide_repeats(primes)` finds where it repeats, `B[i] == B[i+1]`, which is
|
|
81
|
+
exactly where `p ^ p' == carry(i)`: 165 of the first 2,261 steps, two thirds of them
|
|
82
|
+
twin primes. The carry is independent of the gap, so the repeats are a lamp on the
|
|
83
|
+
sequence rather than a sieve of it; the measurements are in `ruler_stack` next door.
|
|
84
|
+
|
|
85
|
+
## The pack
|
|
86
|
+
|
|
87
|
+
For a prime `p` at zero-based index `i`, in the family `(L, c, M)`:
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
K = 2p ^ ruler(i, L, c)
|
|
91
|
+
X = 2p ^ L*i
|
|
92
|
+
N = K*M + X M = 2^shift, fixed for the family; X < M is checked on encode
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Then `K ^ X = L*(i + c)` for every `i` and `p`, so
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
X = N mod M, K = N div M, i = (K ^ X)/L - c, p = (X ^ L*i)/2
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`M` is a parameter of the family like `L` and `c`. It is never stored in `N`
|
|
102
|
+
and never guessed back from it; guessing it is a search and the search collides.
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
rp.F513 # Family(L=256, c=2, shift=32), the default
|
|
106
|
+
rp.F257 # Family(L=4, c=64, shift=32)
|
|
107
|
+
rp.Family(256, 2, 1100) # for 1000-bit primes
|
|
108
|
+
rp.encode(i, p), rp.decode(K, X)
|
|
109
|
+
rp.encode_array(P) # numpy, a million primes at once
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Verified round trip on the first 1,000,000 primes for both families.
|
|
113
|
+
|
|
114
|
+
## Counting, the forward map
|
|
115
|
+
|
|
116
|
+
Each method is its own function so they can be compared.
|
|
117
|
+
|
|
118
|
+
| function | what | π(10^9) |
|
|
119
|
+
|---|---|---|
|
|
120
|
+
| `rp.primepi_sieve(x)` | sieve to x | 28.9 s |
|
|
121
|
+
| `rp.phi(x, a)` | Legendre: numbers up to x with no factor among the first a primes | |
|
|
122
|
+
| `rp.primepi_legendre(x)` | `phi(x, a) + a - 1`, a = π(√x) | 4.3 s |
|
|
123
|
+
| `rp.primepi_meissel(x)` | a = π(∛x) plus the correction sum; `rp.primepi` | 0.07 s |
|
|
124
|
+
| `rp.index_of(p)` | `primepi(p) - 1`, refuses a composite | |
|
|
125
|
+
| `rp.nth_prime(i)` | Riemann R guesses, Meissel pins, the walk closes | billionth in 0.67 s |
|
|
126
|
+
|
|
127
|
+
All exact, checked against the published values of π up to 10^12.
|
|
128
|
+
|
|
129
|
+
The seed rule (odd, no factor in 3..131, not a square) is Legendre's `phi`
|
|
130
|
+
applied one candidate at a time. With seeds up to ∛x, the composites that
|
|
131
|
+
survive the rule are exactly the terms of Meissel's correction sum. Checked:
|
|
132
|
+
at x = 10^6 both are 42,286.
|
|
133
|
+
|
|
134
|
+
If you need fast counting, use [primecount](https://github.com/kimwalisch/primecount).
|
|
135
|
+
It is about 100x faster than this at 10^12. This library counts so the pack
|
|
136
|
+
can be tested from nothing; counting is not what it is for.
|
|
137
|
+
|
|
138
|
+
## Marks, our own way
|
|
139
|
+
|
|
140
|
+
Sieve once, keep every k-th prime as a packed `N`, and from then on walk from
|
|
141
|
+
the nearest mark with the seed rule and `isprime`. No li, no Riemann, no
|
|
142
|
+
Meissel. Each mark carries its own index and prime, so the table is self-describing.
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
M = rp.Marks.build(50_000_000, every=1024) # 4.4 s, 48,829 marks
|
|
146
|
+
M.nth_prime(49_999_999) # 982451653 in about 1 ms
|
|
147
|
+
M.index_of(982451653) # 49999999
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Flat a few milliseconds at any index the table covers. The Rust crate
|
|
151
|
+
(`rulerpack-rs`) does the same with a spacing of 64 in about 100 µs.
|
|
152
|
+
`M.pi(x)` counts to any x the table covers by the same walk.
|
|
153
|
+
|
|
154
|
+
## Live marks, the table that fills itself
|
|
155
|
+
|
|
156
|
+
Same walk, same pack, no table up front. A block you have never touched costs
|
|
157
|
+
one counting call (`nth_prime`: Riemann guess, Meissel pin, walk) and is stored
|
|
158
|
+
as a packed N; every later lookup in that block is a walk.
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
lm = rp.LiveMarks(every=64)
|
|
162
|
+
lm.nth_prime(999_999_999) # 22801763489 in 0.67 s, one mark computed
|
|
163
|
+
lm.nth_prime(999_999_998) # 22801763477 in 1 ms, same mark
|
|
164
|
+
lm.pi(10**9) # 50847534 in 0.19 s, three marks
|
|
165
|
+
lm.seed(rp.Marks.load()) # start from a built table
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## The window: a sieve of Eratosthenes that starts anywhere
|
|
169
|
+
|
|
170
|
+
Every candidate is `anchor + d`, so its residue mod a seed q is `(anchor mod q) + d`.
|
|
171
|
+
One division per seed finds where the multiples of q fall in the window; after
|
|
172
|
+
that it is strike-outs, no arithmetic per candidate.
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
rp.window(10**12, 1_000_000) # survivor offsets, closest first; 2 ms
|
|
176
|
+
rp.next_prime_after(10**12) # 1000000000039 in about 100 us
|
|
177
|
+
rp.next_prime_after(2**60) # 1152921504606847009
|
|
178
|
+
rp.primes_between(10**15, 10**15 + 10**7) # every prime there, certified, 0.5 s
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Any size. The first window is the largest known prime gap plus slack below
|
|
182
|
+
2^64 (the next prime is always inside) and 7 ln(anchor) above (one miss in a
|
|
183
|
+
thousand, then it slides). Seed depth follows the cost of a test: the 31 small
|
|
184
|
+
seeds through 512 bits, 8191 through 1536, up to a million above, where a
|
|
185
|
+
single certification costs 100 ms and the deeper sieve still pays. A base-2
|
|
186
|
+
probable-prime test runs before the full one.
|
|
187
|
+
|
|
188
|
+
The survivors are independent, so the parallel paths certify them on every
|
|
189
|
+
core. `next_prime_after_parallel` returns the smallest that passes, same
|
|
190
|
+
answers as gmpy2's `next_prime`. `any_prime_after` returns the first pass from
|
|
191
|
+
any core: a prime above the anchor, not necessarily the next one, with no stop
|
|
192
|
+
rule to wait on. For "a random prime of this size" that is the fast path.
|
|
193
|
+
The worker pool stays alive between calls (`rp.close_pools()` to release it).
|
|
194
|
+
|
|
195
|
+
| anchor | gmpy2.next_prime | any_prime_after | next_prime_after_parallel |
|
|
196
|
+
|---|---|---|---|
|
|
197
|
+
| 2^2048 | 0.15 s | 0.07 s | 0.08 s |
|
|
198
|
+
| 2^4096 | 1.29 s | 0.43 s | 0.68 s |
|
|
199
|
+
| 2^8191 | 7.84 s | 2.91 s | 3.31 s |
|
|
200
|
+
| 2^12191 | 61.6 s | 14.7 s | 21.2 s |
|
|
201
|
+
|
|
202
|
+
(16 cores, warm pool, every result a verified prime.) The ordering of the
|
|
203
|
+
survivors is the one knob that cannot be improved: the next prime needs every
|
|
204
|
+
survivor below it cleared, and offset order tests exactly those. We measured
|
|
205
|
+
residue statistics, bit patterns, Jacobi symbols and offset keys as sort keys
|
|
206
|
+
against a random key; the only things that sort are the offset itself (the
|
|
207
|
+
gap distribution) and a modular power of the candidate. `ranked_window` shows
|
|
208
|
+
that order; `order="squeeze"` (both ends toward the centre) and `key=` are
|
|
209
|
+
there to try your own. `gap_profile` accounts for a range: gap histogram,
|
|
210
|
+
twin share against 1.32/ln x, mean against ln x.
|
|
211
|
+
|
|
212
|
+
`window` strikes with the 31 seeds up to 131 and leaves primes plus a few
|
|
213
|
+
composites for `isprime` to settle. `primes_between` brings every prime up to
|
|
214
|
+
the square root of the top, so what survives is prime and nothing is tested.
|
|
215
|
+
Counts agree with primecount at 10^12, 10^15 and 10^18.
|
|
216
|
+
|
|
217
|
+
| primes in | count | Python | Rust |
|
|
218
|
+
|---|---|---|---|
|
|
219
|
+
| [10^12, +10^7) | 361,726 | 0.12 s | 38 ms |
|
|
220
|
+
| [10^15, +10^7) | 289,394 | 0.52 s | 161 ms |
|
|
221
|
+
| [10^18, +10^6) | 24,280 | 7.7 s | 3.7 s |
|
|
222
|
+
|
|
223
|
+
(The 10^18 case is mostly building the 50 million seeds up to 10^9.)
|
|
224
|
+
|
|
225
|
+
```
|
|
226
|
+
rulerpack --between 1000000000000 1000000000100
|
|
227
|
+
rulerpack --next 1000000000000
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Hints: the next prime from its low bits, no test
|
|
231
|
+
|
|
232
|
+
A number between two consecutive primes is less than one gap from the next
|
|
233
|
+
prime, so it cannot share that prime's low `b` bits once 2^b exceeds the gap.
|
|
234
|
+
`hinted_next(anchor, hint, bits)` is therefore one modulo and one add, and it
|
|
235
|
+
is right whenever the gap is under 2^bits: proven for 11 bits below 2^64
|
|
236
|
+
(largest gap 1,550), heuristic above (13 bits to about 1e39, 17 bits to about
|
|
237
|
+
2^256 with headroom). Choose the bits.
|
|
238
|
+
|
|
239
|
+
```python
|
|
240
|
+
p = rp.next_prime_after(2**256) # certified once: 2^256 + 297
|
|
241
|
+
rp.hinted_next(2**256, rp.hint_of(p, 17)) # p again, about 1 us, no test
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
The hint can live inside the pack. `hinted_family(bits)` uses L = 2^(bits+1),
|
|
245
|
+
so the bottom bits of X are the bottom bits of 2p, untouched by the index, and
|
|
246
|
+
`K ^ X = L(i+c)` still opens the pair. A bare stream of X values then walks
|
|
247
|
+
the primes from any start, no K, no index, no count, no test:
|
|
248
|
+
|
|
249
|
+
```python
|
|
250
|
+
fam = rp.hinted_family(17, shift=300)
|
|
251
|
+
K, X = fam.encode_array(rp.primes_first(1_000_000))
|
|
252
|
+
rp.primes_from_x(2, X[1:], 17) # every prime back, 0.5 s, zero tests
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
**Seventeen bits per prime is the whole thing.** Strip X down to the hints and
|
|
256
|
+
throw the rest away; the walk still runs, and so does the comparison Lars ran
|
|
257
|
+
in the session:
|
|
258
|
+
|
|
259
|
+
```python
|
|
260
|
+
hints = (X >> 1) & ((1 << 17) - 1) # rp.hints_from_x(X, 17)
|
|
261
|
+
|
|
262
|
+
p = 2
|
|
263
|
+
out = []
|
|
264
|
+
for h in hints[1:31]:
|
|
265
|
+
p = rp.hinted_next(p, int(h), 17)
|
|
266
|
+
out.append(p)
|
|
267
|
+
|
|
268
|
+
out == rp.primes_from_x(2, X[1:31], 17) # True
|
|
269
|
+
rp.hint_walk(2, hints[1:], 17) # the same walk, as one call
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
So one encoding reads two ways: K and X together open any pair directly, by
|
|
273
|
+
index or by prime, and the low bits of X alone rebuild the whole sequence in
|
|
274
|
+
order. The walk is not about primes in particular; any increasing integers
|
|
275
|
+
rebuild from their low bits as long as each step stays under the hint modulus,
|
|
276
|
+
and the prime gaps do so to about 2^256 at 17 bits.
|
|
277
|
+
|
|
278
|
+
## Indexed primes above an anchor
|
|
279
|
+
|
|
280
|
+
Count once at the anchor, then every prime found above it takes the next index
|
|
281
|
+
and packs. With primecount installed the count is milliseconds at any size.
|
|
282
|
+
|
|
283
|
+
Baseline, stated exactly. The only route in Kim Walisch's tools that returns an
|
|
284
|
+
index is primecount's `nth_prime`, called once per prime; against that this is
|
|
285
|
+
thousands of times faster (10,000 indexed primes above 10^12: 39 ms here, 190 s
|
|
286
|
+
there). His list mode, primesieve, sieves a range without indices and is faster
|
|
287
|
+
than this at listing: a million primes above 10^14 in 0.04 s against 1.1 s for
|
|
288
|
+
`primes_between` in Python and 0.22 s for the Rust `between`. A reader with both
|
|
289
|
+
tools gets an indexed list in about 0.3 s. What they do not get is the pack,
|
|
290
|
+
the marks, or random access by index; that is what this library is for.
|
|
291
|
+
The Rust crate's `indexed_after` runs the same race in compiled code: given the
|
|
292
|
+
count, primesieve lists a million primes above 10^14 in 49 ms and the Rust
|
|
293
|
+
sieve takes 597 ms while also attaching indices and packs.
|
|
294
|
+
|
|
295
|
+
```python
|
|
296
|
+
rp.indexed_after(10**12, 3) # [(37607912018, 1000000000039, N), ...]
|
|
297
|
+
rp.indexed_after(2**44, 3) # 53 ms with primecount installed
|
|
298
|
+
rp.indexed_after(2**60, 3) # 20 s, all of it primecount counting to 1.15e18
|
|
299
|
+
rp.primepi_fastest(x) # primecount if present, else Meissel
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
## Which to use
|
|
303
|
+
|
|
304
|
+
| tool | holds | cost per call | reaches |
|
|
305
|
+
|---|---|---|---|
|
|
306
|
+
| `primepi` (Meissel) | nothing | grows with x, 0.07 s at 10^9 | about 10^12 here |
|
|
307
|
+
| `Marks` | a table built once | flat, the walk length | the end of the table |
|
|
308
|
+
| `LiveMarks` | only blocks you have touched | first touch counts, after that the walk | wherever the counter reaches |
|
|
309
|
+
| [primecount](https://github.com/kimwalisch/primecount) | nothing | 20 ms at 10^12, one thread | 10^20 and beyond |
|
|
310
|
+
| `any_prime_after` | a warm pool | 2.9 s at 8191 bits, 16 cores | any size; a prime, not the next |
|
|
311
|
+
| `next_prime_after_parallel` | a warm pool | 3.3 s at 8191 bits | any size; the next prime |
|
|
312
|
+
|
|
313
|
+
primecount computes; marks remember. Use the calculator for a one-off count of
|
|
314
|
+
any size, the ruler for many lookups in a range you have drawn.
|
|
315
|
+
|
|
316
|
+
## Also
|
|
317
|
+
|
|
318
|
+
- `rp.isprime(n)`: gmpy2's Miller-Rabin when gmpy2 is present, else a pure
|
|
319
|
+
Python deterministic one exact below 3.3·10^24.
|
|
320
|
+
- `rp.sweep(P, step, prime_test)`: the window sweep. With a residue hint of
|
|
321
|
+
p mod 256 the seed rule finds the next prime with no primality test for the
|
|
322
|
+
first 831,000 primes.
|
|
323
|
+
- `rp.li`, `rp.li_inverse`, `rp.riemann_r`, `rp.riemann_r_inverse`.
|
|
324
|
+
- `rp.key(n)`, `rp.topo(n)`, `rp.build_kx(n)`: the original session arrays.
|
|
325
|
+
- `rp.wheel_pass(a, count=100, seeds=None)`: the seed wheel run over `a, a+1, ...`; each
|
|
326
|
+
offset's first divisor among `[2, *seeds]`, or `None` when it survives. `seeds` defaults
|
|
327
|
+
to `SEEDS`; `a` is any int, so `rp.wheel_report(2**8191 - 1)` prints which of the hundred
|
|
328
|
+
numbers past the Mersenne prime the wheel removes and by what (twelve survive).
|
|
329
|
+
|
|
330
|
+
## Experiments
|
|
331
|
+
|
|
332
|
+
`rulerpack.experiments` is the drawer for things that were fun in a session and might be
|
|
333
|
+
more. Nothing else imports it. Each function reproduces an IPython moment exactly and says
|
|
334
|
+
what it saw.
|
|
335
|
+
|
|
336
|
+
```python
|
|
337
|
+
import rulerpack.experiments as rx
|
|
338
|
+
rx.stripe(primes) # (idx ^ 2p) % 4 with idx = arange(2, n, 10): 2, 0, 2, 0, ...
|
|
339
|
+
rx.jumps(primes) # 97, 172, 309, 564 among them: 2p crossing 1024, 2048, 4096, 8192
|
|
340
|
+
rx.last_before_blocks(primes) # 127, 251, 383, 509, ... 1021, 2039, 4093, 8191 among them
|
|
341
|
+
rx.layers(primes) # z % 4, 8, 16, 32: one more bit of the primes per row, the stripe under all of them
|
|
342
|
+
rx.recover(primes, 32) # ((z % 32) ^ (idx % 32)) >> 1 == p % 16: XOR hides nothing
|
|
343
|
+
rx.stripe_control() # odd composites stripe the same way: the stripe is not about primes
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
## Install
|
|
347
|
+
|
|
348
|
+
```
|
|
349
|
+
pip install -e . # numpy only; gmpy2 optional (pip install -e ".[fast]")
|
|
350
|
+
pytest
|
|
351
|
+
rulerpack --check 1000000
|
|
352
|
+
rulerpack 78501 1000039
|
|
353
|
+
rulerpack --ladder 32
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
MIT license.
|
|
357
|
+
|
|
358
|
+
## Changes
|
|
359
|
+
|
|
360
|
+
**0.2.1** (2026-11-01)
|
|
361
|
+
|
|
362
|
+
- `rulerpack.experiments`: the drawer, with the step-10 index, its layers, the recovery and the
|
|
363
|
+
odd-composite control. Ported to the Rust crate's `experiments` module in the same release.
|
|
364
|
+
|
|
365
|
+
**0.2.0** (2026-11-01)
|
|
366
|
+
|
|
367
|
+
- `wheel_pass`, `wheel_report`: the seed wheel over any anchor, `seeds` as a parameter with
|
|
368
|
+
`SEEDS` the default; `2**8191 - 1` works as an anchor.
|
|
369
|
+
- `hints_from_x`, `hint_walk`: the primes back in order from seventeen bits each, no test.
|
|
370
|
+
- `R896`, `carry`, `slide512`, `unslide512`, `slide_repeats`: the 512 slide and where it repeats.
|
|
371
|
+
|
|
372
|
+
**0.1.0** — the rulers, the pack, counting, marks, the window, hints, indexed primes.
|