1use logicaffeine_kernel::{normalize, Context, Term};
16
17fn global(name: &str) -> Term {
18 Term::Global(name.to_string())
19}
20fn app(f: Term, x: Term) -> Term {
21 Term::App(Box::new(f), Box::new(x))
22}
23fn app2(f: Term, x: Term, y: Term) -> Term {
24 app(app(f, x), y)
25}
26fn app3(f: Term, x: Term, y: Term, z: Term) -> Term {
27 app(app2(f, x, y), z)
28}
29fn int() -> Term {
30 global("Int")
31}
32
33fn refl(t: Term) -> Term {
35 app2(global("refl"), int(), t)
36}
37
38fn eq_sym(x: Term, y: Term, proof: Term) -> Term {
40 app(
41 app(app(app(global("Eq_sym"), int()), x), y),
42 proof,
43 )
44}
45
46fn match_bin(t: &Term, op: &str) -> Option<(Term, Term)> {
48 if let Term::App(f, b) = t {
49 if let Term::App(g, a) = f.as_ref() {
50 if let Term::Global(name) = g.as_ref() {
51 if name == op {
52 return Some(((**a).clone(), (**b).clone()));
53 }
54 }
55 }
56 }
57 None
58}
59
60fn conv(ctx: &Context, a: &Term, b: &Term) -> bool {
62 normalize(ctx, a) == normalize(ctx, b)
63}
64
65pub fn prove_int_eq(ctx: &Context, lhs: &Term, rhs: &Term) -> Option<Term> {
70 let mut polys = Polynomials { atoms: Vec::new() };
74 let pl = to_poly(&mut polys, ctx, lhs);
75 let pr = to_poly(&mut polys, ctx, rhs);
76 if pl != pr {
77 return None;
78 }
79
80 if let Some(p) = prove_eq(ctx, lhs, rhs, MAX_REWRITE_DEPTH) {
82 return Some(p);
83 }
84 prove_by_normalization(ctx, lhs, rhs)
88}
89
90struct Polynomials {
98 atoms: Vec<Term>,
99}
100impl Polynomials {
101 fn atom_id(&mut self, t: &Term) -> usize {
102 if let Some(i) = self.atoms.iter().position(|a| a == t) {
103 i
104 } else {
105 self.atoms.push(t.clone());
106 self.atoms.len() - 1
107 }
108 }
109}
110
111type Mono = Vec<usize>;
113type Poly = Vec<(Mono, i64)>;
115
116fn poly_canon(mut terms: Vec<(Mono, i64)>) -> Poly {
117 for (m, _) in terms.iter_mut() {
118 m.sort_unstable();
119 }
120 terms.sort_by(|a, b| a.0.cmp(&b.0));
121 let mut out: Poly = Vec::new();
122 for (m, c) in terms {
123 if c == 0 {
124 continue;
125 }
126 if let Some(last) = out.last_mut() {
127 if last.0 == m {
128 last.1 += c;
129 if last.1 == 0 {
130 out.pop();
131 }
132 continue;
133 }
134 }
135 out.push((m, c));
136 }
137 out
138}
139
140fn poly_add(a: &Poly, b: &Poly) -> Poly {
141 let mut t = a.clone();
142 t.extend(b.iter().cloned());
143 poly_canon(t)
144}
145fn poly_mul(a: &Poly, b: &Poly) -> Poly {
146 let mut t = Vec::new();
147 for (m1, c1) in a {
148 for (m2, c2) in b {
149 let mut m = m1.clone();
150 m.extend(m2.iter().cloned());
151 t.push((m, c1 * c2));
152 }
153 }
154 poly_canon(t)
155}
156fn poly_scale(k: i64, a: &Poly) -> Poly {
157 poly_canon(a.iter().map(|(m, c)| (m.clone(), c * k)).collect())
158}
159
160fn to_poly(p: &mut Polynomials, ctx: &Context, t: &Term) -> Poly {
162 let t = normalize(ctx, t);
163 if let Term::Lit(logicaffeine_kernel::Literal::Int(n)) = t {
164 return if n == 0 { vec![] } else { vec![(vec![], n)] };
165 }
166 if let Some((a, b)) = match_bin(&t, "add") {
167 return poly_add(&to_poly(p, ctx, &a), &to_poly(p, ctx, &b));
168 }
169 if let Some((a, b)) = match_bin(&t, "mul") {
170 return poly_mul(&to_poly(p, ctx, &a), &to_poly(p, ctx, &b));
171 }
172 if let Some((a, b)) = match_bin(&t, "sub") {
173 return poly_add(&to_poly(p, ctx, &a), &poly_scale(-1, &to_poly(p, ctx, &b)));
174 }
175 let id = p.atom_id(&t);
176 vec![(vec![id], 1)]
177}
178
179fn lit_t(n: i64) -> Term {
190 Term::Lit(logicaffeine_kernel::Literal::Int(n))
191}
192fn ax1(name: &str, a: Term) -> Term {
193 app(global(name), a)
194}
195fn ax2(name: &str, a: Term, b: Term) -> Term {
196 app2(global(name), a, b)
197}
198fn ax3(name: &str, a: Term, b: Term, c: Term) -> Term {
199 app3(global(name), a, b, c)
200}
201
202fn mono_to_term(mono: &[usize], atoms: &[Term]) -> Option<Term> {
204 let mut iter = mono.iter();
205 let first = *iter.next()?;
206 let mut t = atoms[first].clone();
207 for &id in iter {
208 t = ax2("mul", t, atoms[id].clone());
209 }
210 Some(t)
211}
212fn scaled_term(mono: &[usize], coeff: i64, atoms: &[Term]) -> Term {
214 match mono_to_term(mono, atoms) {
215 None => lit_t(coeff),
216 Some(m) if coeff == 1 => m,
217 Some(m) => ax2("mul", lit_t(coeff), m),
218 }
219}
220fn reify(poly: &[(Mono, i64)], atoms: &[Term]) -> Term {
222 let mut iter = poly.iter().filter(|(_, c)| *c != 0);
226 let Some((m0, c0)) = iter.next() else {
227 return lit_t(0);
228 };
229 let mut t = scaled_term(m0, *c0, atoms);
230 for (m, c) in iter {
231 t = ax2("add", t, scaled_term(m, *c, atoms));
232 }
233 t
234}
235
236fn as_scaled_mul(c: i64, m_term: &Term) -> Term {
239 if c == 1 {
240 let mul1m = ax2("mul", lit_t(1), m_term.clone());
242 let chain = eq_trans(
243 mul1m.clone(),
244 ax2("mul", m_term.clone(), lit_t(1)),
245 m_term.clone(),
246 ax2("mul_comm", lit_t(1), m_term.clone()),
247 ax1("mul_one", m_term.clone()),
248 );
249 eq_sym(mul1m, m_term.clone(), chain)
250 } else {
251 refl(ax2("mul", lit_t(c), m_term.clone()))
252 }
253}
254
255fn rev_distrib(c1: i64, c2: i64, m_term: &Term) -> Term {
257 let big_c = ax2("add", lit_t(c1), lit_t(c2));
258 let mul_c1 = ax2("mul", lit_t(c1), m_term.clone());
259 let mul_c2 = ax2("mul", lit_t(c2), m_term.clone());
260 let s1 = ax2("mul_comm", big_c.clone(), m_term.clone());
262 let s2 = ax3("mul_distrib_add", m_term.clone(), lit_t(c1), lit_t(c2));
264 let s3 = cong2(
266 "add",
267 &ax2("mul", m_term.clone(), lit_t(c1)),
268 &mul_c1,
269 &ax2("mul", m_term.clone(), lit_t(c2)),
270 &mul_c2,
271 ax2("mul_comm", m_term.clone(), lit_t(c1)),
272 ax2("mul_comm", m_term.clone(), lit_t(c2)),
273 );
274 let forward = eq_trans(
276 ax2("mul", big_c.clone(), m_term.clone()),
277 ax2("mul", m_term.clone(), big_c.clone()),
278 ax2("add", mul_c1.clone(), mul_c2.clone()),
279 s1,
280 eq_trans(
281 ax2("mul", m_term.clone(), big_c.clone()),
282 ax2("add", ax2("mul", m_term.clone(), lit_t(c1)), ax2("mul", m_term.clone(), lit_t(c2))),
283 ax2("add", mul_c1.clone(), mul_c2.clone()),
284 s2,
285 s3,
286 ),
287 );
288 eq_sym(ax2("mul", big_c, m_term.clone()), ax2("add", mul_c1, mul_c2), forward)
289}
290
291fn combine_coeff(m: &[usize], c1: i64, c2: i64, atoms: &[Term]) -> Option<Term> {
293 let t1 = scaled_term(m, c1, atoms);
294 let t2 = scaled_term(m, c2, atoms);
295 let sum = c1 + c2;
296 let result = scaled_term(m, sum, atoms);
297 let _ = (&t1, &t2, &result);
298 match mono_to_term(m, atoms) {
299 None => Some(refl(lit_t(sum))),
301 Some(m_term) => {
302 let coerce = cong2("add", &t1, &ax2("mul", lit_t(c1), m_term.clone()),
304 &t2, &ax2("mul", lit_t(c2), m_term.clone()),
305 as_scaled_mul(c1, &m_term), as_scaled_mul(c2, &m_term));
306 let rd = rev_distrib(c1, c2, &m_term);
307 let coerced = ax2("add", ax2("mul", lit_t(c1), m_term.clone()), ax2("mul", lit_t(c2), m_term.clone()));
308 if sum != 1 {
309 Some(eq_trans(ax2("add", t1, t2), coerced, result, coerce, rd))
310 } else {
311 let mul1m = ax2("mul", lit_t(1), m_term.clone());
315 let to_bare = eq_trans(
316 mul1m.clone(),
317 ax2("mul", m_term.clone(), lit_t(1)),
318 m_term.clone(),
319 ax2("mul_comm", lit_t(1), m_term.clone()),
320 ax1("mul_one", m_term.clone()),
321 );
322 let inner = eq_trans(coerced.clone(), mul1m, result.clone(), rd, to_bare);
323 Some(eq_trans(ax2("add", t1, t2), coerced, result, coerce, inner))
324 }
325 }
326 }
327}
328
329fn swap_top(x: Term, y: Term, z: Term) -> Term {
331 let s1 = ax3("add_assoc", x.clone(), y.clone(), z.clone());
333 let s2 = cong2(
334 "add",
335 &x,
336 &x,
337 &ax2("add", y.clone(), z.clone()),
338 &ax2("add", z.clone(), y.clone()),
339 refl(x.clone()),
340 ax2("add_comm", y.clone(), z.clone()),
341 );
342 let s3 = eq_sym(
343 ax2("add", ax2("add", x.clone(), z.clone()), y.clone()),
344 ax2("add", x.clone(), ax2("add", z.clone(), y.clone())),
345 ax3("add_assoc", x.clone(), z.clone(), y.clone()),
346 );
347 eq_trans(
348 ax2("add", ax2("add", x.clone(), y.clone()), z.clone()),
349 ax2("add", x.clone(), ax2("add", y.clone(), z.clone())),
350 ax2("add", ax2("add", x.clone(), z.clone()), y.clone()),
351 s1,
352 eq_trans(
353 ax2("add", x.clone(), ax2("add", y.clone(), z.clone())),
354 ax2("add", x.clone(), ax2("add", z.clone(), y.clone())),
355 ax2("add", ax2("add", x.clone(), z.clone()), y),
356 s2,
357 s3,
358 ),
359 )
360}
361
362fn merge_term(atoms: &[Term], p: &[(Mono, i64)], m: &[usize], c: i64) -> Option<(Poly, Term)> {
365 let st = scaled_term(m, c, atoms);
366 if p.is_empty() {
367 let proof = eq_trans(
369 ax2("add", lit_t(0), st.clone()),
370 ax2("add", st.clone(), lit_t(0)),
371 st.clone(),
372 ax2("add_comm", lit_t(0), st.clone()),
373 ax1("add_zero", st.clone()),
374 );
375 return Some((vec![(m.to_vec(), c)], proof));
376 }
377 let (ml, cl) = p.last().unwrap().clone();
378 let init = &p[..p.len() - 1];
379 let last_t = scaled_term(&ml, cl, atoms);
380 let reify_p = reify(p, atoms);
381
382 use std::cmp::Ordering;
383 match m.to_vec().cmp(&ml) {
384 Ordering::Greater => {
385 let mut res = p.to_vec();
387 res.push((m.to_vec(), c));
388 Some((res, refl(ax2("add", reify_p, st))))
389 }
390 Ordering::Equal => {
391 if cl + c == 0 {
392 let cc = combine_coeff(&ml, cl, c, atoms)?;
396 let cancel = match mono_to_term(&ml, atoms) {
397 None => cc, Some(m_term) => eq_trans(
399 ax2("add", last_t.clone(), st.clone()),
400 ax2("mul", lit_t(0), m_term.clone()),
401 lit_t(0),
402 cc,
403 eq_trans(
404 ax2("mul", lit_t(0), m_term.clone()),
405 ax2("mul", m_term.clone(), lit_t(0)),
406 lit_t(0),
407 ax2("mul_comm", lit_t(0), m_term.clone()),
408 ax1("mul_zero", m_term),
409 ),
410 ),
411 };
412 if init.is_empty() {
414 return Some((vec![], cancel));
415 }
416 let ri = reify(init, atoms);
417 let assoc = ax3("add_assoc", ri.clone(), last_t.clone(), st.clone());
418 let cong = cong2(
419 "add",
420 &ri,
421 &ri,
422 &ax2("add", last_t.clone(), st.clone()),
423 &lit_t(0),
424 refl(ri.clone()),
425 cancel,
426 );
427 let azero = ax1("add_zero", ri.clone());
428 let proof = eq_trans(
429 ax2("add", ax2("add", ri.clone(), last_t.clone()), st.clone()),
430 ax2("add", ri.clone(), ax2("add", last_t.clone(), st.clone())),
431 ri.clone(),
432 assoc,
433 eq_trans(
434 ax2("add", ri.clone(), ax2("add", last_t.clone(), st.clone())),
435 ax2("add", ri.clone(), lit_t(0)),
436 ri.clone(),
437 cong,
438 azero,
439 ),
440 );
441 return Some((init.to_vec(), proof));
442 }
443 let cc = combine_coeff(&ml, cl, c, atoms)?; let combined = scaled_term(&ml, cl + c, atoms);
445 if init.is_empty() {
446 Some((vec![(ml, cl + c)], cc))
447 } else {
448 let ri = reify(init, atoms);
449 let assoc = ax3("add_assoc", ri.clone(), last_t.clone(), st.clone());
450 let cong = cong2(
451 "add",
452 &ri,
453 &ri,
454 &ax2("add", last_t.clone(), st.clone()),
455 &combined,
456 refl(ri.clone()),
457 cc,
458 );
459 let proof = eq_trans(
460 ax2("add", ax2("add", ri.clone(), last_t), st),
461 ax2("add", ri.clone(), ax2("add", scaled_term(&ml, cl, atoms), scaled_term(m, c, atoms))),
462 ax2("add", ri.clone(), combined),
463 assoc,
464 cong,
465 );
466 let mut res = init.to_vec();
467 res.push((ml, cl + c));
468 Some((res, proof))
469 }
470 }
471 Ordering::Less => {
472 if init.is_empty() {
473 let mut res = vec![(m.to_vec(), c)];
475 res.push((ml, cl));
476 Some((res, ax2("add_comm", last_t, st)))
477 } else {
478 let ri = reify(init, atoms);
479 let swap = swap_top(ri.clone(), last_t.clone(), st.clone());
480 let (init2, inner) = merge_term(atoms, init, m, c)?; let ri2 = reify(&init2, atoms);
482 let cong = cong2(
483 "add",
484 &ax2("add", ri.clone(), st.clone()),
485 &ri2,
486 &last_t,
487 &last_t,
488 inner,
489 refl(last_t.clone()),
490 );
491 let proof = eq_trans(
492 ax2("add", ax2("add", ri.clone(), last_t.clone()), st.clone()),
493 ax2("add", ax2("add", ri, st.clone()), last_t.clone()),
494 ax2("add", ri2.clone(), last_t.clone()),
495 swap,
496 cong,
497 );
498 let proof = if init2.is_empty() {
502 let zfix = eq_trans(
503 ax2("add", lit_t(0), last_t.clone()),
504 ax2("add", last_t.clone(), lit_t(0)),
505 last_t.clone(),
506 ax2("add_comm", lit_t(0), last_t.clone()),
507 ax1("add_zero", last_t.clone()),
508 );
509 eq_trans(
510 ax2("add", ax2("add", reify(init, atoms), last_t.clone()), st.clone()),
511 ax2("add", ri2, last_t.clone()),
512 last_t.clone(),
513 proof,
514 zfix,
515 )
516 } else {
517 proof
518 };
519 let mut res = init2;
520 res.push((ml, cl));
521 Some((res, proof))
522 }
523 }
524 }
525}
526
527fn merge_canonical(atoms: &[Term], pa: &[(Mono, i64)], pb: &[(Mono, i64)]) -> Option<(Poly, Term)> {
530 let ra = reify(pa, atoms);
531 if pb.is_empty() {
532 return Some((pa.to_vec(), ax1("add_zero", ra)));
534 }
535 if pb.len() == 1 {
536 let (m, c) = &pb[0];
537 return merge_term(atoms, pa, m, *c);
538 }
539 let (ml, cl) = pb.last().unwrap().clone();
540 let pb_init = &pb[..pb.len() - 1];
541 let rbi = reify(pb_init, atoms);
542 let slast = scaled_term(&ml, cl, atoms);
543 let assoc_sym = eq_sym(
545 ax2("add", ax2("add", ra.clone(), rbi.clone()), slast.clone()),
546 ax2("add", ra.clone(), ax2("add", rbi.clone(), slast.clone())),
547 ax3("add_assoc", ra.clone(), rbi.clone(), slast.clone()),
548 );
549 let (m1, p1) = merge_canonical(atoms, pa, pb_init)?; let rm1 = reify(&m1, atoms);
551 let cong = cong2(
552 "add",
553 &ax2("add", ra.clone(), rbi.clone()),
554 &rm1,
555 &slast,
556 &slast,
557 p1,
558 refl(slast.clone()),
559 );
560 let (m2, p2) = merge_term(atoms, &m1, &ml, cl)?; let rm2 = reify(&m2, atoms);
562 let proof = eq_trans(
563 ax2("add", ra.clone(), ax2("add", rbi.clone(), slast.clone())),
564 ax2("add", ax2("add", ra, rbi), slast.clone()),
565 rm2,
566 assoc_sym,
567 eq_trans(
568 ax2("add", ax2("add", reify(pa, atoms), reify(pb_init, atoms)), slast.clone()),
569 ax2("add", rm1, slast),
570 reify(&m2, atoms),
571 cong,
572 p2,
573 ),
574 );
575 Some((m2, proof))
576}
577
578fn dist_mul(ctx: &Context, polys: &mut Polynomials, ca: &Term, cb: &Term) -> Option<(Poly, Term)> {
581 if let Some((cb1, cb2)) = match_bin(cb, "add") {
582 let distrib = ax3("mul_distrib_add", ca.clone(), cb1.clone(), cb2.clone());
584 let (pp1, d1) = dist_mul(ctx, polys, ca, &cb1)?;
585 let (pp2, d2) = dist_mul(ctx, polys, ca, &cb2)?;
586 let rp1 = reify(&pp1, &polys.atoms);
587 let rp2 = reify(&pp2, &polys.atoms);
588 let cong = cong2(
589 "add",
590 &ax2("mul", ca.clone(), cb1.clone()),
591 &rp1,
592 &ax2("mul", ca.clone(), cb2.clone()),
593 &rp2,
594 d1,
595 d2,
596 );
597 let (pm, mproof) = merge_canonical(&polys.atoms, &pp1, &pp2)?;
598 let rpm = reify(&pm, &polys.atoms);
599 let proof = eq_trans(
600 ax2("mul", ca.clone(), cb.clone()),
601 ax2("add", ax2("mul", ca.clone(), cb1.clone()), ax2("mul", ca.clone(), cb2.clone())),
602 rpm,
603 distrib,
604 eq_trans(
605 ax2("add", ax2("mul", ca.clone(), cb1), ax2("mul", ca.clone(), cb2)),
606 ax2("add", rp1, rp2),
607 reify(&pm, &polys.atoms),
608 cong,
609 mproof,
610 ),
611 );
612 return Some((pm, proof));
613 }
614 if let Some((_ca1, _ca2)) = match_bin(ca, "add") {
615 let comm = ax2("mul_comm", ca.clone(), cb.clone());
617 let (pm, inner) = dist_mul(ctx, polys, cb, ca)?; let rpm = reify(&pm, &polys.atoms);
619 return Some((
620 pm,
621 eq_trans(ax2("mul", ca.clone(), cb.clone()), ax2("mul", cb.clone(), ca.clone()), rpm, comm, inner),
622 ));
623 }
624 let prod = ax2("mul", ca.clone(), cb.clone());
626 let pp = to_poly(polys, ctx, &prod);
627 let c = reify(&pp, &polys.atoms);
628 let proof = prove_eq(ctx, &prod, &c, MAX_REWRITE_DEPTH)?;
629 Some((pp, proof))
630}
631
632fn norm(ctx: &Context, polys: &mut Polynomials, t: &Term) -> Option<(Term, Term)> {
634 if let Some((a, b)) = match_bin(t, "add") {
635 let (ca, pa) = norm(ctx, polys, &a)?;
636 let (cb, pb) = norm(ctx, polys, &b)?;
637 let pa_poly = to_poly(polys, ctx, &a);
638 let pb_poly = to_poly(polys, ctx, &b);
639 let cong = cong2("add", &a, &ca, &b, &cb, pa, pb); let (merged, merge) = merge_canonical(&polys.atoms, &pa_poly, &pb_poly)?;
641 let c = reify(&merged, &polys.atoms);
642 return Some((c.clone(), eq_trans(t.clone(), ax2("add", ca, cb), c, cong, merge)));
643 }
644 if let Some((a, b)) = match_bin(t, "mul") {
645 let (ca, pa) = norm(ctx, polys, &a)?;
646 let (cb, pb) = norm(ctx, polys, &b)?;
647 let cong = cong2("mul", &a, &ca, &b, &cb, pa, pb); let (pm, dproof) = dist_mul(ctx, polys, &ca, &cb)?;
649 let c = reify(&pm, &polys.atoms);
650 return Some((c.clone(), eq_trans(t.clone(), ax2("mul", ca, cb), c, cong, dproof)));
651 }
652 let c = reify(&to_poly(polys, ctx, t), &polys.atoms);
655 let proof = prove_eq(ctx, t, &c, MAX_REWRITE_DEPTH)?;
656 Some((c, proof))
657}
658
659fn prove_by_normalization(ctx: &Context, lhs: &Term, rhs: &Term) -> Option<Term> {
661 let mut polys = Polynomials { atoms: Vec::new() };
662 let (cl, pl) = norm(ctx, &mut polys, lhs)?; let (cr, pr) = norm(ctx, &mut polys, rhs)?; if cl != cr {
666 return None;
667 }
668 Some(eq_trans(lhs.clone(), cl, rhs.clone(), pl, eq_sym(rhs.clone(), cr, pr)))
670}
671
672const MAX_REWRITE_DEPTH: u32 = 6;
676
677fn prove_eq(ctx: &Context, lhs: &Term, rhs: &Term, depth: u32) -> Option<Term> {
678 let nlhs = normalize(ctx, lhs);
681 let nrhs = normalize(ctx, rhs);
682 if nlhs == nrhs {
683 return Some(refl(nlhs));
684 }
685
686 if let Some(p) = match_axiom(ctx, lhs, rhs) {
688 return Some(p);
689 }
690 if let Some(p) = match_axiom(ctx, rhs, lhs) {
691 return Some(eq_sym(rhs.clone(), lhs.clone(), p));
693 }
694
695 for op in ["add", "mul", "sub"] {
698 if let (Some((la, lb)), Some((ra, rb))) = (match_bin(lhs, op), match_bin(rhs, op)) {
699 if let (Some(pa), Some(pb)) =
700 (prove_eq(ctx, &la, &ra, depth), prove_eq(ctx, &lb, &rb, depth))
701 {
702 return Some(cong2(op, &la, &ra, &lb, &rb, pa, pb));
703 }
704 }
705 }
706
707 if depth > 0 {
711 for (mid, p_lhs_mid) in forward_rewrites(lhs) {
712 if let Some(p_mid_rhs) = prove_eq(ctx, &mid, rhs, depth - 1) {
713 return Some(eq_trans(lhs.clone(), mid, rhs.clone(), p_lhs_mid, p_mid_rhs));
714 }
715 }
716 }
717
718 None
719}
720
721fn eq_trans(x: Term, y: Term, z: Term, p1: Term, p2: Term) -> Term {
723 app(
724 app(app(app(app(app(global("Eq_trans"), int()), x), y), z), p1),
725 p2,
726 )
727}
728
729fn forward_rewrites(l: &Term) -> Vec<(Term, Term)> {
732 let g = global;
733 let mut out = Vec::new();
734 if let Some((a, b)) = match_bin(l, "add") {
736 out.push((
737 app2(g("add"), b.clone(), a.clone()),
738 app2(g("add_comm"), a.clone(), b.clone()),
739 ));
740 if let Some((a2, b2)) = match_bin(&a, "add") {
742 let c = b.clone();
743 out.push((
744 app2(g("add"), a2.clone(), app2(g("add"), b2.clone(), c.clone())),
745 app3(g("add_assoc"), a2.clone(), b2.clone(), c.clone()),
746 ));
747 }
748 if let Some((b2, c2)) = match_bin(&b, "add") {
750 let lhs_a = app2(g("add"), app2(g("add"), a.clone(), b2.clone()), c2.clone());
751 let rhs_a = app2(g("add"), a.clone(), app2(g("add"), b2.clone(), c2.clone()));
752 out.push((
753 lhs_a.clone(),
754 eq_sym(lhs_a, rhs_a, app3(g("add_assoc"), a.clone(), b2.clone(), c2.clone())),
755 ));
756 }
757 }
758 if let Some((a, b)) = match_bin(l, "mul") {
760 out.push((
761 app2(g("mul"), b.clone(), a.clone()),
762 app2(g("mul_comm"), a.clone(), b.clone()),
763 ));
764 if let Some((a2, b2)) = match_bin(&a, "mul") {
766 let c = b.clone();
767 out.push((
768 app2(g("mul"), a2.clone(), app2(g("mul"), b2.clone(), c.clone())),
769 app3(g("mul_assoc"), a2.clone(), b2.clone(), c.clone()),
770 ));
771 }
772 if let Some((b2, c2)) = match_bin(&b, "add") {
774 out.push((
775 app2(g("add"), app2(g("mul"), a.clone(), b2.clone()), app2(g("mul"), a.clone(), c2.clone())),
776 app3(g("mul_distrib_add"), a.clone(), b2.clone(), c2.clone()),
777 ));
778 }
779 }
780 out
781}
782
783fn eq_int_term(l: Term, r: Term) -> Term {
785 app(app(app(global("Eq"), int()), l), r)
786}
787
788fn eq_rec(x: Term, motive: Term, base: Term, y: Term, eqp: Term) -> Term {
790 app(
791 app(app(app(app(app(global("Eq_rec"), int()), x), motive), base), y),
792 eqp,
793 )
794}
795
796fn lam_int(body: Term) -> Term {
798 Term::Lambda {
799 param: "__w".to_string(),
800 param_type: Box::new(int()),
801 body: Box::new(body),
802 }
803}
804
805fn cong2(op: &str, a: &Term, a2: &Term, b: &Term, b2: &Term, pa: Term, pb: Term) -> Term {
808 let opab = app2(global(op), a.clone(), b.clone());
809 let w = Term::Var("__w".to_string());
810
811 let p1 = lam_int(eq_int_term(opab.clone(), app2(global(op), w.clone(), b.clone())));
814 let step1 = eq_rec(a.clone(), p1, refl(opab.clone()), a2.clone(), pa);
815
816 let p2 = lam_int(eq_int_term(opab.clone(), app2(global(op), a2.clone(), w)));
819 eq_rec(b.clone(), p2, step1, b2.clone(), pb)
820}
821
822fn match_axiom(ctx: &Context, l: &Term, r: &Term) -> Option<Term> {
824 if let (Some((la, lb)), Some((ra, rb))) = (match_bin(l, "add"), match_bin(r, "add")) {
826 if conv(ctx, &la, &rb) && conv(ctx, &lb, &ra) {
827 return Some(app2(global("add_comm"), la, lb));
828 }
829 }
830 if let (Some((la, lb)), Some((ra, rb))) = (match_bin(l, "mul"), match_bin(r, "mul")) {
832 if conv(ctx, &la, &rb) && conv(ctx, &lb, &ra) {
833 return Some(app2(global("mul_comm"), la, lb));
834 }
835 }
836 if let Some((lab, lc)) = match_bin(l, "add") {
838 if let Some((la, lb)) = match_bin(&lab, "add") {
839 if let Some((ra, rbc)) = match_bin(r, "add") {
840 if let Some((rb, rc)) = match_bin(&rbc, "add") {
841 if conv(ctx, &la, &ra) && conv(ctx, &lb, &rb) && conv(ctx, &lc, &rc) {
842 return Some(app3(global("add_assoc"), la, lb, lc));
843 }
844 }
845 }
846 }
847 }
848 if let Some((lab, lc)) = match_bin(l, "mul") {
850 if let Some((la, lb)) = match_bin(&lab, "mul") {
851 if let Some((ra, rbc)) = match_bin(r, "mul") {
852 if let Some((rb, rc)) = match_bin(&rbc, "mul") {
853 if conv(ctx, &la, &ra) && conv(ctx, &lb, &rb) && conv(ctx, &lc, &rc) {
854 return Some(app3(global("mul_assoc"), la, lb, lc));
855 }
856 }
857 }
858 }
859 }
860 if let Some((la, lb)) = match_bin(l, "add") {
862 if conv(ctx, &lb, &Term::Lit(logicaffeine_kernel::Literal::Int(0))) && conv(ctx, &la, r) {
863 return Some(app(global("add_zero"), la));
864 }
865 }
866 if let Some((la, lb)) = match_bin(l, "mul") {
868 if conv(ctx, &lb, &Term::Lit(logicaffeine_kernel::Literal::Int(1))) && conv(ctx, &la, r) {
869 return Some(app(global("mul_one"), la));
870 }
871 }
872 if let Some((a, bc)) = match_bin(l, "mul") {
874 if let Some((b, c)) = match_bin(&bc, "add") {
875 if let Some((rab, rac)) = match_bin(r, "add") {
876 if let (Some((ra1, rb1)), Some((ra2, rc1))) =
877 (match_bin(&rab, "mul"), match_bin(&rac, "mul"))
878 {
879 if conv(ctx, &a, &ra1)
880 && conv(ctx, &a, &ra2)
881 && conv(ctx, &b, &rb1)
882 && conv(ctx, &c, &rc1)
883 {
884 return Some(app3(global("mul_distrib_add"), a, b, c));
885 }
886 }
887 }
888 }
889 }
890 None
891}
892
893#[cfg(test)]
894mod tests {
895 use super::*;
896 use logicaffeine_kernel::{infer_type, prelude::StandardLibrary};
897
898 fn ctx() -> Context {
899 let mut c = Context::new();
900 StandardLibrary::register(&mut c);
901 c.add_declaration("x", int());
902 c.add_declaration("y", int());
903 c
904 }
905
906 fn assert_certifies(ctx: &Context, lhs: &Term, rhs: &Term) {
908 let proof = prove_int_eq(ctx, lhs, rhs)
909 .unwrap_or_else(|| panic!("oracle found no proof for {lhs:?} = {rhs:?}"));
910 let ty = infer_type(ctx, &proof)
911 .unwrap_or_else(|e| panic!("kernel rejected the proof for {lhs:?} = {rhs:?}: {e:?}"));
912 let want = eq_int_term(lhs.clone(), rhs.clone());
913 assert!(
914 conv(ctx, &ty, &want),
915 "proof types as {ty:?}, wanted Eq Int {lhs:?} {rhs:?}"
916 );
917 }
918
919 fn add_t(a: Term, b: Term) -> Term {
920 ax2("add", a, b)
921 }
922 fn mul_t(a: Term, b: Term) -> Term {
923 ax2("mul", a, b)
924 }
925
926 #[test]
927 fn coefficients_summing_to_one_recombine() {
928 let ctx = ctx();
931 let x = global("x");
932 assert_certifies(&ctx, &add_t(mul_t(lit_t(2), x.clone()), mul_t(lit_t(-1), x.clone())), &x);
933 assert_certifies(&ctx, &add_t(mul_t(lit_t(-1), x.clone()), mul_t(lit_t(2), x.clone())), &x);
934 assert_certifies(
935 &ctx,
936 &add_t(mul_t(lit_t(3), x.clone()), mul_t(lit_t(-2), x.clone())),
937 &x,
938 );
939 }
940
941 #[test]
942 fn farkas_shape_big_l_certifies() {
943 let ctx = ctx();
945 let big_l = add_t(mul_t(lit_t(1), lit_t(0)), mul_t(lit_t(1), lit_t(0)));
946 assert_certifies(&ctx, &big_l, &lit_t(0));
947 }
948
949 #[test]
950 fn farkas_shape_double_constant_big_r_certifies() {
951 let ctx = ctx();
956 let x = global("x");
957 let y = global("y");
958 let l1 = add_t(x.clone(), lit_t(1));
959 let r1 = y.clone();
960 let l2 = add_t(y.clone(), lit_t(1));
961 let r2 = add_t(x.clone(), lit_t(1));
962 let diff1 = add_t(r1, mul_t(lit_t(-1), l1));
963 let diff2 = add_t(r2, mul_t(lit_t(-1), l2));
964 let big_r = add_t(mul_t(lit_t(1), diff1), mul_t(lit_t(1), diff2));
965 assert_certifies(&ctx, &big_r, &lit_t(-1));
966 }
967}