Skip to main content

proof_engine/number_theory/
primes.rs

1//! Prime number distribution, sieves, and rendering utilities.
2
3use glam::{Vec2, Vec3, Vec4};
4
5/// Classic sieve of Eratosthenes returning all primes up to `limit`.
6pub 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
32/// Deterministic Miller-Rabin witnesses sufficient for all u64.
33const 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
72/// Primality test. Uses trial division for small n, Miller-Rabin for large n.
73pub 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    // Small trial division
87    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    // Miller-Rabin
98    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
109/// Returns the n-th prime (1-indexed: nth_prime(1) == 2).
110pub 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
130/// Prime counting function pi(x): number of primes <= x.
131pub fn prime_counting(x: u64) -> usize {
132    if x < 2 {
133        return 0;
134    }
135    sieve_of_eratosthenes(x).len()
136}
137
138/// Gaps between consecutive primes up to `limit`.
139pub 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
144/// Twin prime pairs (p, p+2) up to `limit`.
145pub 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
159/// Prime factorization of n, returned as sorted (prime, exponent) pairs.
160pub 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
194/// Ulam spiral: maps positive integers to a 2D grid position via a spiral walk,
195/// highlighting prime positions.
196pub struct UlamSpiral {
197    pub size: usize,
198}
199
200impl UlamSpiral {
201    pub fn new(size: usize) -> Self {
202        Self { size }
203    }
204
205    /// Map integer n (starting at 1 in center) to grid (x, y).
206    pub fn position(n: u64) -> Vec2 {
207        if n == 1 {
208            return Vec2::ZERO;
209        }
210        // Layer k: numbers from (2k-1)^2+1 to (2k+1)^2
211        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            // right side going up
218            (k, -k + 1 + offset)
219        } else if offset < 2 * side_len {
220            // top going left
221            (k - 1 - (offset - side_len), k)
222        } else if offset < 3 * side_len {
223            // left going down
224            (-k, k - 1 - (offset - 2 * side_len))
225        } else {
226            // bottom going right
227            (-k + 1 + (offset - 3 * side_len), -k)
228        };
229        Vec2::new(x as f32, y as f32)
230    }
231
232    /// Generate all spiral positions up to size*size, returning (position, is_prime).
233    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
243/// Sacks spiral: polar plot where integer n is at angle sqrt(n) * 2*pi, radius sqrt(n).
244pub struct SacksSpiral;
245
246impl SacksSpiral {
247    /// Convert integer n to polar coordinates then to Vec2.
248    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    /// Generate points for integers 1..=limit, returning (position, is_prime).
255    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
264/// Maps primes to glyph positions, sizes, and colors for engine rendering.
265pub struct PrimeDistributionRenderer {
266    pub origin: Vec3,
267    pub scale: f32,
268}
269
270/// A renderable glyph descriptor for a prime.
271pub 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    /// Render primes on an Ulam spiral as glyphs.
284    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    /// Render primes on a Sacks spiral as glyphs.
307    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    /// Linear layout: primes along a horizontal line.
330    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// ─── Tests ──────────────────────────────────────────────────────────────────
357
358#[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        // Large prime
385        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        // primes: 2,3,5,7,11,13,17,19  gaps: 1,2,2,4,2,4,2
409        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        // 2 is prime
446        assert!(data[1].1);
447        // 4 is not
448        assert!(!data[3].1);
449    }
450
451    #[test]
452    fn sacks_positions() {
453        let p1 = SacksSpiral::position(1);
454        // radius = 1, angle = 2*pi => (1, 0) approximately
455        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        // All glyphs should be primes
465        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        // Known large primes
490        assert!(is_prime(999999999989));
491        assert!(!is_prime(999999999990));
492    }
493}