proof_engine/number_theory/
primes.rs1use glam::{Vec2, Vec3, Vec4};
4
5pub fn sieve_of_eratosthenes(limit: u64) -> Vec<u64> {
7 if limit < 2 {
8 return Vec::new();
9 }
10 let n = limit as usize;
11 let mut is_prime = vec![true; n + 1];
12 is_prime[0] = false;
13 is_prime[1] = false;
14 let mut i = 2;
15 while i * i <= n {
16 if is_prime[i] {
17 let mut j = i * i;
18 while j <= n {
19 is_prime[j] = false;
20 j += i;
21 }
22 }
23 i += 1;
24 }
25 is_prime
26 .iter()
27 .enumerate()
28 .filter_map(|(idx, &p)| if p { Some(idx as u64) } else { None })
29 .collect()
30}
31
32const WITNESSES: [u64; 7] = [2, 3, 5, 7, 11, 13, 17];
34
35fn mod_pow_u128(mut base: u128, mut exp: u128, modulus: u128) -> u128 {
36 let mut result: u128 = 1;
37 base %= modulus;
38 while exp > 0 {
39 if exp & 1 == 1 {
40 result = result * base % modulus;
41 }
42 exp >>= 1;
43 base = base * base % modulus;
44 }
45 result
46}
47
48fn miller_rabin_test(n: u64, a: u64) -> bool {
49 if a % n == 0 {
50 return true;
51 }
52 let n128 = n as u128;
53 let mut d = n - 1;
54 let mut r = 0u32;
55 while d % 2 == 0 {
56 d /= 2;
57 r += 1;
58 }
59 let mut x = mod_pow_u128(a as u128, d as u128, n128);
60 if x == 1 || x == n128 - 1 {
61 return true;
62 }
63 for _ in 0..r - 1 {
64 x = x * x % n128;
65 if x == n128 - 1 {
66 return true;
67 }
68 }
69 false
70}
71
72pub fn is_prime(n: u64) -> bool {
74 if n < 2 {
75 return false;
76 }
77 if n < 4 {
78 return true;
79 }
80 if n % 2 == 0 || n % 3 == 0 {
81 return false;
82 }
83 if n < 25 {
84 return true;
85 }
86 let mut i = 5u64;
88 while i * i <= n && i < 1000 {
89 if n % i == 0 || n % (i + 2) == 0 {
90 return false;
91 }
92 i += 6;
93 }
94 if i * i > n {
95 return true;
96 }
97 for &a in &WITNESSES {
99 if a >= n {
100 continue;
101 }
102 if !miller_rabin_test(n, a) {
103 return false;
104 }
105 }
106 true
107}
108
109pub fn nth_prime(n: usize) -> u64 {
111 if n == 0 {
112 return 0;
113 }
114 if n == 1 {
115 return 2;
116 }
117 let mut count = 1usize;
118 let mut candidate = 3u64;
119 loop {
120 if is_prime(candidate) {
121 count += 1;
122 if count == n {
123 return candidate;
124 }
125 }
126 candidate += 2;
127 }
128}
129
130pub fn prime_counting(x: u64) -> usize {
132 if x < 2 {
133 return 0;
134 }
135 sieve_of_eratosthenes(x).len()
136}
137
138pub fn prime_gaps(limit: u64) -> Vec<u64> {
140 let primes = sieve_of_eratosthenes(limit);
141 primes.windows(2).map(|w| w[1] - w[0]).collect()
142}
143
144pub fn twin_primes(limit: u64) -> Vec<(u64, u64)> {
146 let primes = sieve_of_eratosthenes(limit);
147 primes
148 .windows(2)
149 .filter_map(|w| {
150 if w[1] - w[0] == 2 {
151 Some((w[0], w[1]))
152 } else {
153 None
154 }
155 })
156 .collect()
157}
158
159pub fn prime_factorization(n: u64) -> Vec<(u64, u32)> {
161 if n <= 1 {
162 return Vec::new();
163 }
164 let mut factors = Vec::new();
165 let mut remaining = n;
166
167 let mut count = 0u32;
168 while remaining % 2 == 0 {
169 remaining /= 2;
170 count += 1;
171 }
172 if count > 0 {
173 factors.push((2u64, count));
174 }
175
176 let mut d = 3u64;
177 while d * d <= remaining {
178 let mut count = 0u32;
179 while remaining % d == 0 {
180 remaining /= d;
181 count += 1;
182 }
183 if count > 0 {
184 factors.push((d, count));
185 }
186 d += 2;
187 }
188 if remaining > 1 {
189 factors.push((remaining, 1));
190 }
191 factors
192}
193
194pub struct UlamSpiral {
197 pub size: usize,
198}
199
200impl UlamSpiral {
201 pub fn new(size: usize) -> Self {
202 Self { size }
203 }
204
205 pub fn position(n: u64) -> Vec2 {
207 if n == 1 {
208 return Vec2::ZERO;
209 }
210 let k = ((((n as f64).sqrt() - 1.0) / 2.0).ceil()) as i64;
212 let side_len = 2 * k;
213 let start = (2 * k - 1) * (2 * k - 1) + 1;
214 let offset = (n as i64) - start;
215
216 let (x, y) = if offset < side_len {
217 (k, -k + 1 + offset)
219 } else if offset < 2 * side_len {
220 (k - 1 - (offset - side_len), k)
222 } else if offset < 3 * side_len {
223 (-k, k - 1 - (offset - 2 * side_len))
225 } else {
226 (-k + 1 + (offset - 3 * side_len), -k)
228 };
229 Vec2::new(x as f32, y as f32)
230 }
231
232 pub fn generate(&self) -> Vec<(Vec2, bool)> {
234 let total = (self.size * self.size) as u64;
235 let primes_set: std::collections::HashSet<u64> =
236 sieve_of_eratosthenes(total).into_iter().collect();
237 (1..=total)
238 .map(|n| (Self::position(n), primes_set.contains(&n)))
239 .collect()
240 }
241}
242
243pub struct SacksSpiral;
245
246impl SacksSpiral {
247 pub fn position(n: u64) -> Vec2 {
249 let r = (n as f64).sqrt();
250 let theta = r * std::f64::consts::TAU;
251 Vec2::new((r * theta.cos()) as f32, (r * theta.sin()) as f32)
252 }
253
254 pub fn generate(limit: u64) -> Vec<(Vec2, bool)> {
256 let primes_set: std::collections::HashSet<u64> =
257 sieve_of_eratosthenes(limit).into_iter().collect();
258 (1..=limit)
259 .map(|n| (SacksSpiral::position(n), primes_set.contains(&n)))
260 .collect()
261 }
262}
263
264pub struct PrimeDistributionRenderer {
266 pub origin: Vec3,
267 pub scale: f32,
268}
269
270pub struct PrimeGlyph {
272 pub value: u64,
273 pub position: Vec3,
274 pub color: Vec4,
275 pub character: char,
276}
277
278impl PrimeDistributionRenderer {
279 pub fn new(origin: Vec3, scale: f32) -> Self {
280 Self { origin, scale }
281 }
282
283 pub fn ulam_glyphs(&self, size: usize) -> Vec<PrimeGlyph> {
285 let spiral = UlamSpiral::new(size);
286 let data = spiral.generate();
287 data.into_iter()
288 .enumerate()
289 .filter_map(|(i, (pos, is_p))| {
290 if !is_p {
291 return None;
292 }
293 let n = (i + 1) as u64;
294 let brightness = 1.0 - (pos.length() / (size as f32)).min(1.0);
295 Some(PrimeGlyph {
296 value: n,
297 position: self.origin
298 + Vec3::new(pos.x * self.scale, pos.y * self.scale, 0.0),
299 color: Vec4::new(brightness, 0.7, 1.0 - brightness, 1.0),
300 character: prime_char(n),
301 })
302 })
303 .collect()
304 }
305
306 pub fn sacks_glyphs(&self, limit: u64) -> Vec<PrimeGlyph> {
308 let data = SacksSpiral::generate(limit);
309 data.into_iter()
310 .enumerate()
311 .filter_map(|(i, (pos, is_p))| {
312 if !is_p {
313 return None;
314 }
315 let n = (i + 1) as u64;
316 let r = (n as f32).sqrt();
317 let hue = (r / (limit as f32).sqrt()).min(1.0);
318 Some(PrimeGlyph {
319 value: n,
320 position: self.origin
321 + Vec3::new(pos.x * self.scale, pos.y * self.scale, 0.0),
322 color: Vec4::new(hue, 1.0 - hue, 0.5, 1.0),
323 character: prime_char(n),
324 })
325 })
326 .collect()
327 }
328
329 pub fn linear_glyphs(&self, limit: u64) -> Vec<PrimeGlyph> {
331 let primes = sieve_of_eratosthenes(limit);
332 primes
333 .iter()
334 .enumerate()
335 .map(|(i, &p)| {
336 let t = i as f32 / primes.len().max(1) as f32;
337 PrimeGlyph {
338 value: p,
339 position: self.origin + Vec3::new(i as f32 * self.scale, 0.0, 0.0),
340 color: Vec4::new(t, 0.3, 1.0 - t, 1.0),
341 character: prime_char(p),
342 }
343 })
344 .collect()
345 }
346}
347
348fn prime_char(p: u64) -> char {
349 match p % 6 {
350 1 => '*',
351 5 => '+',
352 _ => '.',
353 }
354}
355
356#[cfg(test)]
359mod tests {
360 use super::*;
361
362 #[test]
363 fn sieve_small() {
364 let primes = sieve_of_eratosthenes(30);
365 assert_eq!(primes, vec![2, 3, 5, 7, 11, 13, 17, 19, 23, 29]);
366 }
367
368 #[test]
369 fn sieve_edge() {
370 assert!(sieve_of_eratosthenes(0).is_empty());
371 assert!(sieve_of_eratosthenes(1).is_empty());
372 assert_eq!(sieve_of_eratosthenes(2), vec![2]);
373 }
374
375 #[test]
376 fn primality() {
377 assert!(!is_prime(0));
378 assert!(!is_prime(1));
379 assert!(is_prime(2));
380 assert!(is_prime(3));
381 assert!(!is_prime(4));
382 assert!(is_prime(7919));
383 assert!(!is_prime(7917));
384 assert!(is_prime(104729));
386 assert!(!is_prime(104730));
387 }
388
389 #[test]
390 fn nth_prime_test() {
391 assert_eq!(nth_prime(1), 2);
392 assert_eq!(nth_prime(2), 3);
393 assert_eq!(nth_prime(5), 11);
394 assert_eq!(nth_prime(10), 29);
395 assert_eq!(nth_prime(100), 541);
396 }
397
398 #[test]
399 fn counting() {
400 assert_eq!(prime_counting(10), 4);
401 assert_eq!(prime_counting(100), 25);
402 assert_eq!(prime_counting(1000), 168);
403 }
404
405 #[test]
406 fn gaps() {
407 let g = prime_gaps(20);
408 assert_eq!(g, vec![1, 2, 2, 4, 2, 4, 2]);
410 }
411
412 #[test]
413 fn twins() {
414 let t = twin_primes(50);
415 assert!(t.contains(&(3, 5)));
416 assert!(t.contains(&(5, 7)));
417 assert!(t.contains(&(11, 13)));
418 assert!(t.contains(&(29, 31)));
419 assert!(t.contains(&(41, 43)));
420 }
421
422 #[test]
423 fn factorization() {
424 assert_eq!(prime_factorization(1), vec![]);
425 assert_eq!(prime_factorization(2), vec![(2, 1)]);
426 assert_eq!(prime_factorization(12), vec![(2, 2), (3, 1)]);
427 assert_eq!(prime_factorization(360), vec![(2, 3), (3, 2), (5, 1)]);
428 assert_eq!(
429 prime_factorization(2 * 3 * 5 * 7 * 11),
430 vec![(2, 1), (3, 1), (5, 1), (7, 1), (11, 1)]
431 );
432 }
433
434 #[test]
435 fn ulam_center() {
436 let p = UlamSpiral::position(1);
437 assert_eq!(p, Vec2::ZERO);
438 }
439
440 #[test]
441 fn ulam_generate() {
442 let spiral = UlamSpiral::new(5);
443 let data = spiral.generate();
444 assert_eq!(data.len(), 25);
445 assert!(data[1].1);
447 assert!(!data[3].1);
449 }
450
451 #[test]
452 fn sacks_positions() {
453 let p1 = SacksSpiral::position(1);
454 assert!((p1.x - 1.0).abs() < 0.01);
456 assert!(p1.y.abs() < 0.01);
457 }
458
459 #[test]
460 fn renderer_produces_glyphs() {
461 let r = PrimeDistributionRenderer::new(Vec3::ZERO, 1.0);
462 let glyphs = r.ulam_glyphs(5);
463 assert!(!glyphs.is_empty());
464 for g in &glyphs {
466 assert!(is_prime(g.value));
467 }
468 }
469
470 #[test]
471 fn renderer_sacks() {
472 let r = PrimeDistributionRenderer::new(Vec3::ZERO, 0.5);
473 let glyphs = r.sacks_glyphs(100);
474 assert!(!glyphs.is_empty());
475 for g in &glyphs {
476 assert!(is_prime(g.value));
477 }
478 }
479
480 #[test]
481 fn renderer_linear() {
482 let r = PrimeDistributionRenderer::new(Vec3::ZERO, 1.0);
483 let glyphs = r.linear_glyphs(50);
484 assert_eq!(glyphs.len(), prime_counting(50));
485 }
486
487 #[test]
488 fn miller_rabin_large() {
489 assert!(is_prime(999999999989));
491 assert!(!is_prime(999999999990));
492 }
493}