1use crate::algebraic::Algebraic;
24use crate::numeric::BigInt;
25use std::fmt;
26
27pub const MAX_MODULUS: u64 = crate::exact_codec::MAX_WIRE_LEN as u64;
30
31#[derive(Clone, Debug, PartialEq, Eq)]
33pub enum ResidueError {
34 Contradiction,
36 TooFine { classes: BigInt },
38}
39
40impl ResidueError {
41 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
68pub(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#[derive(Clone, Debug, PartialEq, Eq, Hash)]
86pub struct ResidueType {
87 modulus: u64,
89 allowed: Vec<bool>,
91}
92
93impl ResidueType {
94 pub fn any() -> ResidueType {
96 ResidueType {
97 modulus: 1,
98 allowed: vec![true],
99 }
100 }
101
102 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 pub fn admits(&self, x: u64) -> bool {
131 self.allowed[(x % self.modulus) as usize]
132 }
133
134 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 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 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 pub fn residues(&self) -> Vec<u64> {
169 (0..self.modulus).filter(|&r| self.allowed[r as usize]).collect()
170 }
171
172 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#[derive(Clone, Debug, PartialEq, Eq, Hash)]
185pub struct Depends<T> {
186 ty: ResidueType,
187 modulus: u64,
188 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 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 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 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 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 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 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 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 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 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#[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 pub fn apply(&self, rule: LimitRule) -> Result<Algebraic, String> {
352 self.apply_with(rule, "⌈1/dt⌉")
353 }
354
355 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#[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 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 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 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 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 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 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 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;