Skip to main content

proof_engine/number_theory/
gaussian.rs

1//! Gaussian integers Z[i]: arithmetic, primes, factorization, and lattice rendering.
2
3use glam::{Vec2, Vec3, Vec4};
4
5/// A Gaussian integer a + bi where a, b are in Z.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7pub struct GaussianInt {
8    pub re: i64,
9    pub im: i64,
10}
11
12impl GaussianInt {
13    pub const ZERO: Self = Self { re: 0, im: 0 };
14    pub const ONE: Self = Self { re: 1, im: 0 };
15    pub const I: Self = Self { re: 0, im: 1 };
16
17    pub fn new(re: i64, im: i64) -> Self {
18        Self { re, im }
19    }
20
21    /// Norm: N(a+bi) = a^2 + b^2.
22    pub fn norm(self) -> i64 {
23        self.re * self.re + self.im * self.im
24    }
25
26    /// Complex conjugate: conj(a+bi) = a - bi.
27    pub fn conj(self) -> Self {
28        Self { re: self.re, im: -self.im }
29    }
30
31    /// Whether this is the zero element.
32    pub fn is_zero(self) -> bool {
33        self.re == 0 && self.im == 0
34    }
35
36    /// Whether this is a unit (norm = 1): {1, -1, i, -i}.
37    pub fn is_unit(self) -> bool {
38        self.norm() == 1
39    }
40
41    /// Gaussian integer division (rounded): returns (quotient, remainder).
42    pub fn div_rem(self, other: Self) -> (Self, Self) {
43        if other.is_zero() {
44            panic!("division by zero in Gaussian integers");
45        }
46        // (a+bi)/(c+di) = (a+bi)(c-di) / (c^2+d^2)
47        let n = other.norm();
48        let num = self * other.conj();
49        // Round to nearest Gaussian integer
50        let qr = div_round(num.re, n);
51        let qi = div_round(num.im, n);
52        let q = GaussianInt::new(qr, qi);
53        let r = self - q * other;
54        (q, r)
55    }
56}
57
58fn div_round(a: i64, b: i64) -> i64 {
59    // Round to nearest integer (ties go to even or just use standard rounding)
60    let (d, r) = (a / b, a % b);
61    if 2 * r.abs() > b.abs() {
62        if (a > 0) == (b > 0) { d + 1 } else { d - 1 }
63    } else {
64        d
65    }
66}
67
68impl std::ops::Add for GaussianInt {
69    type Output = Self;
70    fn add(self, rhs: Self) -> Self {
71        Self { re: self.re + rhs.re, im: self.im + rhs.im }
72    }
73}
74
75impl std::ops::Sub for GaussianInt {
76    type Output = Self;
77    fn sub(self, rhs: Self) -> Self {
78        Self { re: self.re - rhs.re, im: self.im - rhs.im }
79    }
80}
81
82impl std::ops::Mul for GaussianInt {
83    type Output = Self;
84    fn mul(self, rhs: Self) -> Self {
85        Self {
86            re: self.re * rhs.re - self.im * rhs.im,
87            im: self.re * rhs.im + self.im * rhs.re,
88        }
89    }
90}
91
92impl std::ops::Neg for GaussianInt {
93    type Output = Self;
94    fn neg(self) -> Self {
95        Self { re: -self.re, im: -self.im }
96    }
97}
98
99impl std::fmt::Display for GaussianInt {
100    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101        if self.im == 0 {
102            write!(f, "{}", self.re)
103        } else if self.re == 0 {
104            if self.im == 1 {
105                write!(f, "i")
106            } else if self.im == -1 {
107                write!(f, "-i")
108            } else {
109                write!(f, "{}i", self.im)
110            }
111        } else {
112            let sign = if self.im > 0 { "+" } else { "-" };
113            let abs_im = self.im.abs();
114            if abs_im == 1 {
115                write!(f, "{}{}i", self.re, sign)
116            } else {
117                write!(f, "{}{}{}i", self.re, sign, abs_im)
118            }
119        }
120    }
121}
122
123/// GCD of two Gaussian integers via the Euclidean algorithm.
124pub fn gcd(mut a: GaussianInt, mut b: GaussianInt) -> GaussianInt {
125    while !b.is_zero() {
126        let (_, r) = a.div_rem(b);
127        a = b;
128        b = r;
129    }
130    // Normalize: prefer associate with positive real part
131    normalize(a)
132}
133
134/// Normalize a Gaussian integer to its canonical associate
135/// (multiply by unit so that the result is in the first quadrant or positive real axis).
136fn normalize(z: GaussianInt) -> GaussianInt {
137    if z.is_zero() {
138        return z;
139    }
140    // Multiply by i^k to get into "first quadrant": re > 0, im >= 0
141    // or re >= 0, im > 0
142    let units = [
143        GaussianInt::new(1, 0),
144        GaussianInt::new(0, 1),
145        GaussianInt::new(-1, 0),
146        GaussianInt::new(0, -1),
147    ];
148    for &u in &units {
149        let w = z * u;
150        if w.re > 0 && w.im >= 0 {
151            return w;
152        }
153    }
154    // fallback: just pick re >= 0
155    for &u in &units {
156        let w = z * u;
157        if w.re >= 0 && w.im >= 0 {
158            return w;
159        }
160    }
161    z
162}
163
164/// Check if a Gaussian integer is a Gaussian prime.
165pub fn is_gaussian_prime(z: GaussianInt) -> bool {
166    if z.is_zero() {
167        return false;
168    }
169    let n = z.norm();
170    if n <= 1 {
171        return false; // units are not primes
172    }
173    // z is a Gaussian prime iff:
174    // 1. z = a + 0i (or 0 + bi) and |a| (or |b|) is an ordinary prime p ≡ 3 (mod 4)
175    // 2. N(z) = a^2 + b^2 is an ordinary prime
176    super::primes::is_prime(n as u64)
177        || (z.im == 0 && {
178            let p = z.re.unsigned_abs();
179            super::primes::is_prime(p) && p % 4 == 3
180        })
181        || (z.re == 0 && {
182            let p = z.im.unsigned_abs();
183            super::primes::is_prime(p) && p % 4 == 3
184        })
185}
186
187/// Factorize a Gaussian integer into Gaussian primes.
188/// Returns a list of (not necessarily normalized) prime factors.
189pub fn factorize(z: GaussianInt) -> Vec<GaussianInt> {
190    if z.is_zero() || z.is_unit() {
191        return vec![];
192    }
193
194    let mut factors = Vec::new();
195    let mut remaining = z;
196
197    // Factor out units to make norm analysis cleaner
198    let n = remaining.norm();
199
200    // Factor the norm in the ordinary integers
201    let norm_factors = super::primes::prime_factorization(n as u64);
202
203    for (p, exp) in norm_factors {
204        let p_val = p as i64;
205        if p == 2 {
206            // 2 = -i * (1+i)^2, and (1+i) is a Gaussian prime
207            let pi = GaussianInt::new(1, 1);
208            for _ in 0..exp {
209                let (q, r) = remaining.div_rem(pi);
210                if r.is_zero() {
211                    factors.push(pi);
212                    remaining = q;
213                } else {
214                    break;
215                }
216            }
217        } else if p % 4 == 3 {
218            // p stays prime in Z[i]; it divides as p itself
219            let gp = GaussianInt::new(p_val, 0);
220            let pairs = exp / 2; // norm contribution is p^2 per factor of p in Z[i]
221            for _ in 0..pairs {
222                let (q, r) = remaining.div_rem(gp);
223                if r.is_zero() {
224                    factors.push(gp);
225                    remaining = q;
226                } else {
227                    break;
228                }
229            }
230        } else {
231            // p ≡ 1 (mod 4): splits as p = pi * conj(pi)
232            // Find a+bi such that a^2 + b^2 = p
233            if let Some((a, b)) = sum_of_two_squares(p) {
234                let pi1 = GaussianInt::new(a as i64, b as i64);
235                let pi2 = pi1.conj();
236                for pi in &[pi1, pi2] {
237                    loop {
238                        let (q, r) = remaining.div_rem(*pi);
239                        if r.is_zero() {
240                            factors.push(*pi);
241                            remaining = q;
242                        } else {
243                            break;
244                        }
245                    }
246                }
247            }
248        }
249    }
250
251    // remaining should be a unit; if not unit, include it
252    if !remaining.is_unit() && !remaining.is_zero() {
253        factors.push(remaining);
254    }
255
256    factors
257}
258
259/// Find a, b >= 0 such that a^2 + b^2 = p (for prime p ≡ 1 mod 4).
260fn sum_of_two_squares(p: u64) -> Option<(u64, u64)> {
261    if p == 2 {
262        return Some((1, 1));
263    }
264    if p % 4 != 1 {
265        return None;
266    }
267    // Find a square root of -1 mod p
268    let mut r = 2u64;
269    loop {
270        let candidate = super::modular::mod_pow(r, (p - 1) / 4, p);
271        if (candidate as u128 * candidate as u128) % p as u128 == p as u128 - 1 {
272            // Use Cornacchia / Fermat descent
273            return fermat_descent(p, candidate);
274        }
275        r += 1;
276        if r >= p {
277            return None;
278        }
279    }
280}
281
282fn fermat_descent(p: u64, mut a: u64) -> Option<(u64, u64)> {
283    let mut b = p;
284    let limit = (p as f64).sqrt() as u64;
285    while a > limit {
286        let t = b % a;
287        b = a;
288        a = t;
289    }
290    let remainder = p - a * a;
291    let b_sq = (remainder as f64).sqrt().round() as u64;
292    if b_sq * b_sq == remainder {
293        Some((a.max(b_sq), a.min(b_sq)))
294    } else {
295        None
296    }
297}
298
299// ─── Renderer ───────────────────────────────────────────────────────────────
300
301/// Render Gaussian integer lattice points with primes highlighted.
302pub struct GaussianLatticeRenderer {
303    pub range: i64,
304    pub origin: Vec3,
305    pub scale: f32,
306}
307
308pub struct LatticeGlyph {
309    pub z: GaussianInt,
310    pub position: Vec3,
311    pub color: Vec4,
312    pub character: char,
313    pub is_prime: bool,
314}
315
316impl GaussianLatticeRenderer {
317    pub fn new(range: i64, origin: Vec3, scale: f32) -> Self {
318        Self { range, origin, scale }
319    }
320
321    /// Generate lattice glyphs for all Gaussian integers with |re|, |im| <= range.
322    pub fn render(&self) -> Vec<LatticeGlyph> {
323        let mut glyphs = Vec::new();
324        for a in -self.range..=self.range {
325            for b in -self.range..=self.range {
326                let z = GaussianInt::new(a, b);
327                let prime = is_gaussian_prime(z);
328                let norm = z.norm() as f32;
329                let max_norm = (2 * self.range * self.range) as f32;
330                let t = (norm / max_norm).min(1.0);
331                let color = if prime {
332                    Vec4::new(1.0, 0.2, 0.2, 1.0)
333                } else if z.is_unit() {
334                    Vec4::new(1.0, 1.0, 0.0, 1.0)
335                } else {
336                    Vec4::new(0.3, 0.3 + t * 0.5, 0.8, 0.6)
337                };
338                let ch = if prime {
339                    '*'
340                } else if z.is_zero() {
341                    'O'
342                } else if z.is_unit() {
343                    'U'
344                } else {
345                    '.'
346                };
347                glyphs.push(LatticeGlyph {
348                    z,
349                    position: self.origin
350                        + Vec3::new(a as f32 * self.scale, b as f32 * self.scale, 0.0),
351                    color,
352                    character: ch,
353                    is_prime: prime,
354                });
355            }
356        }
357        glyphs
358    }
359}
360
361// ─── Tests ──────────────────────────────────────────────────────────────────
362
363#[cfg(test)]
364mod tests {
365    use super::*;
366
367    #[test]
368    fn basic_arithmetic() {
369        let a = GaussianInt::new(3, 4);
370        let b = GaussianInt::new(1, -2);
371        assert_eq!(a + b, GaussianInt::new(4, 2));
372        assert_eq!(a - b, GaussianInt::new(2, 6));
373        // (3+4i)(1-2i) = 3 -6i +4i -8i^2 = 11 - 2i
374        assert_eq!(a * b, GaussianInt::new(11, -2));
375    }
376
377    #[test]
378    fn norm_and_conj() {
379        let z = GaussianInt::new(3, 4);
380        assert_eq!(z.norm(), 25);
381        assert_eq!(z.conj(), GaussianInt::new(3, -4));
382        assert_eq!(z * z.conj(), GaussianInt::new(25, 0));
383    }
384
385    #[test]
386    fn units() {
387        assert!(GaussianInt::ONE.is_unit());
388        assert!(GaussianInt::I.is_unit());
389        assert!((-GaussianInt::ONE).is_unit());
390        assert!((-GaussianInt::I).is_unit());
391        assert!(!GaussianInt::new(1, 1).is_unit());
392    }
393
394    #[test]
395    fn div_rem_exact() {
396        let a = GaussianInt::new(11, -2);
397        let b = GaussianInt::new(1, -2);
398        let (q, r) = a.div_rem(b);
399        assert_eq!(a, q * b + r);
400        assert!(r.norm() <= b.norm()); // remainder has smaller norm (approximately)
401    }
402
403    #[test]
404    fn gcd_test() {
405        let a = GaussianInt::new(3, 4);
406        let b = GaussianInt::new(1, 2);
407        let g = gcd(a, b);
408        // gcd should divide both
409        let (_, r1) = a.div_rem(g);
410        let (_, r2) = b.div_rem(g);
411        assert!(r1.is_zero(), "gcd should divide a, remainder = {:?}", r1);
412        assert!(r2.is_zero(), "gcd should divide b, remainder = {:?}", r2);
413    }
414
415    #[test]
416    fn gaussian_primes() {
417        // 1+i has norm 2 which is prime => Gaussian prime
418        assert!(is_gaussian_prime(GaussianInt::new(1, 1)));
419        // 3 is ordinary prime, 3 ≡ 3 mod 4 => stays prime
420        assert!(is_gaussian_prime(GaussianInt::new(3, 0)));
421        // 5 = (2+i)(2-i), norm(2+i) = 5 which is prime => 2+i is Gaussian prime
422        assert!(is_gaussian_prime(GaussianInt::new(2, 1)));
423        // 5 itself: norm = 25, not prime => not a Gaussian prime
424        assert!(!is_gaussian_prime(GaussianInt::new(5, 0)));
425        // Units are not prime
426        assert!(!is_gaussian_prime(GaussianInt::ONE));
427        assert!(!is_gaussian_prime(GaussianInt::I));
428    }
429
430    #[test]
431    fn factorize_5() {
432        // 5 = (2+i)(2-i) up to units
433        let z = GaussianInt::new(5, 0);
434        let factors = factorize(z);
435        // Product of factors * some unit should equal z
436        let product = factors.iter().fold(GaussianInt::ONE, |acc, &f| acc * f);
437        assert_eq!(product.norm(), z.norm());
438        assert!(factors.len() >= 2);
439    }
440
441    #[test]
442    fn factorize_prime_3() {
443        // 3 ≡ 3 mod 4, stays prime in Z[i]
444        let z = GaussianInt::new(3, 0);
445        let factors = factorize(z);
446        assert_eq!(factors.len(), 1);
447        assert_eq!(factors[0].norm(), 9); // norm of 3 is 9
448    }
449
450    #[test]
451    fn display() {
452        assert_eq!(format!("{}", GaussianInt::new(3, 4)), "3+4i");
453        assert_eq!(format!("{}", GaussianInt::new(3, -4)), "3-4i");
454        assert_eq!(format!("{}", GaussianInt::new(0, 1)), "i");
455        assert_eq!(format!("{}", GaussianInt::new(5, 0)), "5");
456    }
457
458    #[test]
459    fn lattice_renderer() {
460        let r = GaussianLatticeRenderer::new(3, Vec3::ZERO, 1.0);
461        let glyphs = r.render();
462        assert_eq!(glyphs.len(), 7 * 7); // -3..=3 in both dims
463        let prime_count = glyphs.iter().filter(|g| g.is_prime).count();
464        assert!(prime_count > 0);
465    }
466}