Skip to main content

logicaffeine_base/
hypernatural.rs

1//! Infinite integers generated by the program's generator `H` (`dt = 1/H`) — the trip counts of
2//! supertasks.
3//!
4//! A loop that steps `t` by `dt` from `0` while `t < T` runs `⌈T/dt⌉ = ⌈T·H⌉` times: an infinite
5//! natural, exactly determined once the residue of `H` is. A [`Hypernatural`] is held as a
6//! polynomial in `H` with rational coefficients on each residue class of `H` (a [`Depends`] of
7//! polynomials), each one integer-valued on every admitted member of its class — so `⌈H/3⌉` is
8//! `H/3`, `(H + 2)/3` or `(H + 1)/3` as `H ≡ 0, 1, 2 (mod 3)`, and `H/2` is an infinite integer
9//! once `H` is declared even.
10//!
11//! Since `H` exceeds every standard natural, two values compare by the leading coefficient of their
12//! difference, branch by branch. The residue of a value modulo `k` is periodic in `H`: an
13//! integer-valued polynomial on an admitted class `H ≡ c (mod L)` is an integer combination
14//! `Σ c_j·C(t, j)` of binomial coefficients in `t = (H − c)/L`. The term `c_j·C(t, j) mod k` reads
15//! `C(t, j)` modulo `q_j = k / gcd(c_j, k)`, and by Kummer's theorem `C(t, j) mod p^e` has period
16//! `p^(e + ⌊log_p j⌋)`; so `q_j` times the part of `lcm(1, …, j)` made of primes dividing `q_j` is a
17//! period, and evaluating one period of representatives gives every residue exactly. `H^64 mod 2`
18//! reads two classes and `C(H, 25) mod 2` the thirty-two Lucas's theorem names.
19
20use crate::hyperreal::{check_power_degree, Hyperreal, PowerError};
21use crate::limit_value::{Depends, ResidueError, ResidueType, MAX_MODULUS};
22use crate::numeric::{BigInt, Rational};
23use crate::poly::Poly;
24use std::cmp::Ordering;
25
26/// Why the integer part of a hyperreal is not an infinite integer.
27#[derive(Clone, Debug, PartialEq, Eq)]
28pub enum FloorError {
29    /// A power of `1/dt` with an irrational coefficient: `⌊√2/dt⌋` is a Beatty sequence in `H`,
30    /// no polynomial.
31    Irrational,
32    /// The residues the integer part depends on need more classes than one value holds.
33    Residue(ResidueError),
34}
35
36impl FloorError {
37    /// The refusal, with the infinitesimal written `inf`.
38    pub fn describe(&self, inf: &str) -> String {
39        match self {
40            FloorError::Irrational => format!(
41                "the integer part of a value with an irrational multiple of a power of 1/{inf} is \
42                 no polynomial in 1/{inf}"
43            ),
44            FloorError::Residue(e) => e.describe(&format!("⌈1/{inf}⌉")),
45        }
46    }
47}
48
49/// The longest period of a polynomial's residues [`integer_on`] reads class by class; past it, the
50/// polynomial is evaluated at consecutive members.
51const DIRECT_PERIOD: u64 = 1 << 16;
52
53/// Whether `p` is an integer at every `h = first + step·t` for natural `t`. A polynomial with integer
54/// coefficients is, at once. Otherwise `p = q/D` with `q` integral and `D` the coefficients' common
55/// denominator, and `q(h) mod D` depends only on `h mod D`, so it is read on the
56/// `D/gcd(step, D)` residues the members take; when that period passes [`DIRECT_PERIOD`], `p` is
57/// evaluated at `deg p + 1` consecutive members, which decides it by finite differences.
58fn integer_on(p: &Poly, first: u64, step: u64) -> bool {
59    let coeffs = p.coeffs();
60    if coeffs.iter().all(Rational::is_integer) {
61        return true;
62    }
63    let d = coeffs.iter().fold(BigInt::from_u64(1), |l, c| {
64        let den = c.denominator();
65        let g = l.gcd(den);
66        l.div_rem(&g).expect("a gcd of positive denominators is positive").0.mul(den)
67    });
68    let modular = d.to_u64().and_then(|dd| {
69        let period = dd / num_gcd(step % dd, dd).max(1);
70        (period <= DIRECT_PERIOD).then_some((dd, period))
71    });
72    let Some((dd, period)) = modular else {
73        let degree = p.degree().unwrap_or(0) as u64;
74        return (0..=degree).all(|t| {
75            let h = BigInt::from_u64(first).add(&BigInt::from_u64(step).mul(&BigInt::from_u64(t)));
76            p.eval(&Rational::from_bigint(h)).is_integer()
77        });
78    };
79    let residues: Vec<u128> = coeffs
80        .iter()
81        .map(|c| {
82            let q = c.numerator().mul(&d.div_rem(c.denominator()).expect("D is a multiple of each denominator").0);
83            let r = q.div_rem(&d).expect("D is positive").1;
84            let r = if r.is_negative() { r.add(&d) } else { r };
85            u128::from(r.to_u64().expect("a residue below D"))
86        })
87        .collect();
88    let dd = u128::from(dd);
89    (0..period).all(|t| {
90        let h = (u128::from(first) + u128::from(step) * u128::from(t)) % dd;
91        residues.iter().rev().fold(0u128, |acc, c| (acc * h + c) % dd) == 0
92    })
93}
94
95fn num_gcd(a: u64, b: u64) -> u64 {
96    if b == 0 {
97        a
98    } else {
99        num_gcd(b, a % b)
100    }
101}
102
103/// `⌊y⌋` for a limited hyperreal: the floor of its standard part, one less when the standard part
104/// is an integer approached from below.
105fn floor_limited(y: &Hyperreal) -> BigInt {
106    let st = y.standard_part().expect("a limited value has a standard part");
107    let fl = st.floor();
108    let on_integer = st == crate::algebraic::Algebraic::from_rational(Rational::from_bigint(fl.clone()));
109    if on_integer && y.sub(&Hyperreal::from_algebraic(st)).signum() < 0 {
110        fl.sub(&BigInt::from_u64(1))
111    } else {
112        fl
113    }
114}
115
116/// An integer polynomial in the generator, by residue class. See the module documentation.
117#[derive(Clone, Debug, PartialEq, Eq, Hash)]
118pub struct Hypernatural {
119    value: Depends<Poly>,
120}
121
122/// `lcm(a, b)` of positive integers.
123fn lcm(a: &BigInt, b: &BigInt) -> BigInt {
124    a.mul(b).div_rem(&a.gcd(b)).expect("a positive gcd").0
125}
126
127/// The part of `n` made of the primes that divide `q`.
128fn smooth_part(n: &BigInt, q: &BigInt) -> BigInt {
129    let one = BigInt::from_u64(1);
130    let mut rest = n.clone();
131    loop {
132        let g = rest.gcd(q);
133        if g == one {
134            break;
135        }
136        rest = rest.div_rem(&g).expect("a positive gcd").0;
137    }
138    n.div_rem(&rest).expect("a positive remainder").0
139}
140
141/// The integer value of `f` at the integer `x` (`f` is integer-valued there).
142fn eval_integer(f: &Poly, x: u64) -> BigInt {
143    f.eval(&Rational::from_bigint(BigInt::from_u64(x)))
144        .to_bigint()
145        .expect("a hypernatural branch is integer-valued on its class")
146}
147
148/// `p` in descending powers of `generator`: `2·H + 1`, `H^2 + 3·H`, `(1/3)·H + 2/3`.
149fn poly_text(p: &Poly, generator: &str) -> String {
150    let terms: Vec<(bool, String)> = (0..p.coeffs().len())
151        .rev()
152        .filter(|&i| !p.coeff(i).is_zero())
153        .map(|i| {
154            let c = p.coeff(i);
155            let negative = c.is_negative();
156            let m = c.abs();
157            let power = match i {
158                0 => String::new(),
159                1 => generator.to_string(),
160                k => format!("{generator}^{k}"),
161            };
162            let body = if i == 0 {
163                m.to_string()
164            } else if m == Rational::one() {
165                power
166            } else if m.is_integer() {
167                format!("{m}·{power}")
168            } else {
169                format!("({m})·{power}")
170            };
171            (negative, body)
172        })
173        .collect();
174    join_signed(terms)
175}
176
177/// `p(1/dt)` written with the infinitesimal's name: `2/dt + 1`, `1/dt^2`, `1/(3·dt)`.
178fn reciprocal_text(p: &Poly, inf: &str) -> String {
179    let terms: Vec<(bool, String)> = (0..p.coeffs().len())
180        .rev()
181        .filter(|&i| !p.coeff(i).is_zero())
182        .map(|i| {
183            let c = p.coeff(i);
184            let negative = c.is_negative();
185            let m = c.abs();
186            if i == 0 {
187                return (negative, m.to_string());
188            }
189            let power = if i == 1 {
190                inf.to_string()
191            } else {
192                format!("{inf}^{i}")
193            };
194            let (num, den) = (m.numerator().clone(), m.denominator().clone());
195            let body = if den == BigInt::from_i64(1) {
196                format!("{num}/{power}")
197            } else {
198                format!("{num}/({den}·{power})")
199            };
200            (negative, body)
201        })
202        .collect();
203    join_signed(terms)
204}
205
206fn join_signed(terms: Vec<(bool, String)>) -> String {
207    if terms.is_empty() {
208        return "0".to_string();
209    }
210    let mut out = String::new();
211    for (i, (negative, body)) in terms.into_iter().enumerate() {
212        match (i, negative) {
213            (0, true) => out.push_str(&format!("-{body}")),
214            (0, false) => out.push_str(&body),
215            (_, true) => out.push_str(&format!(" - {body}")),
216            (_, false) => out.push_str(&format!(" + {body}")),
217        }
218    }
219    out
220}
221
222impl Hypernatural {
223    /// `H` itself.
224    pub fn generator(ty: &ResidueType) -> Hypernatural {
225        Hypernatural {
226            value: Depends::constant(Poly::x(), ty),
227        }
228    }
229
230    /// A standard integer.
231    pub fn from_integer(n: BigInt, ty: &ResidueType) -> Hypernatural {
232        Hypernatural {
233            value: Depends::constant(Poly::constant(Rational::from_bigint(n)), ty),
234        }
235    }
236
237    /// The polynomial `p` in `H` as an infinite integer, when it is an integer on every class the
238    /// type admits: a polynomial of degree `d` that is an integer at `d + 1` consecutive integers is
239    /// one at every integer, and likewise along one residue class.
240    pub fn from_polynomial(p: Poly, ty: &ResidueType) -> Option<Hypernatural> {
241        let m = ty.modulus();
242        if !integer_on(&p, 0, 1) && !ty.residues().into_iter().all(|r| integer_on(&p, r, m)) {
243            return None;
244        }
245        Some(Hypernatural {
246            value: Depends::constant(p, ty),
247        })
248    }
249
250    /// The value `p` on each class, when each `p` is an integer on the admitted members of its
251    /// class.
252    pub fn from_branches(value: Depends<Poly>) -> Option<Hypernatural> {
253        let candidate = Hypernatural { value };
254        let (l, classes) = candidate.admitted_classes();
255        for (c, p) in classes {
256            if !integer_on(p, c, l) {
257                return None;
258            }
259        }
260        Some(candidate)
261    }
262
263    /// `⌊x⌋` for a hyperreal `x` under the residue type `ty`. With `x = P(H) + r` — `P` its
264    /// principal part with rational coefficients, `r` limited — and `D` the common denominator of
265    /// `P`, the fractional part of `P(H)` is `(D·P(H) mod D)/D` on each class, so
266    /// `⌊x⌋ = P(H) − frac + ⌊frac + r⌋` there.
267    pub fn floor_of(x: &Hyperreal, ty: &ResidueType) -> Result<Hypernatural, FloorError> {
268        let (principal, rest) = x.principal_part();
269        let mut coefficients = vec![Rational::zero()];
270        for a in &principal {
271            coefficients.push(a.to_rational().ok_or(FloorError::Irrational)?);
272        }
273        let p = Poly::from_coeffs(coefficients);
274        let denominator = p
275            .coeffs()
276            .iter()
277            .fold(BigInt::from_u64(1), |acc, c| lcm(&acc, c.denominator()));
278        let scaled = Hypernatural::from_polynomial(p.scale(&Rational::from_bigint(denominator.clone())), ty)
279            .expect("an integer polynomial is an integer everywhere");
280        let fractions = scaled.rem(&denominator).map_err(FloorError::Residue)?;
281        let value = fractions.map(|f| {
282            let frac = Rational::new(f.clone(), denominator.clone()).expect("a positive denominator");
283            let carry = floor_limited(&rest.add(&Hyperreal::from_rational(frac.clone())));
284            p.sub(&Poly::constant(frac))
285                .add(&Poly::constant(Rational::from_bigint(carry)))
286        });
287        Ok(Hypernatural::from_branches(value).expect("each branch is an integer on its class"))
288    }
289
290    /// `⌈x⌉ = −⌊−x⌋`.
291    pub fn ceil_of(x: &Hyperreal, ty: &ResidueType) -> Result<Hypernatural, FloorError> {
292        Ok(Self::floor_of(&x.neg(), ty)?.neg())
293    }
294
295    pub fn depends(&self) -> &Depends<Poly> {
296        &self.value
297    }
298
299    pub fn add(&self, o: &Hypernatural) -> Result<Hypernatural, ResidueError> {
300        Ok(Hypernatural {
301            value: self.value.zip_with(&o.value, |a, b| a.add(b))?,
302        })
303    }
304
305    pub fn sub(&self, o: &Hypernatural) -> Result<Hypernatural, ResidueError> {
306        Ok(Hypernatural {
307            value: self.value.zip_with(&o.value, |a, b| a.sub(b))?,
308        })
309    }
310
311    pub fn mul(&self, o: &Hypernatural) -> Result<Hypernatural, ResidueError> {
312        Ok(Hypernatural {
313            value: self.value.zip_with(&o.value, |a, b| a.mul(b))?,
314        })
315    }
316
317    /// The highest degree in `H` over every class.
318    pub fn degree(&self) -> usize {
319        self.value
320            .branches()
321            .map(|(_, p)| p.degree().unwrap_or(0))
322            .max()
323            .unwrap_or(0)
324    }
325
326    /// `self^k` by repeated squaring, refused when its degree would pass
327    /// [`MAX_POWER_DEGREE`](crate::hyperreal::MAX_POWER_DEGREE).
328    pub fn pow(&self, k: u64) -> Result<Hypernatural, PowerError> {
329        check_power_degree(self.degree(), k)?;
330        let square = |a: &Hypernatural, b: &Hypernatural| {
331            a.mul(b).expect("a value meets its own residue classes")
332        };
333        let mut acc = Hypernatural::from_integer(BigInt::from_u64(1), self.value.residue_type());
334        let mut base = self.clone();
335        let mut e = k;
336        while e > 0 {
337            if e & 1 == 1 {
338                acc = square(&acc, &base);
339            }
340            e >>= 1;
341            if e > 0 {
342                base = square(&base, &base);
343            }
344        }
345        Ok(acc)
346    }
347
348    pub fn neg(&self) -> Hypernatural {
349        Hypernatural {
350            value: self.value.map(|a| a.neg()),
351        }
352    }
353
354    /// Each class modulo `L = lcm(modulus, type modulus)` the type admits, with its branch: the
355    /// arithmetic progressions `c + L·t` along which a branch is integer-valued. A branch held at a
356    /// coarser modulus than the type's is integral only on the admitted members of its class.
357    fn admitted_classes(&self) -> (u64, Vec<(u64, &Poly)>) {
358        let l = lcm(
359            &BigInt::from_u64(self.value.modulus()),
360            &BigInt::from_u64(self.value.residue_type().modulus()),
361        )
362        .to_u64()
363        .expect("a value is held within the bound of its own type");
364        let classes = (0..l).filter_map(|c| self.value.at(c).map(|f| (c, f))).collect();
365        (l, classes)
366    }
367
368    /// A period in `H` of `self mod k`. See the module documentation.
369    fn residue_period(&self, k: &BigInt) -> BigInt {
370        let one = BigInt::from_u64(1);
371        let (l, classes) = self.admitted_classes();
372        let mut period = one.clone();
373        for (c, f) in classes {
374            let degree = f.degree().unwrap_or(0) as u64;
375            let mut differences: Vec<BigInt> =
376                (0..=degree).map(|t| eval_integer(f, c + l * t)).collect();
377            let mut lcm_to_j = one.clone();
378            for j in 1..=degree {
379                differences = differences.windows(2).map(|w| w[1].sub(&w[0])).collect();
380                lcm_to_j = lcm(&lcm_to_j, &BigInt::from_u64(j));
381                let q = k.div_rem(&differences[0].gcd(k)).expect("k > 0").0;
382                if q != one {
383                    period = lcm(&period, &q.mul(&smooth_part(&lcm_to_j, &q)));
384                }
385            }
386        }
387        period.mul(&BigInt::from_u64(l))
388    }
389
390    /// `self mod k` (in `0..k`) on every residue class of `H`; refused when reading it needs more
391    /// than [`MAX_MODULUS`] classes.
392    pub fn rem(&self, k: &BigInt) -> Result<Depends<BigInt>, ResidueError> {
393        assert!(k.is_positive(), "a modulus is positive");
394        let period = self.residue_period(k);
395        let ty = self.value.residue_type();
396        let classes = lcm(&period, &BigInt::from_u64(ty.modulus()));
397        let period = match period.to_u64() {
398            Some(p) if classes <= BigInt::from_u64(MAX_MODULUS) => p,
399            _ => return Err(ResidueError::TooFine { classes }),
400        };
401        let m = self.value.modulus();
402        let raw = self.value.raw_branches();
403        Depends::from_fn(period, ty, move |x| {
404            let f = raw[(x % m) as usize]
405                .as_ref()
406                .expect("an admitted residue has a branch");
407            let r = eval_integer(f, x).div_rem(k).expect("k > 0").1;
408            if r.is_negative() {
409                r.add(k)
410            } else {
411                r
412            }
413        })
414    }
415
416    /// `⌊t·self⌋` for a rational `t`.
417    pub fn scaled_floor(&self, t: &Rational) -> Result<Hypernatural, ResidueError> {
418        let p = Rational::from_bigint(t.numerator().clone());
419        let q = t.denominator().clone();
420        let scaled = Hypernatural {
421            value: self.value.map(|f| f.scale(&p)),
422        };
423        let rem = scaled.rem(&q)?;
424        let inv = Rational::from_bigint(q).recip().expect("q > 0");
425        Ok(Hypernatural {
426            value: scaled.value.zip_with(&rem, |f, r| {
427                f.sub(&Poly::constant(Rational::from_bigint(r.clone()))).scale(&inv)
428            })?,
429        })
430    }
431
432    /// `⌈t·self⌉ = −⌊−t·self⌋`.
433    pub fn scaled_ceil(&self, t: &Rational) -> Result<Hypernatural, ResidueError> {
434        Ok(self.scaled_floor(&t.negated())?.neg())
435    }
436
437    /// The order against another value on every class (`H` exceeds every standard natural, so the
438    /// leading coefficient of the difference decides).
439    pub fn compare(&self, o: &Hypernatural) -> Result<Depends<Ordering>, ResidueError> {
440        self.value.zip_with(&o.value, |a, b| {
441            let d = a.sub(b);
442            match d.leading() {
443                None => Ordering::Equal,
444                Some(c) if c.is_negative() => Ordering::Less,
445                Some(_) => Ordering::Greater,
446            }
447        })
448    }
449
450    pub fn is_infinite(&self) -> Depends<bool> {
451        self.value.map(|p| p.degree().unwrap_or(0) > 0)
452    }
453
454    pub fn is_negative(&self) -> Depends<bool> {
455        self.value.map(|p| p.leading().is_some_and(Rational::is_negative))
456    }
457
458    /// The value at the standard point `H = h`, when the type admits it.
459    pub fn eval_at(&self, h: u64) -> Option<BigInt> {
460        self.value.at(h).map(|f| eval_integer(f, h))
461    }
462
463    /// `H ↦ 1/dt`.
464    pub fn to_hyperreal(&self) -> Depends<Hyperreal> {
465        self.value.map(|p| Hyperreal::from_reciprocal_powers(p.coeffs()))
466    }
467
468    /// The value as a polynomial in `generator`, with its dependence on the residue when there is
469    /// one.
470    pub fn render(&self, generator: &str) -> String {
471        self.value.render(generator, |p| poly_text(p, generator))
472    }
473
474    /// The value written in the infinitesimal `dt = 1/H`: `2/dt + 1`.
475    pub fn render_reciprocal(&self, inf: &str) -> String {
476        let generator = format!("⌈1/{inf}⌉");
477        self.value.render(&generator, |p| reciprocal_text(p, inf))
478    }
479}
480
481impl crate::exact_codec::ExactCodec for Hypernatural {
482    /// The residue-indexed polynomials in the generator.
483    fn write(&self, out: &mut Vec<u8>) {
484        self.value.write(out);
485    }
486
487    /// Refused unless every branch is integer-valued on the admitted members of its class: a
488    /// polynomial of degree `d` that is an integer at `d + 1` consecutive members of an arithmetic
489    /// progression is one at every member.
490    fn read(buf: &[u8], pos: &mut usize) -> Option<Self> {
491        Hypernatural::from_branches(crate::exact_codec::ExactCodec::read(buf, pos)?)
492    }
493}
494
495#[cfg(test)]
496#[path = "../../logicaffeine_tests/unit/logicaffeine_base/hypernatural/tests.rs"]
497mod tests;