atlas_embeddings/arithmetic/
mod.rs

1//! Exact arithmetic for Atlas computations
2//!
3//! This module provides exact rational arithmetic with NO floating point.
4//! All computations use exact integers and rationals to preserve mathematical
5//! structure.
6//!
7//! # Key Types
8//!
9//! - [`Rational`] - Exact rational numbers (alias for `Ratio<i64>`)
10//! - [`HalfInteger`] - Half-integers for E₈ coordinates (multiples of 1/2)
11//! - [`Vector8`] - 8-dimensional vectors with exact coordinates
12//! - [`RationalMatrix`] - Matrices with exact rational entries (for Weyl groups)
13//!
14//! # Design Principles
15//!
16//! 1. **No floating point** - All arithmetic is exact
17//! 2. **Fraction preservation** - Rationals never lose precision
18//! 3. **Overflow detection** - All operations check for overflow
19//! 4. **Type safety** - Prevent mixing incompatible number types
20
21mod matrix;
22
23pub use matrix::{RationalMatrix, RationalVector};
24
25use num_rational::Ratio;
26use num_traits::Zero;
27use std::fmt;
28use std::ops::{Add, Mul, Neg, Sub};
29
30/// Exact rational number (fraction)
31///
32/// This is an alias for `Ratio<i64>` from the `num_rational` crate.
33/// All arithmetic operations are exact with no loss of precision.
34///
35/// # Examples
36///
37/// ```
38/// use atlas_embeddings::arithmetic::Rational;
39///
40/// let a = Rational::new(1, 2);  // 1/2
41/// let b = Rational::new(1, 3);  // 1/3
42/// let sum = a + b;              // 5/6 (exact)
43///
44/// assert_eq!(sum, Rational::new(5, 6));
45/// ```
46pub type Rational = Ratio<i64>;
47
48/// Half-integer: numbers of the form n/2 where n ∈ ℤ
49///
50/// E₈ root coordinates are half-integers (elements of ℤ ∪ ½ℤ).
51/// This type represents them exactly as `numerator / 2`.
52///
53/// # Invariant
54///
55/// The denominator is always 2 (enforced by construction).
56///
57/// # Examples
58///
59/// ```
60/// use atlas_embeddings::arithmetic::HalfInteger;
61///
62/// let x = HalfInteger::new(1);  // 1/2
63/// let y = HalfInteger::new(3);  // 3/2
64/// let sum = x + y;               // 2 = 4/2
65///
66/// assert_eq!(sum.numerator(), 4);
67/// ```
68#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
69pub struct HalfInteger {
70    numerator: i64,
71}
72
73impl HalfInteger {
74    /// Create a half-integer from a numerator (denominator is always 2)
75    ///
76    /// # Examples
77    ///
78    /// ```
79    /// use atlas_embeddings::arithmetic::HalfInteger;
80    ///
81    /// let half = HalfInteger::new(1);    // 1/2
82    /// let one = HalfInteger::new(2);     // 2/2 = 1
83    /// let neg_half = HalfInteger::new(-1); // -1/2
84    /// ```
85    #[must_use]
86    pub const fn new(numerator: i64) -> Self {
87        Self { numerator }
88    }
89
90    /// Create from an integer (multiply by 2 internally)
91    ///
92    /// # Examples
93    ///
94    /// ```
95    /// use atlas_embeddings::arithmetic::HalfInteger;
96    ///
97    /// let two = HalfInteger::from_integer(1);  // 2/2 = 1
98    /// assert_eq!(two.numerator(), 2);
99    /// ```
100    #[must_use]
101    pub const fn from_integer(n: i64) -> Self {
102        Self { numerator: n * 2 }
103    }
104
105    /// Get the numerator (denominator is always 2)
106    #[must_use]
107    pub const fn numerator(self) -> i64 {
108        self.numerator
109    }
110
111    /// Convert to rational number
112    #[must_use]
113    pub fn to_rational(self) -> Rational {
114        Rational::new(self.numerator, 2)
115    }
116
117    /// Create from a rational number
118    ///
119    /// # Panics
120    ///
121    /// Panics if the rational cannot be represented as n/2
122    #[must_use]
123    pub fn from_rational(r: Rational) -> Self {
124        // Reduce the rational to lowest terms
125        let numer = *r.numer();
126        let denom = *r.denom();
127
128        // Check if denominator is 1 (integer) or 2 (half-integer)
129        match denom {
130            1 => Self::from_integer(numer),
131            2 => Self::new(numer),
132            _ => panic!("Rational {numer}/{denom} cannot be represented as half-integer"),
133        }
134    }
135
136    /// Square of this half-integer (exact)
137    #[must_use]
138    pub fn square(self) -> Rational {
139        Rational::new(self.numerator * self.numerator, 4)
140    }
141
142    /// Check if this is an integer (numerator is even)
143    #[must_use]
144    pub const fn is_integer(self) -> bool {
145        self.numerator % 2 == 0
146    }
147
148    /// Get as integer if possible
149    #[must_use]
150    pub const fn as_integer(self) -> Option<i64> {
151        if self.is_integer() {
152            Some(self.numerator / 2)
153        } else {
154            None
155        }
156    }
157}
158
159impl Zero for HalfInteger {
160    fn zero() -> Self {
161        Self::new(0)
162    }
163
164    fn is_zero(&self) -> bool {
165        self.numerator == 0
166    }
167}
168
169// Note: We cannot implement One for HalfInteger because
170// HalfInteger * HalfInteger = Rational (not HalfInteger)
171// This is mathematically correct: (a/2) * (b/2) = (ab)/4
172
173impl Add for HalfInteger {
174    type Output = Self;
175
176    fn add(self, other: Self) -> Self {
177        Self::new(self.numerator + other.numerator)
178    }
179}
180
181impl Sub for HalfInteger {
182    type Output = Self;
183
184    fn sub(self, other: Self) -> Self {
185        Self::new(self.numerator - other.numerator)
186    }
187}
188
189impl Mul for HalfInteger {
190    type Output = Rational;
191
192    fn mul(self, other: Self) -> Rational {
193        Rational::new(self.numerator * other.numerator, 4)
194    }
195}
196
197impl Neg for HalfInteger {
198    type Output = Self;
199
200    fn neg(self) -> Self {
201        Self::new(-self.numerator)
202    }
203}
204
205impl fmt::Display for HalfInteger {
206    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
207        if self.is_integer() {
208            write!(f, "{}", self.numerator / 2)
209        } else {
210            write!(f, "{}/2", self.numerator)
211        }
212    }
213}
214
215/// 8-dimensional vector with exact coordinates
216///
217/// Used for E₈ root system and Atlas coordinates.
218/// All coordinates are half-integers for exact arithmetic.
219///
220/// # Examples
221///
222/// ```
223/// use atlas_embeddings::arithmetic::{Vector8, HalfInteger};
224///
225/// let v = Vector8::new([
226///     HalfInteger::new(1),   // 1/2
227///     HalfInteger::new(1),   // 1/2
228///     HalfInteger::new(1),   // 1/2
229///     HalfInteger::new(1),   // 1/2
230///     HalfInteger::new(1),   // 1/2
231///     HalfInteger::new(1),   // 1/2
232///     HalfInteger::new(1),   // 1/2
233///     HalfInteger::new(1),   // 1/2
234/// ]);
235///
236/// // Norm squared: 8 * (1/2)² = 8 * 1/4 = 2
237/// assert_eq!(v.norm_squared(), num_rational::Ratio::new(2, 1));
238/// ```
239#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
240pub struct Vector8 {
241    coords: [HalfInteger; 8],
242}
243
244impl Vector8 {
245    /// Create a vector from 8 half-integer coordinates
246    #[must_use]
247    pub const fn new(coords: [HalfInteger; 8]) -> Self {
248        Self { coords }
249    }
250
251    /// Create a zero vector
252    #[must_use]
253    pub fn zero() -> Self {
254        Self::new([HalfInteger::zero(); 8])
255    }
256
257    /// Get coordinate at index
258    #[must_use]
259    pub const fn get(&self, index: usize) -> HalfInteger {
260        self.coords[index]
261    }
262
263    /// Get all coordinates as slice
264    #[must_use]
265    pub const fn coords(&self) -> &[HalfInteger; 8] {
266        &self.coords
267    }
268
269    /// Inner product (dot product) - exact rational result
270    ///
271    /// ⟨v, w⟩ = Σᵢ vᵢ·wᵢ
272    #[must_use]
273    pub fn inner_product(&self, other: &Self) -> Rational {
274        let mut sum = Rational::zero();
275        for i in 0..8 {
276            sum += self.coords[i] * other.coords[i];
277        }
278        sum
279    }
280
281    /// Norm squared: ‖v‖² = ⟨v, v⟩
282    #[must_use]
283    pub fn norm_squared(&self) -> Rational {
284        self.inner_product(self)
285    }
286
287    /// Check if this is a root (norm² = 2)
288    #[must_use]
289    pub fn is_root(&self) -> bool {
290        self.norm_squared() == Rational::new(2, 1)
291    }
292
293    /// Vector addition
294    #[must_use]
295    pub fn add(&self, other: &Self) -> Self {
296        let result: [HalfInteger; 8] = std::array::from_fn(|i| self.coords[i] + other.coords[i]);
297        Self::new(result)
298    }
299
300    /// Vector subtraction
301    #[must_use]
302    pub fn sub(&self, other: &Self) -> Self {
303        let result: [HalfInteger; 8] = std::array::from_fn(|i| self.coords[i] - other.coords[i]);
304        Self::new(result)
305    }
306
307    /// Scalar multiplication by integer
308    #[must_use]
309    pub fn scale(&self, scalar: i64) -> Self {
310        let result: [HalfInteger; 8] =
311            std::array::from_fn(|i| HalfInteger::new(self.coords[i].numerator() * scalar));
312        Self::new(result)
313    }
314
315    /// Scalar multiplication by rational
316    ///
317    /// Multiplies each coordinate by a rational scalar (exact arithmetic)
318    #[must_use]
319    pub fn scale_rational(&self, scalar: Rational) -> Self {
320        let result: [HalfInteger; 8] = std::array::from_fn(|i| {
321            let coord_rational = self.coords[i].to_rational();
322            let product = coord_rational * scalar;
323            HalfInteger::from_rational(product)
324        });
325        Self::new(result)
326    }
327
328    /// Negation
329    #[must_use]
330    pub fn negate(&self) -> Self {
331        let result: [HalfInteger; 8] = std::array::from_fn(|i| -self.coords[i]);
332        Self::new(result)
333    }
334}
335
336impl Add for Vector8 {
337    type Output = Self;
338
339    fn add(self, other: Self) -> Self {
340        Self::add(&self, &other)
341    }
342}
343
344impl Sub for Vector8 {
345    type Output = Self;
346
347    fn sub(self, other: Self) -> Self {
348        Self::sub(&self, &other)
349    }
350}
351
352impl Neg for Vector8 {
353    type Output = Self;
354
355    fn neg(self) -> Self {
356        self.negate()
357    }
358}
359
360impl fmt::Display for Vector8 {
361    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
362        write!(f, "(")?;
363        for (i, coord) in self.coords.iter().enumerate() {
364            if i > 0 {
365                write!(f, ", ")?;
366            }
367            write!(f, "{coord}")?;
368        }
369        write!(f, ")")
370    }
371}
372
373#[cfg(test)]
374mod tests {
375    use super::*;
376
377    #[test]
378    fn test_half_integer_creation() {
379        let half = HalfInteger::new(1);
380        assert_eq!(half.numerator(), 1);
381        assert_eq!(half.to_rational(), Rational::new(1, 2));
382
383        let one = HalfInteger::from_integer(1);
384        assert_eq!(one.numerator(), 2);
385        assert!(one.is_integer());
386    }
387
388    #[test]
389    fn test_half_integer_arithmetic() {
390        let a = HalfInteger::new(1); // 1/2
391        let b = HalfInteger::new(3); // 3/2
392
393        let sum = a + b; // 2
394        assert_eq!(sum.numerator(), 4);
395        assert!(sum.is_integer());
396
397        let diff = b - a; // 1
398        assert_eq!(diff.numerator(), 2);
399
400        let prod = a * b; // 3/4
401        assert_eq!(prod, Rational::new(3, 4));
402    }
403
404    #[test]
405    fn test_half_integer_square() {
406        let half = HalfInteger::new(1); // 1/2
407        assert_eq!(half.square(), Rational::new(1, 4));
408
409        let one = HalfInteger::from_integer(1); // 1
410        assert_eq!(one.square(), Rational::new(1, 1));
411    }
412
413    #[test]
414    fn test_vector8_creation() {
415        let v = Vector8::zero();
416        assert!(v.norm_squared().is_zero());
417
418        let ones = Vector8::new([HalfInteger::from_integer(1); 8]);
419        assert_eq!(ones.norm_squared(), Rational::new(8, 1));
420    }
421
422    #[test]
423    fn test_vector8_inner_product() {
424        let v = Vector8::new([HalfInteger::new(1); 8]); // All 1/2
425        let w = Vector8::new([HalfInteger::new(2); 8]); // All 1
426
427        // ⟨v, w⟩ = 8 * (1/2 * 1) = 8 * 1/2 = 4
428        assert_eq!(v.inner_product(&w), Rational::new(4, 1));
429    }
430
431    #[test]
432    fn test_vector8_norm_squared() {
433        let v = Vector8::new([HalfInteger::new(1); 8]); // All 1/2
434
435        // ‖v‖² = 8 * (1/2)² = 8 * 1/4 = 2
436        assert_eq!(v.norm_squared(), Rational::new(2, 1));
437        assert!(v.is_root());
438    }
439
440    #[test]
441    fn test_vector8_arithmetic() {
442        let v = Vector8::new([HalfInteger::new(1); 8]);
443        let w = Vector8::new([HalfInteger::new(1); 8]);
444
445        let sum = v + w;
446        assert_eq!(sum.coords[0].numerator(), 2);
447
448        let diff = v - w;
449        assert!(diff.norm_squared().is_zero());
450
451        let neg = -v;
452        assert_eq!(neg.coords[0].numerator(), -1);
453    }
454
455    #[test]
456    fn test_vector8_scaling() {
457        let v = Vector8::new([HalfInteger::from_integer(1); 8]);
458        let scaled = v.scale(3);
459
460        assert_eq!(scaled.coords[0].numerator(), 6);
461        assert_eq!(scaled.norm_squared(), Rational::new(72, 1));
462    }
463
464    #[test]
465    fn test_rational_exact_arithmetic() {
466        let a = Rational::new(1, 3);
467        let b = Rational::new(1, 6);
468
469        let sum = a + b;
470        assert_eq!(sum, Rational::new(1, 2));
471
472        let prod = a * b;
473        assert_eq!(prod, Rational::new(1, 18));
474    }
475}