Skip to main content

logicaffeine_base/
limit_value.rs

1//! Values at the end of a supertask: what depends on the infinite generator's residue, the limit
2//! rules that turn such a value into one number, and the provisional answer of an unfinished
3//! search.
4//!
5//! A program's infinitesimal is `dt = 1/H` for an infinite natural `H` the program never fixes. A
6//! loop that runs `H` times can end in a state that depends on `H` itself — Thomson's lamp, toggled
7//! `H` times, is on exactly when `H` is odd. Which residues `H` may take is all the program knows
8//! (`Let dt be an infinitesimal whose reciprocal is even.`), recorded as a [`ResidueType`]: the
9//! residues allowed modulo some modulus. In Presburger arithmetic the complete type of an infinite
10//! natural is exactly its residue system — an element of the profinite integers — so a residue type
11//! is the most a program can say about `H` without naming it.
12//!
13//! A [`Depends`] value is the same quantity on every residue class of `H`, held modulo the smallest
14//! modulus that still separates its branches. When the residue type pins a single branch it is an
15//! ordinary value; otherwise it prints its dependence (`0 when ⌈1/dt⌉ is even, 1 when it is odd`)
16//! and a [`LimitRule`] resolves it: the limit superior or inferior (the extreme branch, the
17//! infinite-time Turing machine's rule at a limit stage), the Cesàro mean (each admitted residue
18//! weighted by its density), or the limit itself, which exists only when every branch agrees.
19//!
20//! A [`Converging`] value is the answer of a search no proof has settled: the current guess, how
21//! many cases were checked and how often the guess changed — Shoenfield's limit lemma made honest.
22
23use crate::algebraic::Algebraic;
24use crate::numeric::BigInt;
25use std::fmt;
26
27/// The most residue classes one value may be split over: the wire's collection bound, so every
28/// value a program holds can travel.
29pub const MAX_MODULUS: u64 = crate::exact_codec::MAX_WIRE_LEN as u64;
30
31/// Why a residue system cannot be formed.
32#[derive(Clone, Debug, PartialEq, Eq)]
33pub enum ResidueError {
34    /// No natural satisfies every fact.
35    Contradiction,
36    /// The value would split the generator over more than [`MAX_MODULUS`] classes.
37    TooFine { classes: BigInt },
38}
39
40impl ResidueError {
41    /// The refusal, with the generator written `generator`.
42    pub fn describe(&self, generator: &str) -> String {
43        match self {
44            ResidueError::Contradiction => format!(
45                "the facts about {generator} contradict each other: no natural satisfies them all"
46            ),
47            ResidueError::TooFine { classes } => format!(
48                "{generator} would split over {classes} residue classes; one value holds at most 65,536"
49            ),
50        }
51    }
52}
53
54impl fmt::Display for ResidueError {
55    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56        f.write_str(&self.describe("the generator"))
57    }
58}
59
60fn gcd(a: u64, b: u64) -> u64 {
61    if b == 0 {
62        a
63    } else {
64        gcd(b, a % b)
65    }
66}
67
68/// `lcm(a, b)` when it is within [`MAX_MODULUS`].
69pub(crate) fn bounded_lcm(a: u64, b: u64) -> Result<u64, ResidueError> {
70    let l = u128::from(a / gcd(a, b)) * u128::from(b);
71    bounded(l)
72}
73
74fn bounded(classes: u128) -> Result<u64, ResidueError> {
75    if classes > u128::from(MAX_MODULUS) {
76        let (hi, lo) = ((classes >> 64) as u64, classes as u64);
77        return Err(ResidueError::TooFine {
78            classes: BigInt::from_u64(hi).shl_bits(64).add(&BigInt::from_u64(lo)),
79        });
80    }
81    Ok(classes as u64)
82}
83
84/// The residues the infinite generator may take. See the module documentation.
85#[derive(Clone, Debug, PartialEq, Eq, Hash)]
86pub struct ResidueType {
87    /// The smallest modulus that describes the allowed set.
88    modulus: u64,
89    /// `allowed[r]`: whether `H ≡ r (mod modulus)` is possible.
90    allowed: Vec<bool>,
91}
92
93impl ResidueType {
94    /// Nothing is known.
95    pub fn any() -> ResidueType {
96        ResidueType {
97            modulus: 1,
98            allowed: vec![true],
99        }
100    }
101
102    /// `H ≡ r (mod m)`.
103    ///
104    /// # Panics
105    ///
106    /// When `m` is `0` or above [`MAX_MODULUS`]; a modulus a program supplies goes through
107    /// [`ResidueType::from_allowed`], which refuses it instead.
108    pub fn congruent(r: u64, m: u64) -> ResidueType {
109        assert!(m > 0 && m <= MAX_MODULUS, "a modulus is in 1..=MAX_MODULUS");
110        Self::canonical(m, (0..m).map(|x| x == r % m).collect())
111    }
112
113    fn canonical(m: u64, allowed: Vec<bool>) -> ResidueType {
114        for d in (1..=m).filter(|d| m.is_multiple_of(*d)) {
115            if (0..m as usize).all(|x| allowed[x] == allowed[x % d as usize]) {
116                return ResidueType {
117                    modulus: d,
118                    allowed: allowed[..d as usize].to_vec(),
119                };
120            }
121        }
122        unreachable!("the modulus itself is a period")
123    }
124
125    pub fn modulus(&self) -> u64 {
126        self.modulus
127    }
128
129    /// Whether `H = x` is consistent with the type.
130    pub fn admits(&self, x: u64) -> bool {
131        self.allowed[(x % self.modulus) as usize]
132    }
133
134    /// Whether some admitted `H` has `H ≡ r (mod m)`. By the Chinese remainder theorem that is
135    /// an admitted class `s` with `s ≡ r` modulo `gcd(m, modulus)`, so any `m` is answered in time
136    /// linear in the type's own modulus.
137    pub fn allows(&self, r: u64, m: u64) -> bool {
138        assert!(m > 0, "a modulus is positive");
139        let g = gcd(m, self.modulus);
140        self.residues().iter().any(|s| s % g == r % m % g)
141    }
142
143    /// Which classes modulo `g`, a divisor of the modulus, contain an admitted class.
144    fn admitted_modulo(&self, g: u64) -> Vec<bool> {
145        let mut out = vec![false; g as usize];
146        for s in self.residues() {
147            out[(s % g) as usize] = true;
148        }
149        out
150    }
151
152    /// The type allowing exactly `allowed` modulo `m` (residues reduce modulo `m`): a
153    /// contradiction when nothing is allowed, too fine when `m` passes [`MAX_MODULUS`].
154    pub fn from_allowed(m: u64, allowed: &[u64]) -> Result<ResidueType, ResidueError> {
155        assert!(m > 0, "a modulus is positive");
156        let m = bounded(u128::from(m))?;
157        let mut set = vec![false; m as usize];
158        for r in allowed {
159            set[(r % m) as usize] = true;
160        }
161        if !set.iter().any(|&a| a) {
162            return Err(ResidueError::Contradiction);
163        }
164        Ok(Self::canonical(m, set))
165    }
166
167    /// The allowed residues modulo [`ResidueType::modulus`], ascending.
168    pub fn residues(&self) -> Vec<u64> {
169        (0..self.modulus).filter(|&r| self.allowed[r as usize]).collect()
170    }
171
172    /// Both facts at once.
173    pub fn and(&self, o: &ResidueType) -> Result<ResidueType, ResidueError> {
174        let l = bounded_lcm(self.modulus, o.modulus)?;
175        let allowed: Vec<bool> = (0..l).map(|x| self.admits(x) && o.admits(x)).collect();
176        if !allowed.iter().any(|&a| a) {
177            return Err(ResidueError::Contradiction);
178        }
179        Ok(Self::canonical(l, allowed))
180    }
181}
182
183/// A quantity on every residue class of the generator. See the module documentation.
184#[derive(Clone, Debug, PartialEq, Eq, Hash)]
185pub struct Depends<T> {
186    ty: ResidueType,
187    modulus: u64,
188    /// `branches[r]` for `H ≡ r (mod modulus)`; `None` for a class the type rules out.
189    branches: Vec<Option<T>>,
190}
191
192impl<T: Clone + PartialEq> Depends<T> {
193    pub fn constant(v: T, ty: &ResidueType) -> Depends<T> {
194        Depends {
195            ty: ty.clone(),
196            modulus: 1,
197            branches: vec![Some(v)],
198        }
199    }
200
201    /// The quantity `f(H mod m)` on the classes the type admits; refused when `m` and the type's
202    /// modulus together pass [`MAX_MODULUS`].
203    pub fn from_fn(m: u64, ty: &ResidueType, f: impl Fn(u64) -> T) -> Result<Depends<T>, ResidueError> {
204        assert!(m > 0, "a modulus is positive");
205        let l = bounded_lcm(m, ty.modulus)?;
206        let raw: Vec<Option<T>> = (0..l).map(|x| ty.admits(x).then(|| f(x % m))).collect();
207        Ok(Self::collapse(ty.clone(), l, raw))
208    }
209
210    /// Hold `raw` (indexed by `H mod l`) modulo the smallest divisor of `l` on which every
211    /// admitted class agrees.
212    fn collapse(ty: ResidueType, l: u64, raw: Vec<Option<T>>) -> Depends<T> {
213        for d in (1..=l).filter(|d| l.is_multiple_of(*d)) {
214            let mut reduced: Vec<Option<T>> = vec![None; d as usize];
215            let mut consistent = true;
216            for (x, v) in raw.iter().enumerate() {
217                let Some(v) = v else { continue };
218                let slot = &mut reduced[x % d as usize];
219                match slot {
220                    Some(w) if w != v => {
221                        consistent = false;
222                        break;
223                    }
224                    Some(_) => {}
225                    None => *slot = Some(v.clone()),
226                }
227            }
228            if consistent {
229                return Depends {
230                    ty,
231                    modulus: d,
232                    branches: reduced,
233                };
234            }
235        }
236        unreachable!("the full modulus is always consistent")
237    }
238
239    /// Rebuild a quantity from its value on every class modulo `m` (`None` exactly for the classes
240    /// `ty` rules out), held at its smallest modulus; `None` when the classes and the type disagree.
241    pub fn from_branches(ty: ResidueType, m: u64, raw: Vec<Option<T>>) -> Option<Depends<T>> {
242        if m == 0 || raw.len() as u64 != m || bounded_lcm(m, ty.modulus).is_err() {
243            return None;
244        }
245        let g = gcd(m, ty.modulus);
246        let admitted = ty.admitted_modulo(g);
247        if raw.iter().enumerate().any(|(x, v)| v.is_some() != admitted[x % g as usize]) {
248            return None;
249        }
250        Some(Self::collapse(ty, m, raw))
251    }
252
253    /// The value on every class modulo [`Depends::modulus`], `None` for a class the type rules out.
254    pub fn raw_branches(&self) -> Vec<Option<T>> {
255        self.branches.clone()
256    }
257
258    pub fn modulus(&self) -> u64 {
259        self.modulus
260    }
261
262    pub fn residue_type(&self) -> &ResidueType {
263        &self.ty
264    }
265
266    /// The value, when every admitted class has the same one.
267    pub fn single(&self) -> Option<&T> {
268        let mut values = self.branches.iter().flatten();
269        let first = values.next()?;
270        values.all(|v| v == first).then_some(first)
271    }
272
273    /// The value at `H = h`; `None` when the type rules `h` out.
274    pub fn at(&self, h: u64) -> Option<&T> {
275        if !self.ty.admits(h) {
276            return None;
277        }
278        self.branches[(h % self.modulus) as usize].as_ref()
279    }
280
281    /// The admitted classes and their values, by residue.
282    pub fn branches(&self) -> impl Iterator<Item = (u64, &T)> {
283        self.branches
284            .iter()
285            .enumerate()
286            .filter_map(|(r, v)| v.as_ref().map(|v| (r as u64, v)))
287    }
288
289    pub fn map<U: Clone + PartialEq>(&self, f: impl Fn(&T) -> U) -> Depends<U> {
290        let raw = self.branches.iter().map(|v| v.as_ref().map(&f)).collect();
291        Depends::collapse(self.ty.clone(), self.modulus, raw)
292    }
293
294    /// Combine two quantities branch by branch; refused when their classes together pass
295    /// [`MAX_MODULUS`] or their types admit no common class.
296    pub fn zip_with<U: Clone + PartialEq, V: Clone + PartialEq>(
297        &self,
298        o: &Depends<U>,
299        f: impl Fn(&T, &U) -> V,
300    ) -> Result<Depends<V>, ResidueError> {
301        let l = bounded_lcm(self.modulus, o.modulus)?;
302        let ty = self.ty.and(&o.ty)?;
303        let l = bounded_lcm(l, ty.modulus)?;
304        let raw = (0..l)
305            .map(|x| {
306                if !ty.admits(x) {
307                    return None;
308                }
309                let a = self.branches[(x % self.modulus) as usize].as_ref()?;
310                let b = o.branches[(x % o.modulus) as usize].as_ref()?;
311                Some(f(a, b))
312            })
313            .collect();
314        Ok(Depends::collapse(ty, l, raw))
315    }
316
317    /// `v` alone when it is single; otherwise the dependence on the generator, written `generator`:
318    /// `0 when ⌈1/dt⌉ is even, 1 when it is odd`, or one clause per class modulo the modulus.
319    pub fn render(&self, generator: &str, show: impl Fn(&T) -> String) -> String {
320        if let Some(v) = self.single() {
321            return show(v);
322        }
323        let classes: Vec<(u64, &T)> = self.branches().collect();
324        if self.modulus == 2 && classes.len() == 2 {
325            return format!(
326                "{} when {generator} is even, {} when it is odd",
327                show(classes[0].1),
328                show(classes[1].1)
329            );
330        }
331        classes
332            .iter()
333            .map(|(r, v)| format!("{} when {generator} ≡ {r} (mod {})", show(v), self.modulus))
334            .collect::<Vec<_>>()
335            .join(", ")
336    }
337}
338
339/// How a quantity that depends on the generator becomes one value at the limit.
340#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
341pub enum LimitRule {
342    StandardPart,
343    Limit,
344    LimitSuperior,
345    LimitInferior,
346    CesaroMean,
347}
348
349impl Depends<Algebraic> {
350    /// The value under `rule`, the generator written `⌈1/dt⌉` in an error.
351    pub fn apply(&self, rule: LimitRule) -> Result<Algebraic, String> {
352        self.apply_with(rule, "⌈1/dt⌉")
353    }
354
355    /// The value under `rule`; an error naming the dependence (the generator written `generator`)
356    /// when the rule demands a limit that does not exist.
357    pub fn apply_with(&self, rule: LimitRule, generator: &str) -> Result<Algebraic, String> {
358        match rule {
359            LimitRule::StandardPart | LimitRule::Limit => self.single().cloned().ok_or_else(|| {
360                format!(
361                    "no limit: the value is {}",
362                    self.render(generator, |v| v.to_string())
363                )
364            }),
365            LimitRule::LimitSuperior => Ok(self
366                .branches()
367                .map(|(_, v)| v.clone())
368                .max()
369                .expect("a residue type admits some class")),
370            LimitRule::LimitInferior => Ok(self
371                .branches()
372                .map(|(_, v)| v.clone())
373                .min()
374                .expect("a residue type admits some class")),
375            LimitRule::CesaroMean => {
376                let l = bounded_lcm(self.modulus, self.ty.modulus)
377                    .expect("a value is held within the bound of its own type");
378                let (sum, count) = (0..l)
379                    .filter(|&x| self.ty.admits(x))
380                    .filter_map(|x| self.branches[(x % self.modulus) as usize].as_ref())
381                    .fold((Algebraic::from_i64(0), 0i64), |(s, c), v| (s.add(v), c + 1));
382                Ok(sum
383                    .div(&Algebraic::from_i64(count))
384                    .expect("a residue type admits some class"))
385            }
386        }
387    }
388}
389
390/// The answer of a search: settled by a certificate, or provisional with its evidence.
391#[derive(Clone, Debug, PartialEq, Eq, Hash)]
392pub struct Converging<T> {
393    value: T,
394    steps: u64,
395    revisions: u64,
396    certificate: Option<String>,
397}
398
399fn thousands(n: u64) -> String {
400    let digits = n.to_string();
401    let mut out = String::new();
402    for (i, ch) in digits.chars().enumerate() {
403        if i > 0 && (digits.len() - i).is_multiple_of(3) {
404            out.push(',');
405        }
406        out.push(ch);
407    }
408    out
409}
410
411impl<T> Converging<T> {
412    /// The current guess after `steps` cases, having changed `revisions` times, with no proof.
413    pub fn provisional(value: T, steps: u64, revisions: u64) -> Converging<T> {
414        Converging {
415            value,
416            steps,
417            revisions,
418            certificate: None,
419        }
420    }
421
422    /// A value a certificate settles.
423    pub fn settled(value: T, certificate: &str) -> Converging<T> {
424        Converging {
425            value,
426            steps: 0,
427            revisions: 0,
428            certificate: Some(certificate.to_string()),
429        }
430    }
431
432    pub fn value(&self) -> &T {
433        &self.value
434    }
435
436    /// The same evidence for `value`.
437    pub fn with_value<U>(&self, value: U) -> Converging<U> {
438        Converging {
439            value,
440            steps: self.steps,
441            revisions: self.revisions,
442            certificate: self.certificate.clone(),
443        }
444    }
445
446    pub fn steps(&self) -> u64 {
447        self.steps
448    }
449
450    pub fn revisions(&self) -> u64 {
451        self.revisions
452    }
453
454    pub fn is_settled(&self) -> bool {
455        self.certificate.is_some()
456    }
457
458    pub fn certificate(&self) -> Option<&str> {
459        self.certificate.as_deref()
460    }
461
462    /// The evidence of `value`, made from this answer and `other`: settled when both are (their
463    /// certificates joined), the other's evidence when one is settled, and otherwise provisional on
464    /// the fewest cases either search checked with every revision either made.
465    pub fn joined<U, W>(&self, other: &Converging<U>, value: W) -> Converging<W> {
466        match (&self.certificate, &other.certificate) {
467            (Some(a), Some(b)) if a == b => Converging::settled(value, a),
468            (Some(a), Some(b)) => Converging::settled(value, &format!("{a}; {b}")),
469            (Some(_), None) => Converging::provisional(value, other.steps, other.revisions),
470            (None, Some(_)) => Converging::provisional(value, self.steps, self.revisions),
471            (None, None) => Converging::provisional(
472                value,
473                self.steps.min(other.steps),
474                self.revisions.saturating_add(other.revisions),
475            ),
476        }
477    }
478
479    /// A settled value prints as itself; a provisional one carries its evidence:
480    /// `false (provisional: checked 10,000,000 cases, 0 revisions; not proved)`.
481    pub fn render(&self, show: impl Fn(&T) -> String) -> String {
482        if self.is_settled() {
483            return show(&self.value);
484        }
485        let revisions = if self.revisions == 1 { "revision" } else { "revisions" };
486        format!(
487            "{} (provisional: checked {} cases, {} {revisions}; not proved)",
488            show(&self.value),
489            thousands(self.steps),
490            self.revisions
491        )
492    }
493}
494
495impl crate::exact_codec::ExactCodec for ResidueType {
496    /// The modulus, then the allowed residues.
497    fn write(&self, out: &mut Vec<u8>) {
498        self.modulus.write(out);
499        crate::exact_codec::write_list(&self.residues(), out);
500    }
501
502    fn read(buf: &[u8], pos: &mut usize) -> Option<Self> {
503        let m = u64::read(buf, pos)?;
504        if m == 0 || m > MAX_MODULUS {
505            return None;
506        }
507        let allowed: Vec<u64> = crate::exact_codec::read_list(buf, pos)?;
508        if allowed.iter().any(|r| *r >= m) {
509            return None;
510        }
511        ResidueType::from_allowed(m, &allowed).ok()
512    }
513}
514
515impl<T: crate::exact_codec::ExactCodec + Clone + PartialEq> crate::exact_codec::ExactCodec for Depends<T> {
516    /// The residue type, the modulus, then each class's value (`0` for a class the type rules out,
517    /// `1` and the value otherwise).
518    fn write(&self, out: &mut Vec<u8>) {
519        self.ty.write(out);
520        self.modulus.write(out);
521        for b in &self.branches {
522            match b {
523                None => out.push(0),
524                Some(v) => {
525                    out.push(1);
526                    v.write(out);
527                }
528            }
529        }
530    }
531
532    fn read(buf: &[u8], pos: &mut usize) -> Option<Self> {
533        let ty = ResidueType::read(buf, pos)?;
534        let m = u64::read(buf, pos)?;
535        if m == 0 || m > MAX_MODULUS {
536            return None;
537        }
538        let mut raw = Vec::with_capacity(m as usize);
539        for _ in 0..m {
540            raw.push(match crate::exact_codec::read_byte(buf, pos)? {
541                0 => None,
542                1 => Some(T::read(buf, pos)?),
543                _ => return None,
544            });
545        }
546        Depends::from_branches(ty, m, raw)
547    }
548}
549
550#[cfg(test)]
551#[path = "../../logicaffeine_tests/unit/logicaffeine_base/limit_value/tests.rs"]
552mod tests;