Skip to main content

adele_ring/
computable.rs

1//! Level 3 — ℝ_c. Computable reals: numbers stored as *algorithms* that produce
2//! a rational approximation to any requested precision, rather than as digits.
3//!
4//! A [`ComputableReal`] wraps an `Arc<dyn Computable>` oracle plus a precision
5//! cache. Asking for `evaluate(p)` returns a rational `r` with `|self - r| < 10⁻ᵖ`.
6//! Transcendental constants use fully rational series (Machin's formula for π,
7//! the factorial series for e) so no floating point ever contaminates the
8//! partial sums.
9
10use std::collections::BTreeMap;
11use std::sync::Arc;
12
13use num_bigint::BigInt;
14use num_rational::BigRational;
15use num_traits::One;
16use parking_lot::Mutex;
17
18use crate::algebraic::AlgebraicNumber;
19use crate::ball::Ball;
20use crate::basis::Basis;
21use crate::rational::RnsRational;
22
23/// An oracle that can approximate a real number to arbitrary precision.
24pub trait Computable: Send + Sync {
25    /// A rational `r` with `|value - r| < 10^(-precision)`.
26    fn evaluate(&self, precision: u64) -> RnsRational;
27}
28
29/// A computable real number (Level 3 of the tower).
30#[derive(Clone)]
31pub struct ComputableReal {
32    inner: Arc<dyn Computable>,
33    cache: Arc<Mutex<BTreeMap<u64, RnsRational>>>,
34    channels: Basis,
35}
36
37impl ComputableReal {
38    fn wrap(inner: Arc<dyn Computable>, channels: Basis) -> Self {
39        ComputableReal {
40            inner,
41            cache: Arc::new(Mutex::new(BTreeMap::new())),
42            channels,
43        }
44    }
45
46    /// Approximate to `precision` decimal places, memoizing the result.
47    ///
48    /// The cache is keyed by precision but a cached entry with key `≥ request`
49    /// satisfies the ask (a 50-digit enclosure answers a 10-digit query), so we
50    /// reuse the tightest already-computed result instead of recomputing.
51    pub fn evaluate(&self, precision: u64) -> RnsRational {
52        {
53            let cache = self.cache.lock();
54            if let Some((_, r)) = cache.range(precision..).next() {
55                return r.clone();
56            }
57        }
58        let r = self.inner.evaluate(precision);
59        self.cache.lock().insert(precision, r.clone());
60        r
61    }
62
63    /// A rigorous [`Ball`] of width `< 2·10^(-precision)` containing this number.
64    ///
65    /// This is the Archimedean (Level-3) face of the value: ordering, sign, and
66    /// magnitude on computable reals all flow through the returned `Ball`, the
67    /// same type the algebraic layer uses for sign-of-a-root. Because the oracle
68    /// contract guarantees `|value - evaluate(p)| < 10^(-p)`, padding the rational
69    /// midpoint by that radius yields a certified enclosure that composes
70    /// rigorously under `Ball` interval arithmetic.
71    pub fn enclose(&self, precision: u64) -> Ball {
72        let (p, q) = self.evaluate(precision).to_pair();
73        let center = BigRational::new(p, q);
74        let eps = BigRational::new(BigInt::one(), BigInt::from(10).pow(precision as u32));
75        Ball::new(&center - &eps, &center + &eps)
76    }
77
78    /// Convenience: evaluate at roughly `f64` precision.
79    pub fn evaluate_f64(&self) -> f64 {
80        self.evaluate(20).to_f64()
81    }
82
83    /// The RNS channels this value computes over.
84    pub fn channels(&self) -> Basis {
85        self.channels.clone()
86    }
87
88    // ── Constructors ────────────────────────────────────────────────────────
89
90    /// A constant rational.
91    pub fn from_rational(r: RnsRational) -> Self {
92        let channels = r.channels.clone();
93        Self::wrap(Arc::new(RationalC { r }), channels)
94    }
95
96    /// Drop a Level-2 algebraic number down to Level 3 for digit production.
97    pub fn from_algebraic(a: AlgebraicNumber) -> Self {
98        let channels = a.channels.clone();
99        Self::wrap(Arc::new(AlgebraicC { a }), channels)
100    }
101
102    /// π via Machin's formula `π = 16·atan(1/5) - 4·atan(1/239)`.
103    pub fn pi(channels: Basis) -> Self {
104        Self::wrap(Arc::new(PiC { channels: channels.clone() }), channels)
105    }
106
107    /// Euler's number e via the factorial series `Σ 1/k!`.
108    pub fn e(channels: Basis) -> Self {
109        Self::wrap(Arc::new(EulerC { channels: channels.clone() }), channels)
110    }
111
112    /// √r by Newton's method on rationals (quadratic convergence).
113    pub fn sqrt(r: RnsRational) -> Self {
114        let channels = r.channels.clone();
115        Self::wrap(Arc::new(SqrtC { r }), channels)
116    }
117
118    /// exp(r) via the Taylor series `Σ rᵏ/k!`.
119    pub fn exp(r: RnsRational) -> Self {
120        let channels = r.channels.clone();
121        Self::wrap(Arc::new(ExpC { r }), channels)
122    }
123
124    /// ln(r) for `r > 0` via `2·atanh((r-1)/(r+1))`.
125    pub fn ln(r: RnsRational) -> Self {
126        let channels = r.channels.clone();
127        Self::wrap(Arc::new(LnC { r }), channels)
128    }
129
130    // ── Lazy arithmetic ─────────────────────────────────────────────────────
131
132    /// Sum (lazy).
133    pub fn add(&self, other: &Self) -> Self {
134        Self::wrap(
135            Arc::new(BinOp {
136                a: self.clone(),
137                b: other.clone(),
138                kind: BinKind::Add,
139            }),
140            self.channels.clone(),
141        )
142    }
143
144    /// Difference (lazy).
145    pub fn sub(&self, other: &Self) -> Self {
146        self.add(&other.neg())
147    }
148
149    /// Product (lazy).
150    pub fn mul(&self, other: &Self) -> Self {
151        Self::wrap(
152            Arc::new(BinOp {
153                a: self.clone(),
154                b: other.clone(),
155                kind: BinKind::Mul,
156            }),
157            self.channels.clone(),
158        )
159    }
160
161    /// Negation (lazy).
162    pub fn neg(&self) -> Self {
163        Self::wrap(Arc::new(NegC { a: self.clone() }), self.channels.clone())
164    }
165
166    /// Reciprocal (lazy). Undefined behaviour if the value is zero.
167    pub fn recip(&self) -> Self {
168        Self::wrap(Arc::new(RecipC { a: self.clone() }), self.channels.clone())
169    }
170}
171
172// ── Precision helpers ────────────────────────────────────────────────────────
173
174/// `10^(-prec)` as a rational.
175fn eps(prec: u64, channels: &Basis) -> RnsRational {
176    RnsRational::new(BigInt::one(), pow10(prec), channels.clone())
177}
178
179fn pow10(p: u64) -> BigInt {
180    BigInt::from(10u8).pow(p as u32)
181}
182
183/// Number of integer digits in `|x|` (at least 1), via a cheap low-precision probe.
184fn magnitude_digits(cr: &ComputableReal) -> u64 {
185    let v = cr.evaluate(4).to_f64().abs();
186    if v < 1.0 {
187        1
188    } else {
189        v.log10().floor() as u64 + 1
190    }
191}
192
193// ── Oracle implementations ───────────────────────────────────────────────────
194
195struct RationalC {
196    r: RnsRational,
197}
198impl Computable for RationalC {
199    fn evaluate(&self, _precision: u64) -> RnsRational {
200        self.r.clone()
201    }
202}
203
204struct AlgebraicC {
205    a: AlgebraicNumber,
206}
207impl Computable for AlgebraicC {
208    fn evaluate(&self, precision: u64) -> RnsRational {
209        let mut clone = self.a.clone();
210        let target = eps(precision + 1, &self.a.channels);
211        clone.refine_interval(&target);
212        clone.interval.0.midpoint(&clone.interval.1)
213    }
214}
215
216/// arctan(1/x) as a rational, accurate to better than `eps`.
217fn atan_inv(x: i64, target: &RnsRational, channels: &Basis) -> RnsRational {
218    let mut acc = RnsRational::zero(channels.clone());
219    let mut n: i64 = 0;
220    loop {
221        let exp = (2 * n + 1) as u32;
222        let denom = BigInt::from(2 * n + 1) * BigInt::from(x).pow(exp);
223        let sign = if n % 2 == 0 { 1 } else { -1 };
224        let term = RnsRational::new(BigInt::from(sign), denom, channels.clone());
225        acc = acc.add(&term);
226        if term.abs() < *target {
227            break;
228        }
229        n += 1;
230    }
231    acc
232}
233
234struct PiC {
235    channels: Basis,
236}
237impl Computable for PiC {
238    fn evaluate(&self, precision: u64) -> RnsRational {
239        let target = eps(precision + 5, &self.channels);
240        let a = atan_inv(5, &target, &self.channels)
241            .mul(&RnsRational::from_int(16, self.channels.clone()));
242        let b = atan_inv(239, &target, &self.channels)
243            .mul(&RnsRational::from_int(4, self.channels.clone()));
244        a.sub(&b)
245    }
246}
247
248struct EulerC {
249    channels: Basis,
250}
251impl Computable for EulerC {
252    fn evaluate(&self, precision: u64) -> RnsRational {
253        let target = eps(precision + 3, &self.channels);
254        let mut acc = RnsRational::zero(self.channels.clone());
255        let mut fact = BigInt::one();
256        let mut k: u64 = 0;
257        loop {
258            if k > 0 {
259                fact *= BigInt::from(k);
260            }
261            let term = RnsRational::new(BigInt::one(), fact.clone(), self.channels.clone());
262            acc = acc.add(&term);
263            if term < target {
264                break;
265            }
266            k += 1;
267        }
268        acc
269    }
270}
271
272struct SqrtC {
273    r: RnsRational,
274}
275impl Computable for SqrtC {
276    fn evaluate(&self, precision: u64) -> RnsRational {
277        let channels = self.r.channels.clone();
278        let target = eps(precision + 2, &channels);
279        // Initial guess from f64.
280        let guess = self.r.to_f64().max(0.0).sqrt();
281        let mut x = if guess > 0.0 {
282            RnsRational::from_f64(guess, channels.clone())
283        } else {
284            RnsRational::from_int(1, channels.clone())
285        };
286        let two = RnsRational::from_int(2, channels.clone());
287        // Newton: x_{n+1} = (x + r/x) / 2 until x² is within target of r.
288        for _ in 0..200 {
289            if x.is_zero() {
290                break;
291            }
292            let next = x.add(&self.r.div(&x)).div(&two);
293            let err = next.mul(&next).sub(&self.r).abs();
294            x = next;
295            if err < target {
296                break;
297            }
298        }
299        x
300    }
301}
302
303struct ExpC {
304    r: RnsRational,
305}
306impl Computable for ExpC {
307    fn evaluate(&self, precision: u64) -> RnsRational {
308        let channels = self.r.channels.clone();
309        let target = eps(precision + 3, &channels);
310        let mut acc = RnsRational::zero(channels.clone());
311        let mut term = RnsRational::from_int(1, channels.clone()); // r^0 / 0!
312        let mut k: u64 = 0;
313        loop {
314            acc = acc.add(&term);
315            if k > 0 && term.abs() < target {
316                break;
317            }
318            k += 1;
319            // term *= r / k
320            term = term.mul(&self.r).div(&RnsRational::from_int(k as i64, channels.clone()));
321            if k > 5000 {
322                break;
323            }
324        }
325        acc
326    }
327}
328
329struct LnC {
330    r: RnsRational,
331}
332impl Computable for LnC {
333    fn evaluate(&self, precision: u64) -> RnsRational {
334        let channels = self.r.channels.clone();
335        let target = eps(precision + 3, &channels);
336        // t = (r - 1) / (r + 1); ln(r) = 2 Σ t^(2k+1)/(2k+1).
337        let one = RnsRational::from_int(1, channels.clone());
338        let t = self.r.sub(&one).div(&self.r.add(&one));
339        let t2 = t.mul(&t);
340        let mut acc = RnsRational::zero(channels.clone());
341        let mut power = t.clone();
342        let mut k: u64 = 0;
343        loop {
344            let term = power.div(&RnsRational::from_int((2 * k + 1) as i64, channels.clone()));
345            acc = acc.add(&term);
346            if term.abs() < target {
347                break;
348            }
349            power = power.mul(&t2);
350            k += 1;
351            if k > 100_000 {
352                break;
353            }
354        }
355        acc.mul(&RnsRational::from_int(2, channels.clone()))
356    }
357}
358
359enum BinKind {
360    Add,
361    Mul,
362}
363
364struct BinOp {
365    a: ComputableReal,
366    b: ComputableReal,
367    kind: BinKind,
368}
369impl Computable for BinOp {
370    fn evaluate(&self, precision: u64) -> RnsRational {
371        match self.kind {
372            BinKind::Add => {
373                let pa = self.a.evaluate(precision + 1);
374                let pb = self.b.evaluate(precision + 1);
375                pa.add(&pb)
376            }
377            BinKind::Mul => {
378                let guard = magnitude_digits(&self.a) + magnitude_digits(&self.b) + 2;
379                let pa = self.a.evaluate(precision + guard);
380                let pb = self.b.evaluate(precision + guard);
381                pa.mul(&pb)
382            }
383        }
384    }
385}
386
387struct NegC {
388    a: ComputableReal,
389}
390impl Computable for NegC {
391    fn evaluate(&self, precision: u64) -> RnsRational {
392        self.a.evaluate(precision).neg()
393    }
394}
395
396struct RecipC {
397    a: ComputableReal,
398}
399impl Computable for RecipC {
400    fn evaluate(&self, precision: u64) -> RnsRational {
401        // 1/v loses precision when |v| < 1; add guard digits accordingly.
402        let v = self.a.evaluate(4).to_f64().abs();
403        let extra = if v > 0.0 && v < 1.0 {
404            (-v.log10()).ceil() as u64 * 2 + 2
405        } else {
406            2
407        };
408        self.a.evaluate(precision + extra).recip()
409    }
410}
411
412/// Bridge from Level 2 to Level 3.
413impl AlgebraicNumber {
414    /// Produce a [`ComputableReal`] that yields digits on demand.
415    pub fn to_computable(&self) -> ComputableReal {
416        ComputableReal::from_algebraic(self.clone())
417    }
418}
419
420#[cfg(test)]
421mod tests {
422    use super::*;
423
424    fn ch() -> Basis {
425        Basis::standard()
426    }
427
428    #[test]
429    fn pi_to_ten_places() {
430        let pi = ComputableReal::pi(ch());
431        assert!((pi.evaluate(10).to_f64() - std::f64::consts::PI).abs() < 1e-10);
432    }
433
434    #[test]
435    fn e_to_fifteen_places() {
436        let e = ComputableReal::e(ch());
437        assert!((e.evaluate(15).to_f64() - std::f64::consts::E).abs() < 1e-14);
438    }
439
440    #[test]
441    fn sqrt_two() {
442        let r2 = RnsRational::from_int(2, ch());
443        let s = ComputableReal::sqrt(r2);
444        assert!((s.evaluate(20).to_f64() - 2f64.sqrt()).abs() < 1e-12);
445    }
446
447    #[test]
448    fn rational_passes_through() {
449        let r = RnsRational::from_fraction(1, 3, ch());
450        let cr = ComputableReal::from_rational(r.clone());
451        assert_eq!(cr.evaluate(100), r);
452    }
453
454    #[test]
455    fn precision_contract() {
456        let pi = ComputableReal::pi(ch());
457        let lo = pi.evaluate(5).to_f64();
458        let hi = pi.evaluate(50).to_f64();
459        assert!((lo - hi).abs() < 1e-5);
460    }
461
462    #[test]
463    fn lazy_sum_of_pi_and_one() {
464        let pi = ComputableReal::pi(ch());
465        let one = ComputableReal::from_rational(RnsRational::from_int(1, ch()));
466        let sum = pi.add(&one);
467        assert!((sum.evaluate(20).to_f64() - (std::f64::consts::PI + 1.0)).abs() < 1e-12);
468    }
469
470    #[test]
471    fn exp_and_ln() {
472        let e = ComputableReal::exp(RnsRational::from_int(1, ch()));
473        assert!((e.evaluate(15).to_f64() - std::f64::consts::E).abs() < 1e-13);
474        let l = ComputableReal::ln(RnsRational::from_int(2, ch()));
475        assert!((l.evaluate(15).to_f64() - 2f64.ln()).abs() < 1e-13);
476    }
477
478    #[test]
479    fn algebraic_to_computable() {
480        let s2 = AlgebraicNumber::sqrt(2, ch()).to_computable();
481        assert!((s2.evaluate(15).to_f64() - 2f64.sqrt()).abs() < 1e-13);
482    }
483
484    #[test]
485    fn enclose_is_rigorous() {
486        use crate::ball::rational_to_f64;
487        // 1/π via Ball interval arithmetic — certified, narrow.
488        let recip = ComputableReal::pi(ch()).enclose(50).recip().unwrap();
489        assert!(rational_to_f64(&recip.width()) < 1e-40);
490        assert!((recip.to_f64() - 1.0 / std::f64::consts::PI).abs() < 1e-40);
491
492        // e·π composed rigorously, then enclosed.
493        let prod = ComputableReal::e(ch()).mul(&ComputableReal::pi(ch())).enclose(30);
494        let epi = std::f64::consts::E * std::f64::consts::PI;
495        assert!(rational_to_f64(&prod.width()) < 1e-25);
496        assert!((prod.to_f64() - epi).abs() < 1e-9);
497    }
498
499    #[test]
500    fn cache_reuses_tighter_entry() {
501        let pi = ComputableReal::pi(ch());
502        let _ = pi.evaluate(40); // populate a high-precision entry
503        // A lower-precision ask is served from the cached 40-digit result.
504        assert!((pi.evaluate(5).to_f64() - std::f64::consts::PI).abs() < 1e-30);
505    }
506}