Skip to main content

adele_ring/
adelic.rs

1//! The adelic carrier โ€” a value living at **both** kinds of place at once.
2//!
3//! ```text
4//! ๐”ธ_โ„š  =  โ„  ร—  โˆโ€ฒ_p โ„š_p
5//!         โ–ฒ            โ–ฒ
6//!    infinite place    finite places
7//! ```
8//!
9//! An [`Adelic`] pairs a **finite** component (pure RNS over a [`Basis`] โ€” the
10//! carry-free, embarrassingly parallel arithmetic) with an **infinite**
11//! component (a rigorous [`Ball`] โ€” the real interval that answers every
12//! Archimedean question: sign, comparison, magnitude, decimal output).
13//!
14//! - Exactness comes from the finite part (reconstructed at the boundary).
15//! - Sign / order / output come from the infinite part โ€” never from the
16//!   residues, which are constitutionally blind to size.
17//!
18//! Overflow is a *detected event*: when the finite reconstruction cannot be
19//! validated against the infinite ball, [`RangeError::ReconstructionFailed`] is
20//! returned and the caller extends the basis (see the headline test in the
21//! refactor plan ยง11).
22
23use std::cmp::Ordering;
24
25use num_bigint::{BigInt, Sign};
26use num_rational::BigRational;
27use num_traits::One;
28
29use crate::ball::Ball;
30use crate::basis::Basis;
31use crate::error::RangeError;
32use crate::primes::mod_inverse;
33use crate::reconstruct::rational_reconstruct;
34use crate::rns::RnsInt;
35
36/// The finite (โˆ โ„š_p) component of an adelic value: RNS arithmetic plus a
37/// reconstruction routine that may *detect* range overflow.
38pub trait Finite: Clone {
39    fn basis(&self) -> &Basis;
40    fn add(&self, o: &Self) -> Self;
41    fn sub(&self, o: &Self) -> Self;
42    fn mul(&self, o: &Self) -> Self;
43    /// Reconstruct the exact `(p, q)` (with `q == 1` for integers), or `None`
44    /// when the value cannot be recovered within the current basis range.
45    fn try_reconstruct(&self) -> Option<(BigInt, BigInt)>;
46    /// Re-encode a known exact `p/q` over a (possibly larger) basis.
47    fn reimage(p: &BigInt, q: &BigInt, basis: &Basis) -> Self;
48}
49
50// โ”€โ”€ Finite: integers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
51
52impl Finite for RnsInt {
53    fn basis(&self) -> &Basis {
54        &self.basis
55    }
56    fn add(&self, o: &Self) -> Self {
57        RnsInt::add(self, o)
58    }
59    fn sub(&self, o: &Self) -> Self {
60        RnsInt::sub(self, o)
61    }
62    fn mul(&self, o: &Self) -> Self {
63        RnsInt::mul(self, o)
64    }
65    fn try_reconstruct(&self) -> Option<(BigInt, BigInt)> {
66        // Balanced lift always "succeeds" numerically; the Adelic layer decides
67        // whether it is *correct* by validating against the infinite ball.
68        Some((self.to_bigint(), BigInt::one()))
69    }
70    fn reimage(p: &BigInt, q: &BigInt, basis: &Basis) -> Self {
71        debug_assert!(q.is_one(), "integer reimage with non-unit denominator");
72        RnsInt::from_bigint(p, basis.clone())
73    }
74}
75
76// โ”€โ”€ Finite: modular fractions (Level 1, single residue per channel) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
77
78/// A rational carried as one field element `pยทqโปยน (mod pแตข)` per channel.
79///
80/// A channel whose prime divides the denominator is **invalid** for this value
81/// (the fraction is not p-adically integral there); it is masked out of *this
82/// value's* reconstruction. This per-value mask is correctness bookkeeping for
83/// one number โ€” never the unsound global "skip channels to save power" idea.
84#[derive(Clone, Debug)]
85pub struct RnsFrac {
86    /// `None` marks a channel invalid for this value (prime divides denom).
87    pub residues: Vec<Option<u32>>,
88    pub basis: Basis,
89}
90
91impl RnsFrac {
92    pub fn from_fraction(p: &BigInt, q: &BigInt, basis: Basis) -> Self {
93        let residues = basis
94            .moduli()
95            .iter()
96            .map(|&m| {
97                let mm = BigInt::from(m);
98                let pm = (((p % &mm) + &mm) % &mm).to_u32();
99                let qm = (((q % &mm) + &mm) % &mm).to_u32();
100                match (pm, qm) {
101                    (Some(pr), Some(qr)) if qr != 0 => {
102                        let inv = mod_inverse(qr as u64, m as u64)? as u32;
103                        Some(((pr as u64 * inv as u64) % m as u64) as u32)
104                    }
105                    _ => None,
106                }
107            })
108            .collect();
109        RnsFrac { residues, basis }
110    }
111
112    fn combine(&self, o: &Self, f: impl Fn(u32, u32, u32) -> u32) -> Self {
113        let residues = self
114            .residues
115            .iter()
116            .zip(&o.residues)
117            .zip(self.basis.moduli())
118            .map(|((a, b), &m)| match (a, b) {
119                (Some(x), Some(y)) => Some(f(*x, *y, m)),
120                _ => None,
121            })
122            .collect();
123        RnsFrac { residues, basis: self.basis.clone() }
124    }
125}
126
127trait ToU32 {
128    fn to_u32(&self) -> Option<u32>;
129}
130impl ToU32 for BigInt {
131    fn to_u32(&self) -> Option<u32> {
132        use num_traits::ToPrimitive;
133        ToPrimitive::to_u32(self)
134    }
135}
136
137impl Finite for RnsFrac {
138    fn basis(&self) -> &Basis {
139        &self.basis
140    }
141    fn add(&self, o: &Self) -> Self {
142        self.combine(o, crate::rns::add_channel)
143    }
144    fn sub(&self, o: &Self) -> Self {
145        self.combine(o, crate::rns::sub_channel)
146    }
147    fn mul(&self, o: &Self) -> Self {
148        self.combine(o, crate::rns::mul_channel)
149    }
150    fn try_reconstruct(&self) -> Option<(BigInt, BigInt)> {
151        let mut res = Vec::new();
152        let mut mods = Vec::new();
153        for (r, &m) in self.residues.iter().zip(self.basis.moduli()) {
154            if let Some(v) = r {
155                res.push(*v);
156                mods.push(m);
157            }
158        }
159        if mods.is_empty() {
160            return None;
161        }
162        let u = crate::rns::garner_crt(&res, &mods);
163        let m_prod: num_bigint::BigUint = mods.iter().map(|&p| num_bigint::BigUint::from(p)).product();
164        rational_reconstruct(&u, &m_prod)
165    }
166    fn reimage(p: &BigInt, q: &BigInt, basis: &Basis) -> Self {
167        RnsFrac::from_fraction(p, q, basis.clone())
168    }
169}
170
171// โ”€โ”€ The carrier โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
172
173/// A value carried at the finite places (`F`) and the infinite place (`Ball`).
174#[derive(Clone)]
175pub struct Adelic<F: Finite> {
176    finite: F,
177    infinite: Ball,
178}
179
180impl<F: Finite> Adelic<F> {
181    /// Assemble from a finite component and its real enclosure.
182    pub fn from_parts(finite: F, infinite: Ball) -> Self {
183        Adelic { finite, infinite }
184    }
185
186    /// The infinite (real) component.
187    pub fn ball(&self) -> &Ball {
188        &self.infinite
189    }
190
191    /// The finite (RNS) component.
192    pub fn finite(&self) -> &F {
193        &self.finite
194    }
195
196    /// Sign, from the infinite place (the only place that can see it).
197    pub fn sign(&self) -> Option<Sign> {
198        self.infinite.sign()
199    }
200
201    /// Ordering, from the infinite place (`None` โ‡’ refine).
202    pub fn cmp(&self, other: &Self) -> Option<Ordering> {
203        self.infinite.cmp(&other.infinite)
204    }
205
206    /// Nearest `f64`.
207    pub fn to_f64(&self) -> f64 {
208        self.infinite.to_f64()
209    }
210
211    /// Addition (updates both places).
212    pub fn add(&self, o: &Self) -> Self {
213        Adelic { finite: self.finite.add(&o.finite), infinite: self.infinite.add(&o.infinite) }
214    }
215
216    /// Subtraction (updates both places).
217    pub fn sub(&self, o: &Self) -> Self {
218        Adelic { finite: self.finite.sub(&o.finite), infinite: self.infinite.sub(&o.infinite) }
219    }
220
221    /// Multiplication (updates both places).
222    pub fn mul(&self, o: &Self) -> Self {
223        Adelic { finite: self.finite.mul(&o.finite), infinite: self.infinite.mul(&o.infinite) }
224    }
225
226    /// The exact value, if the infinite place pins it to a point.
227    fn exact_point(&self) -> Option<(BigInt, BigInt)> {
228        if self.infinite.lo == self.infinite.hi {
229            Some((self.infinite.lo.numer().clone(), self.infinite.lo.denom().clone()))
230        } else {
231            None
232        }
233    }
234
235    /// Re-encode the finite part over a (possibly larger) basis. Requires the
236    /// infinite place to pin the exact value (true for all integer values and
237    /// for any width-0 ball).
238    pub fn with_basis(&self, basis: Basis) -> Self {
239        let (p, q) = self
240            .exact_point()
241            .expect("with_basis requires a point-valued infinite place");
242        Adelic { finite: F::reimage(&p, &q, &basis), infinite: self.infinite.clone() }
243    }
244
245    /// Validate a reconstructed `p/q` against the infinite ball.
246    fn validate(&self, p: &BigInt, q: &BigInt) -> Result<(BigInt, BigInt), RangeError> {
247        let candidate = BigRational::new(p.clone(), q.clone());
248        if self.infinite.contains(&candidate) {
249            Ok((p.clone(), q.clone()))
250        } else {
251            let have_bits = self.finite.basis().capacity_bits();
252            Err(RangeError::ReconstructionFailed { have_bits, need_more: true })
253        }
254    }
255}
256
257/// Integer instantiation.
258pub type AdelicInt = Adelic<RnsInt>;
259/// Rational instantiation.
260pub type AdelicRat = Adelic<RnsFrac>;
261
262impl AdelicInt {
263    /// From an exact `BigInt` over `basis` (the infinite place pins the value).
264    pub fn from_bigint(n: &BigInt, basis: Basis) -> Self {
265        Adelic {
266            finite: RnsInt::from_bigint(n, basis),
267            infinite: Ball::point(&BigRational::from_integer(n.clone())),
268        }
269    }
270
271    /// From a machine integer.
272    pub fn from_i64(n: i64, basis: Basis) -> Self {
273        Self::from_bigint(&BigInt::from(n), basis)
274    }
275
276    /// The exact integer, or [`RangeError::ReconstructionFailed`] if the finite
277    /// reconstruction disagrees with the infinite place (basis too small).
278    pub fn try_exact(&self) -> Result<BigInt, RangeError> {
279        let (p, q) = self
280            .finite
281            .try_reconstruct()
282            .ok_or(RangeError::ReconstructionFailed {
283                have_bits: self.finite.basis().capacity_bits(),
284                need_more: true,
285            })?;
286        self.validate(&p, &q).map(|(p, _)| p)
287    }
288}
289
290impl AdelicRat {
291    /// From an exact reduced `p/q` over `basis`.
292    pub fn from_fraction(p: &BigInt, q: &BigInt, basis: Basis) -> Self {
293        Adelic {
294            finite: RnsFrac::from_fraction(p, q, basis),
295            infinite: Ball::point(&BigRational::new(p.clone(), q.clone())),
296        }
297    }
298
299    /// The exact `(p, q)`, or a detected reconstruction failure.
300    pub fn try_exact(&self) -> Result<(BigInt, BigInt), RangeError> {
301        let (p, q) = self
302            .finite
303            .try_reconstruct()
304            .ok_or(RangeError::ReconstructionFailed {
305                have_bits: self.finite.basis().capacity_bits(),
306                need_more: true,
307            })?;
308        self.validate(&p, &q)
309    }
310}
311
312#[cfg(test)]
313mod tests {
314    use super::*;
315
316    // Headline regression test (refactor plan ยง11): overflow is DETECTED.
317    #[test]
318    fn overflow_is_detected_then_fixed() {
319        let small = Basis::with_bits(40);
320        let big = AdelicInt::from_bigint(&(BigInt::one() << 60), small.clone());
321        assert!(matches!(big.try_exact(), Err(RangeError::ReconstructionFailed { .. })));
322
323        let grown = big.with_basis(small.extend_to_bits(80));
324        assert_eq!(grown.try_exact().unwrap(), BigInt::one() << 60);
325    }
326
327    #[test]
328    fn sign_from_infinite_place_with_mixed_signs() {
329        let b = Basis::standard();
330        let a = AdelicInt::from_i64(-7, b.clone());
331        let c = AdelicInt::from_i64(5, b);
332        assert_eq!(a.add(&c).sign(), Some(Sign::Minus)); // -2
333        assert_eq!(a.sub(&c).sign(), Some(Sign::Minus)); // -12 (batch_sub path)
334    }
335
336    #[test]
337    fn integer_arithmetic_round_trips() {
338        let b = Basis::standard();
339        let a = AdelicInt::from_i64(1000, b.clone());
340        let c = AdelicInt::from_i64(337, b);
341        assert_eq!(a.add(&c).try_exact().unwrap(), BigInt::from(1337));
342        assert_eq!(a.sub(&c).try_exact().unwrap(), BigInt::from(663));
343        assert_eq!(a.mul(&c).try_exact().unwrap(), BigInt::from(337_000));
344    }
345
346    #[test]
347    fn rational_reconstructs() {
348        let b = Basis::standard();
349        let x = AdelicRat::from_fraction(&BigInt::from(1), &BigInt::from(6), b.clone());
350        let y = AdelicRat::from_fraction(&BigInt::from(1), &BigInt::from(4), b);
351        let sum = x.add(&y); // 1/6 + 1/4 = 5/12
352        assert_eq!(sum.try_exact().unwrap(), (BigInt::from(5), BigInt::from(12)));
353    }
354}