1use crate::{ProofExpr, ProofTerm};
18
19pub fn ground(e: &ProofExpr, domain: &[ProofTerm]) -> ProofExpr {
27 match e {
28 ProofExpr::ForAll { variable, body } => fold_conj(
29 domain
30 .iter()
31 .map(|c| ground(&subst(body, variable, c), domain)),
32 ),
33 ProofExpr::Exists { variable, body } => fold_disj(
34 domain
35 .iter()
36 .map(|c| ground(&subst(body, variable, c), domain)),
37 ),
38 ProofExpr::And(l, r) => {
39 ProofExpr::And(Box::new(ground(l, domain)), Box::new(ground(r, domain)))
40 }
41 ProofExpr::Or(l, r) => {
42 ProofExpr::Or(Box::new(ground(l, domain)), Box::new(ground(r, domain)))
43 }
44 ProofExpr::Implies(l, r) => {
45 ProofExpr::Implies(Box::new(ground(l, domain)), Box::new(ground(r, domain)))
46 }
47 ProofExpr::Iff(l, r) => {
48 ProofExpr::Iff(Box::new(ground(l, domain)), Box::new(ground(r, domain)))
49 }
50 ProofExpr::Not(x) => ProofExpr::Not(Box::new(ground(x, domain))),
51 ProofExpr::Temporal { operator, body } => ProofExpr::Temporal {
52 operator: operator.clone(),
53 body: Box::new(ground(body, domain)),
54 },
55 leaf => leaf.clone(),
57 }
58}
59
60fn fold_conj(mut it: impl Iterator<Item = ProofExpr>) -> ProofExpr {
62 match it.next() {
63 None => ProofExpr::Atom("True".to_string()),
64 Some(first) => it.fold(first, |acc, e| ProofExpr::And(Box::new(acc), Box::new(e))),
65 }
66}
67
68fn fold_disj(mut it: impl Iterator<Item = ProofExpr>) -> ProofExpr {
70 match it.next() {
71 None => ProofExpr::Atom("False".to_string()),
72 Some(first) => it.fold(first, |acc, e| ProofExpr::Or(Box::new(acc), Box::new(e))),
73 }
74}
75
76fn subst(e: &ProofExpr, var: &str, to: &ProofTerm) -> ProofExpr {
79 match e {
80 ProofExpr::Predicate { name, args, world } => ProofExpr::Predicate {
81 name: name.clone(),
82 args: args.iter().map(|a| subst_term(a, var, to)).collect(),
83 world: world.clone(),
84 },
85 ProofExpr::Identity(a, b) => {
86 ProofExpr::Identity(subst_term(a, var, to), subst_term(b, var, to))
87 }
88 ProofExpr::And(l, r) => {
89 ProofExpr::And(Box::new(subst(l, var, to)), Box::new(subst(r, var, to)))
90 }
91 ProofExpr::Or(l, r) => {
92 ProofExpr::Or(Box::new(subst(l, var, to)), Box::new(subst(r, var, to)))
93 }
94 ProofExpr::Implies(l, r) => {
95 ProofExpr::Implies(Box::new(subst(l, var, to)), Box::new(subst(r, var, to)))
96 }
97 ProofExpr::Iff(l, r) => {
98 ProofExpr::Iff(Box::new(subst(l, var, to)), Box::new(subst(r, var, to)))
99 }
100 ProofExpr::Not(x) => ProofExpr::Not(Box::new(subst(x, var, to))),
101 ProofExpr::ForAll { variable, body } if variable != var => ProofExpr::ForAll {
102 variable: variable.clone(),
103 body: Box::new(subst(body, var, to)),
104 },
105 ProofExpr::Exists { variable, body } if variable != var => ProofExpr::Exists {
106 variable: variable.clone(),
107 body: Box::new(subst(body, var, to)),
108 },
109 ProofExpr::Temporal { operator, body } => ProofExpr::Temporal {
110 operator: operator.clone(),
111 body: Box::new(subst(body, var, to)),
112 },
113 ProofExpr::Term(t) => ProofExpr::Term(subst_term(t, var, to)),
114 other => other.clone(),
116 }
117}
118
119pub fn domain_constants(exprs: &[ProofExpr]) -> Vec<ProofTerm> {
122 let mut out: Vec<String> = Vec::new();
123 for e in exprs {
124 collect_constants(e, &mut out);
125 }
126 out.sort();
127 out.dedup();
128 out.into_iter().map(ProofTerm::Constant).collect()
129}
130
131fn collect_constants(e: &ProofExpr, out: &mut Vec<String>) {
132 fn term(t: &ProofTerm, out: &mut Vec<String>) {
133 match t {
134 ProofTerm::Constant(s) => out.push(s.clone()),
135 ProofTerm::Function(_, args) | ProofTerm::Group(args) => {
136 args.iter().for_each(|a| term(a, out))
137 }
138 _ => {}
139 }
140 }
141 match e {
142 ProofExpr::Predicate { args, .. } => args.iter().for_each(|a| term(a, out)),
143 ProofExpr::Identity(a, b) => {
144 term(a, out);
145 term(b, out);
146 }
147 ProofExpr::And(l, r)
148 | ProofExpr::Or(l, r)
149 | ProofExpr::Implies(l, r)
150 | ProofExpr::Iff(l, r) => {
151 collect_constants(l, out);
152 collect_constants(r, out);
153 }
154 ProofExpr::Not(x) => collect_constants(x, out),
155 ProofExpr::ForAll { body, .. }
156 | ProofExpr::Exists { body, .. }
157 | ProofExpr::Temporal { body, .. } => collect_constants(body, out),
158 ProofExpr::Term(t) => term(t, out),
159 _ => {}
160 }
161}
162
163use std::collections::HashMap;
164
165pub fn sort_domains(premises: &[ProofExpr]) -> HashMap<String, Vec<ProofTerm>> {
170 fn collect(e: &ProofExpr, m: &mut HashMap<String, Vec<ProofTerm>>) {
171 match e {
172 ProofExpr::Predicate { name, args, .. } if args.len() == 1 => {
173 if let ProofTerm::Constant(_) = &args[0] {
174 let dom = m.entry(name.clone()).or_default();
175 if !dom.contains(&args[0]) {
176 dom.push(args[0].clone());
177 }
178 }
179 }
180 ProofExpr::And(l, r) => {
183 collect(l, m);
184 collect(r, m);
185 }
186 _ => {}
187 }
188 }
189 let mut m = HashMap::new();
190 for p in premises {
191 collect(p, &mut m);
192 }
193 m
194}
195
196fn guard_sort(e: &ProofExpr, var: &str) -> Option<String> {
199 match e {
200 ProofExpr::Predicate { name, args, .. }
201 if args.len() == 1 && matches!(&args[0], ProofTerm::Variable(v) | ProofTerm::BoundVarRef(v) if v == var) =>
202 {
203 Some(name.clone())
204 }
205 ProofExpr::And(l, r)
206 | ProofExpr::Or(l, r)
207 | ProofExpr::Implies(l, r)
208 | ProofExpr::Iff(l, r) => guard_sort(l, var).or_else(|| guard_sort(r, var)),
209 ProofExpr::Not(x) | ProofExpr::Temporal { body: x, .. } => guard_sort(x, var),
210 ProofExpr::ForAll { variable, body } | ProofExpr::Exists { variable, body }
215 if variable != var =>
216 {
217 guard_sort(body, var)
218 }
219 _ => None,
220 }
221}
222
223pub fn ground_sorted(
228 e: &ProofExpr,
229 sorts: &HashMap<String, Vec<ProofTerm>>,
230 fallback: &[ProofTerm],
231) -> ProofExpr {
232 let domain_for = |body: &ProofExpr, var: &str| -> Vec<ProofTerm> {
233 guard_sort(body, var)
234 .and_then(|s| sorts.get(&s).cloned())
235 .unwrap_or_else(|| fallback.to_vec())
236 };
237 match e {
238 ProofExpr::ForAll { variable, body } => {
239 let dom = domain_for(body, variable);
240 fold_conj(
241 dom.iter()
242 .map(|c| ground_sorted(&subst(body, variable, c), sorts, fallback)),
243 )
244 }
245 ProofExpr::Exists { variable, body } => {
246 let dom = domain_for(body, variable);
247 fold_disj(
248 dom.iter()
249 .map(|c| ground_sorted(&subst(body, variable, c), sorts, fallback)),
250 )
251 }
252 ProofExpr::And(l, r) => ProofExpr::And(
253 Box::new(ground_sorted(l, sorts, fallback)),
254 Box::new(ground_sorted(r, sorts, fallback)),
255 ),
256 ProofExpr::Or(l, r) => ProofExpr::Or(
257 Box::new(ground_sorted(l, sorts, fallback)),
258 Box::new(ground_sorted(r, sorts, fallback)),
259 ),
260 ProofExpr::Implies(l, r) => ProofExpr::Implies(
261 Box::new(ground_sorted(l, sorts, fallback)),
262 Box::new(ground_sorted(r, sorts, fallback)),
263 ),
264 ProofExpr::Iff(l, r) => ProofExpr::Iff(
265 Box::new(ground_sorted(l, sorts, fallback)),
266 Box::new(ground_sorted(r, sorts, fallback)),
267 ),
268 ProofExpr::Not(x) => ProofExpr::Not(Box::new(ground_sorted(x, sorts, fallback))),
269 ProofExpr::Temporal { operator, body } => ProofExpr::Temporal {
270 operator: operator.clone(),
271 body: Box::new(ground_sorted(body, sorts, fallback)),
272 },
273 leaf => leaf.clone(),
274 }
275}
276
277pub fn ground_problem_sorted(
280 premises: &[ProofExpr],
281 goal: &ProofExpr,
282) -> (Vec<ProofExpr>, ProofExpr) {
283 let mut all: Vec<ProofExpr> = premises.to_vec();
284 all.push(goal.clone());
285 let fallback = domain_constants(&all);
286 let sorts = sort_domains(premises);
287 let gp = premises
288 .iter()
289 .map(|p| ground_sorted(p, &sorts, &fallback))
290 .collect();
291 let gg = ground_sorted(goal, &sorts, &fallback);
292 (gp, gg)
293}
294
295pub fn ground_problem(
299 premises: &[ProofExpr],
300 goal: &ProofExpr,
301) -> (Vec<ProofExpr>, ProofExpr) {
302 let mut all: Vec<ProofExpr> = premises.to_vec();
303 all.push(goal.clone());
304 let domain = domain_constants(&all);
305 let grounded_premises = premises.iter().map(|p| ground(p, &domain)).collect();
306 let grounded_goal = ground(goal, &domain);
307 (grounded_premises, grounded_goal)
308}
309
310pub fn at_most_one_lemmas(premises: &[ProofExpr]) -> Vec<ProofExpr> {
321 premises.iter().filter_map(at_most_one_of).collect()
322}
323
324pub fn functionality_lemmas(premises: &[ProofExpr]) -> Vec<ProofExpr> {
334 let mut out = Vec::new();
335 for p in premises {
336 let ProofExpr::ForAll { variable, body } = p else {
337 continue;
338 };
339 let ProofExpr::Implies(_guard, cons) = body.as_ref() else {
340 continue;
341 };
342 let lits = flatten_or(cons);
343 let all_atoms_on_var = lits.len() >= 2
344 && lits.iter().all(|l| {
345 matches!(l, ProofExpr::Predicate { args, .. }
346 if args.iter().any(|a| matches!(a, ProofTerm::Variable(v) | ProofTerm::BoundVarRef(v) if v == variable)))
347 });
348 if !all_atoms_on_var {
349 continue;
350 }
351 for (i, li) in lits.iter().enumerate() {
352 for (j, lj) in lits.iter().enumerate() {
353 if i == j {
354 continue;
355 }
356 out.push(ProofExpr::ForAll {
357 variable: variable.clone(),
358 body: Box::new(ProofExpr::Implies(
359 Box::new(li.clone()),
360 Box::new(ProofExpr::Not(Box::new(lj.clone()))),
361 )),
362 });
363 }
364 }
365 }
366 out
367}
368
369pub fn column_closure_lemmas(premises: &[ProofExpr]) -> Vec<ProofExpr> {
383 let domains = sort_domains(premises);
384 let mut flat: Vec<ProofExpr> = Vec::new();
385 for p in premises {
386 flatten_and_into(p, &mut flat);
387 }
388 let mut out = Vec::new();
389 for p in &flat {
390 if at_most_one_of(p).is_none() {
393 continue;
394 }
395 let ProofExpr::Exists { variable: x, body } = p else {
396 continue;
397 };
398 let ProofExpr::And(phi_x, _uniq) = body.as_ref() else {
399 continue;
400 };
401 let Some(sort) = guard_sort(phi_x, x) else {
402 continue;
403 };
404 let Some(domain) = domains.get(&sort) else {
405 continue;
406 };
407 if domain.len() < 2 {
408 continue;
409 }
410 let value = strip_sort_guard(phi_x, &sort, x);
413 let mut disj: Option<ProofExpr> = None;
414 for r in domain {
415 let lit_r = subst(&value, x, r);
416 disj = Some(match disj {
417 None => lit_r,
418 Some(acc) => ProofExpr::Or(Box::new(acc), Box::new(lit_r)),
419 });
420 }
421 if let Some(d) = disj {
422 out.push(d);
423 }
424 }
425 out
426}
427
428fn strip_sort_guard(phi: &ProofExpr, sort: &str, var: &str) -> ProofExpr {
431 let is_guard = |e: &ProofExpr| {
432 matches!(e, ProofExpr::Predicate { name, args, .. }
433 if name == sort && args.len() == 1
434 && matches!(&args[0], ProofTerm::Variable(v) | ProofTerm::BoundVarRef(v) if v == var))
435 };
436 if let ProofExpr::And(l, r) = phi {
437 if is_guard(l) {
438 return strip_sort_guard(r, sort, var);
439 }
440 if is_guard(r) {
441 return strip_sort_guard(l, sort, var);
442 }
443 return ProofExpr::And(
444 Box::new(strip_sort_guard(l, sort, var)),
445 Box::new(strip_sort_guard(r, sort, var)),
446 );
447 }
448 phi.clone()
449}
450
451fn flatten_or(e: &ProofExpr) -> Vec<ProofExpr> {
452 match e {
453 ProofExpr::Or(l, r) => {
454 let mut v = flatten_or(l);
455 v.extend(flatten_or(r));
456 v
457 }
458 _ => vec![e.clone()],
459 }
460}
461
462pub fn discharge_unary_facts(premises: &[ProofExpr]) -> Vec<ProofExpr> {
470 let mut flat: Vec<ProofExpr> = Vec::new();
474 for p in premises {
475 flatten_and_into(p, &mut flat);
476 }
477 let mut facts: Vec<(String, String)> = Vec::new();
478 for p in &flat {
479 if let ProofExpr::Predicate { name, args, .. } = p {
480 if let [ProofTerm::Constant(c)] = args.as_slice() {
481 let key = (name.clone(), c.clone());
482 if !facts.contains(&key) {
483 facts.push(key);
484 }
485 }
486 }
487 }
488 premises.iter().map(|p| discharge(p, &facts)).collect()
489}
490
491fn is_true(e: &ProofExpr) -> bool {
492 matches!(e, ProofExpr::Atom(s) if s == "True")
493}
494
495fn is_false(e: &ProofExpr) -> bool {
496 matches!(e, ProofExpr::Atom(s) if s == "False")
497}
498
499pub fn simplify_trivial_identities(premises: &[ProofExpr]) -> Vec<ProofExpr> {
505 premises.iter().map(simplify_ident).collect()
506}
507
508fn simplify_ident(e: &ProofExpr) -> ProofExpr {
509 match e {
510 ProofExpr::Identity(a, b) if a == b => ProofExpr::Atom("True".to_string()),
511 ProofExpr::Not(inner) => {
512 let s = simplify_ident(inner);
513 if is_true(&s) {
514 ProofExpr::Atom("False".to_string())
515 } else if is_false(&s) {
516 ProofExpr::Atom("True".to_string())
517 } else {
518 ProofExpr::Not(Box::new(s))
519 }
520 }
521 ProofExpr::And(l, r) => {
522 let (l, r) = (simplify_ident(l), simplify_ident(r));
523 if is_false(&l) || is_false(&r) {
524 ProofExpr::Atom("False".to_string())
525 } else if is_true(&l) {
526 r
527 } else if is_true(&r) {
528 l
529 } else {
530 ProofExpr::And(Box::new(l), Box::new(r))
531 }
532 }
533 ProofExpr::Or(l, r) => {
534 let (l, r) = (simplify_ident(l), simplify_ident(r));
535 if is_true(&l) || is_true(&r) {
536 ProofExpr::Atom("True".to_string())
537 } else if is_false(&l) {
538 r
539 } else if is_false(&r) {
540 l
541 } else {
542 ProofExpr::Or(Box::new(l), Box::new(r))
543 }
544 }
545 ProofExpr::Implies(l, r) => {
546 let (l, r) = (simplify_ident(l), simplify_ident(r));
547 if is_false(&l) || is_true(&r) {
548 ProofExpr::Atom("True".to_string())
549 } else if is_true(&l) {
550 r
551 } else {
552 ProofExpr::Implies(Box::new(l), Box::new(r))
553 }
554 }
555 ProofExpr::ForAll { variable, body } => ProofExpr::ForAll {
556 variable: variable.clone(),
557 body: Box::new(simplify_ident(body)),
558 },
559 ProofExpr::Exists { variable, body } => ProofExpr::Exists {
560 variable: variable.clone(),
561 body: Box::new(simplify_ident(body)),
562 },
563 other => other.clone(),
564 }
565}
566
567fn discharge(e: &ProofExpr, facts: &[(String, String)]) -> ProofExpr {
568 match e {
569 ProofExpr::Predicate { name, args, .. } => {
570 if let [ProofTerm::Constant(c)] = args.as_slice() {
571 if facts.iter().any(|(n, k)| n == name && k == c) {
572 return ProofExpr::Atom("True".to_string());
573 }
574 }
575 e.clone()
576 }
577 ProofExpr::And(l, r) => {
578 let (l, r) = (discharge(l, facts), discharge(r, facts));
579 match (is_true(&l), is_true(&r)) {
580 (true, _) => r,
581 (_, true) => l,
582 _ => ProofExpr::And(Box::new(l), Box::new(r)),
583 }
584 }
585 ProofExpr::Implies(l, r) => {
586 let (l, r) = (discharge(l, facts), discharge(r, facts));
587 if is_true(&l) {
588 r
589 } else {
590 ProofExpr::Implies(Box::new(l), Box::new(r))
591 }
592 }
593 ProofExpr::Or(l, r) => {
594 ProofExpr::Or(Box::new(discharge(l, facts)), Box::new(discharge(r, facts)))
595 }
596 ProofExpr::Not(x) => ProofExpr::Not(Box::new(discharge(x, facts))),
597 ProofExpr::ForAll { variable, body } => ProofExpr::ForAll {
598 variable: variable.clone(),
599 body: Box::new(discharge(body, facts)),
600 },
601 ProofExpr::Exists { variable, body } => ProofExpr::Exists {
602 variable: variable.clone(),
603 body: Box::new(discharge(body, facts)),
604 },
605 ProofExpr::Temporal { operator, body } => ProofExpr::Temporal {
606 operator: operator.clone(),
607 body: Box::new(discharge(body, facts)),
608 },
609 other => other.clone(),
610 }
611}
612
613fn at_most_one_of(e: &ProofExpr) -> Option<ProofExpr> {
614 let ProofExpr::Exists { variable: x, body } = e else {
615 return None;
616 };
617 let ProofExpr::And(phi_x, uniq) = body.as_ref() else {
618 return None;
619 };
620 let ProofExpr::ForAll { variable: y, body: uniq_body } = uniq.as_ref() else {
621 return None;
622 };
623 let ProofExpr::Implies(_phi_y, ident) = uniq_body.as_ref() else {
625 return None;
626 };
627 let ProofExpr::Identity(l, r) = ident.as_ref() else {
628 return None;
629 };
630 let is_var = |t: &ProofTerm, name: &str| {
631 matches!(t, ProofTerm::Variable(v) | ProofTerm::BoundVarRef(v) if v == name)
632 };
633 if !((is_var(l, y) && is_var(r, x)) || (is_var(l, x) && is_var(r, y))) {
634 return None;
635 }
636 let y2 = format!("{x}_amo");
638 let phi_x = phi_x.as_ref().clone();
639 let phi_y2 = subst(&phi_x, x, &ProofTerm::Variable(y2.clone()));
640 let body = ProofExpr::Implies(
641 Box::new(ProofExpr::And(Box::new(phi_x), Box::new(phi_y2))),
642 Box::new(ProofExpr::Identity(
643 ProofTerm::Variable(x.clone()),
644 ProofTerm::Variable(y2.clone()),
645 )),
646 );
647 Some(ProofExpr::ForAll {
648 variable: x.clone(),
649 body: Box::new(ProofExpr::ForAll {
650 variable: y2,
651 body: Box::new(body),
652 }),
653 })
654}
655
656pub fn definite_property_implications(premises: &[ProofExpr]) -> Vec<ProofExpr> {
670 let row_sorts = row_sort_predicates(premises);
671 let singletons = singleton_value_signatures(premises, &row_sorts);
672 let mut flat: Vec<ProofExpr> = Vec::new();
673 for p in premises {
674 flatten_and_into(p, &mut flat);
675 }
676 let mut out = Vec::new();
677 for p in &flat {
678 let ProofExpr::Exists { variable: x, body } = p else {
679 continue;
680 };
681 if matches!(body.as_ref(), ProofExpr::Exists { .. }) || at_most_one_of(p).is_some() {
684 continue;
685 }
686 let lits = clue_literals_on(body, x, &row_sorts);
687 for (i, anchor) in lits.iter().enumerate() {
688 let Some(sig) = positive_signature(anchor) else {
689 continue;
690 };
691 if !singletons.contains(&sig) {
692 continue;
693 }
694 for (j, other) in lits.iter().enumerate() {
695 if i == j {
696 continue;
697 }
698 out.push(ProofExpr::ForAll {
699 variable: x.clone(),
700 body: Box::new(ProofExpr::Implies(Box::new(anchor.clone()), Box::new(other.clone()))),
701 });
702 }
703 }
704 }
705 out
706}
707
708fn flatten_and_into(e: &ProofExpr, out: &mut Vec<ProofExpr>) {
709 match e {
710 ProofExpr::And(l, r) => {
711 flatten_and_into(l, out);
712 flatten_and_into(r, out);
713 }
714 _ => out.push(e.clone()),
715 }
716}
717
718fn row_sort_predicates(premises: &[ProofExpr]) -> std::collections::HashSet<String> {
721 let mut flat: Vec<ProofExpr> = Vec::new();
722 for p in premises {
723 flatten_and_into(p, &mut flat);
724 }
725 let mut s = std::collections::HashSet::new();
726 for p in &flat {
727 if let ProofExpr::Predicate { name, args, .. } = p {
728 if matches!(args.as_slice(), [ProofTerm::Constant(_)]) {
729 s.insert(name.clone());
730 }
731 }
732 }
733 s
734}
735
736fn singleton_value_signatures(
739 premises: &[ProofExpr],
740 row_sorts: &std::collections::HashSet<String>,
741) -> std::collections::HashSet<(String, String)> {
742 let mut s = std::collections::HashSet::new();
743 for p in premises {
744 let ProofExpr::Exists { variable: x, body } = p else {
745 continue;
746 };
747 let ProofExpr::And(phi, uniq) = body.as_ref() else {
748 continue;
749 };
750 if !matches!(uniq.as_ref(), ProofExpr::ForAll { .. }) {
751 continue;
752 }
753 for lit in clue_literals_on(phi, x, row_sorts) {
754 if let Some(sig) = positive_signature(&lit) {
755 s.insert(sig);
756 }
757 }
758 }
759 s
760}
761
762fn positive_signature(e: &ProofExpr) -> Option<(String, String)> {
765 let ProofExpr::Predicate { name, args, .. } = e else {
766 return None;
767 };
768 match args.as_slice() {
769 [_, ProofTerm::Constant(v)] => Some((name.clone(), v.clone())),
770 [_] => Some((name.clone(), String::new())),
771 _ => None,
772 }
773}
774
775fn term_is_var(t: &ProofTerm, x: &str) -> bool {
776 matches!(t, ProofTerm::Variable(v) | ProofTerm::BoundVarRef(v) if v == x)
777}
778
779fn mentions_var(args: &[ProofTerm], x: &str) -> bool {
780 args.iter().any(|a| term_is_var(a, x))
781}
782
783fn clue_literals_on(
787 e: &ProofExpr,
788 x: &str,
789 row_sorts: &std::collections::HashSet<String>,
790) -> Vec<ProofExpr> {
791 match e {
792 ProofExpr::And(l, r) => {
793 let mut v = clue_literals_on(l, x, row_sorts);
794 v.extend(clue_literals_on(r, x, row_sorts));
795 v
796 }
797 ProofExpr::Predicate { name, args, .. } => {
798 if mentions_var(args, x) && !row_sorts.contains(name) {
799 vec![e.clone()]
800 } else {
801 vec![]
802 }
803 }
804 ProofExpr::Not(inner) => match drop_row_guards(inner, x, row_sorts) {
805 Some(lit) => vec![ProofExpr::Not(Box::new(lit))],
806 None => vec![],
807 },
808 _ => vec![],
809 }
810}
811
812fn drop_row_guards(
815 e: &ProofExpr,
816 x: &str,
817 row_sorts: &std::collections::HashSet<String>,
818) -> Option<ProofExpr> {
819 let mut lits = clue_literals_on(e, x, row_sorts);
820 if lits.len() == 1 {
821 Some(lits.pop().unwrap())
822 } else {
823 None
824 }
825}
826
827fn subst_term(t: &ProofTerm, var: &str, to: &ProofTerm) -> ProofTerm {
828 match t {
829 ProofTerm::Variable(x) if x == var => to.clone(),
830 ProofTerm::BoundVarRef(x) if x == var => to.clone(),
831 ProofTerm::Function(name, args) => {
832 ProofTerm::Function(name.clone(), args.iter().map(|a| subst_term(a, var, to)).collect())
833 }
834 ProofTerm::Group(args) => {
835 ProofTerm::Group(args.iter().map(|a| subst_term(a, var, to)).collect())
836 }
837 other => other.clone(),
838 }
839}
840
841#[cfg(test)]
842mod tests {
843 use super::*;
844
845 fn c(s: &str) -> ProofTerm {
846 ProofTerm::Constant(s.to_string())
847 }
848 fn v(s: &str) -> ProofTerm {
849 ProofTerm::Variable(s.to_string())
850 }
851 fn pred(name: &str, args: Vec<ProofTerm>) -> ProofExpr {
852 ProofExpr::Predicate {
853 name: name.to_string(),
854 args,
855 world: None,
856 }
857 }
858 fn forall(var: &str, body: ProofExpr) -> ProofExpr {
859 ProofExpr::ForAll {
860 variable: var.to_string(),
861 body: Box::new(body),
862 }
863 }
864 fn exists(var: &str, body: ProofExpr) -> ProofExpr {
865 ProofExpr::Exists {
866 variable: var.to_string(),
867 body: Box::new(body),
868 }
869 }
870
871 #[test]
877 fn definite_descriptions_become_implications() {
878 let trip = |t: ProofTerm| pred("Trip", vec![t]);
879 let in_ = |t: ProofTerm, s: &str| pred("In", vec![t, c(s)]);
880 let with = |t: ProofTerm, f: &str| pred("With", vec![t, c(f)]);
881 let hunt = |t: ProofTerm| pred("Hunt", vec![t]);
882 let exactly_one = |phi: ProofExpr, val: ProofExpr| {
884 exists(
885 "x",
886 ProofExpr::And(
887 Box::new(ProofExpr::And(Box::new(trip(v("x"))), Box::new(phi))),
888 Box::new(forall(
889 "y",
890 ProofExpr::Implies(
891 Box::new(ProofExpr::And(Box::new(trip(v("y"))), Box::new(val))),
892 Box::new(ProofExpr::Identity(v("y"), v("x"))),
893 ),
894 )),
895 ),
896 )
897 };
898 let clue2 = exists(
900 "x",
901 ProofExpr::And(
902 Box::new(ProofExpr::And(
903 Box::new(ProofExpr::And(Box::new(trip(v("x"))), Box::new(in_(v("x"), "Florida")))),
904 Box::new(forall(
905 "y",
906 ProofExpr::Implies(
907 Box::new(ProofExpr::And(Box::new(trip(v("y"))), Box::new(in_(v("y"), "Florida")))),
908 Box::new(ProofExpr::Identity(v("y"), v("x"))),
909 ),
910 )),
911 )),
912 Box::new(ProofExpr::And(Box::new(hunt(v("x"))), Box::new(trip(v("x"))))),
913 ),
914 );
915 let clue4 = exists(
917 "x",
918 ProofExpr::And(
919 Box::new(ProofExpr::And(
920 Box::new(ProofExpr::And(Box::new(trip(v("x"))), Box::new(with(v("x"), "Yvonne")))),
921 Box::new(forall(
922 "y",
923 ProofExpr::Implies(
924 Box::new(ProofExpr::And(Box::new(trip(v("y"))), Box::new(with(v("y"), "Yvonne")))),
925 Box::new(ProofExpr::Identity(v("y"), v("x"))),
926 ),
927 )),
928 )),
929 Box::new(ProofExpr::Not(Box::new(in_(v("x"), "Kentucky")))),
930 ),
931 );
932 let premises = vec![
933 trip(c("Alpha")),
934 exactly_one(in_(v("x"), "Florida"), in_(v("y"), "Florida")),
935 exactly_one(hunt(v("x")), hunt(v("y"))),
936 exactly_one(with(v("x"), "Yvonne"), with(v("y"), "Yvonne")),
937 clue2,
938 clue4,
939 ];
940 let imps = definite_property_implications(&premises);
941 let fwd = |a: ProofExpr, b: ProofExpr| ProofExpr::ForAll {
942 variable: "x".to_string(),
943 body: Box::new(ProofExpr::Implies(Box::new(a), Box::new(b))),
944 };
945 assert!(
946 imps.contains(&fwd(in_(v("x"), "Florida"), hunt(v("x")))),
947 "clue2 forward In(Florida)→Hunt missing; got: {imps:?}"
948 );
949 assert!(
950 imps.contains(&fwd(hunt(v("x")), in_(v("x"), "Florida"))),
951 "clue2 reverse Hunt→In(Florida) missing (biconditional); got: {imps:?}"
952 );
953 assert!(
954 imps.contains(&fwd(with(v("x"), "Yvonne"), ProofExpr::Not(Box::new(in_(v("x"), "Kentucky"))))),
955 "clue4 forward With(Yvonne)→¬In(Kentucky) missing; got: {imps:?}"
956 );
957 assert!(
959 !imps.iter().any(|e| matches!(e, ProofExpr::ForAll { body, .. }
960 if matches!(body.as_ref(), ProofExpr::Implies(a, _) if matches!(a.as_ref(), ProofExpr::Not(_))))),
961 "no rule may have a negative antecedent; got: {imps:?}"
962 );
963 }
964
965 #[test]
966 fn grounds_universal_to_conjunction() {
967 let e = forall("x", pred("P", vec![v("x")]));
969 let g = ground(&e, &[c("a"), c("b")]);
970 let expected = ProofExpr::And(
971 Box::new(pred("P", vec![c("a")])),
972 Box::new(pred("P", vec![c("b")])),
973 );
974 assert_eq!(g, expected);
975 }
976
977 #[test]
978 fn grounds_existential_to_disjunction() {
979 let e = exists("x", pred("P", vec![v("x")]));
981 let g = ground(&e, &[c("a"), c("b")]);
982 let expected = ProofExpr::Or(
983 Box::new(pred("P", vec![c("a")])),
984 Box::new(pred("P", vec![c("b")])),
985 );
986 assert_eq!(g, expected);
987 }
988
989 fn is_quantifier_free(e: &ProofExpr) -> bool {
990 match e {
991 ProofExpr::ForAll { .. } | ProofExpr::Exists { .. } => false,
992 ProofExpr::And(l, r)
993 | ProofExpr::Or(l, r)
994 | ProofExpr::Implies(l, r)
995 | ProofExpr::Iff(l, r) => is_quantifier_free(l) && is_quantifier_free(r),
996 ProofExpr::Not(x) | ProofExpr::Temporal { body: x, .. } => is_quantifier_free(x),
997 _ => true,
998 }
999 }
1000
1001 #[test]
1002 fn domain_is_the_named_constants() {
1003 let premises = vec![
1004 pred("Trip", vec![c("Alpha")]),
1005 ProofExpr::And(
1006 Box::new(pred("Trip", vec![c("Beta")])),
1007 Box::new(pred("In", vec![c("Beta"), c("Maine")])),
1008 ),
1009 ];
1010 let d = domain_constants(&premises);
1011 assert_eq!(d, vec![c("Alpha"), c("Beta"), c("Maine")]);
1012 }
1013
1014 #[test]
1015 fn grounding_the_exactly_one_form_leaves_no_quantifier() {
1016 let inner = ProofExpr::And(
1018 Box::new(pred("P", vec![v("x")])),
1019 Box::new(forall(
1020 "y",
1021 ProofExpr::Implies(
1022 Box::new(pred("P", vec![v("y")])),
1023 Box::new(ProofExpr::Identity(v("y"), v("x"))),
1024 ),
1025 )),
1026 );
1027 let e = exists("x", inner);
1028 let g = ground(&e, &[c("a"), c("b")]);
1029 assert!(is_quantifier_free(&g), "grounded exactly-one must be quantifier-free: {g:?}");
1030 }
1031
1032 #[test]
1033 fn kernel_does_grounded_disjunctive_syllogism() {
1034 let in_ = |t: &str, s: &str| pred("In", vec![c(t), c(s)]);
1036 let premises = vec![
1037 ProofExpr::Or(Box::new(in_("Beta", "Florida")), Box::new(in_("Beta", "Maine"))),
1038 ProofExpr::Not(Box::new(in_("Beta", "Florida"))),
1039 ];
1040 let goal = in_("Beta", "Maine");
1041 let r = crate::verify::prove_certify_check(&premises, &goal);
1042 assert!(r.verified, "grounded disjunctive syllogism; err: {:?}", r.verification_error);
1043 }
1044
1045 #[test]
1051 fn grounded_two_value_grid_proves_with_our_kernel() {
1052 let trip = |t: ProofTerm| pred("Trip", vec![t]);
1056 let in_ = |t: ProofTerm, s: ProofTerm| pred("In", vec![t, s]);
1057 let fl = || c("Florida");
1058 let me_ = || c("Maine");
1059 let closure = forall(
1061 "x",
1062 ProofExpr::Implies(
1063 Box::new(trip(v("x"))),
1064 Box::new(ProofExpr::Or(
1065 Box::new(in_(v("x"), fl())),
1066 Box::new(in_(v("x"), me_())),
1067 )),
1068 ),
1069 );
1070 let exactly_one_fl = forall(
1073 "x",
1074 forall(
1075 "y",
1076 ProofExpr::Implies(
1077 Box::new(ProofExpr::And(
1078 Box::new(ProofExpr::And(Box::new(trip(v("x"))), Box::new(in_(v("x"), fl())))),
1079 Box::new(ProofExpr::And(Box::new(trip(v("y"))), Box::new(in_(v("y"), fl())))),
1080 )),
1081 Box::new(ProofExpr::Identity(v("x"), v("y"))),
1082 ),
1083 ),
1084 );
1085 let premises = vec![
1086 trip(c("Alpha")),
1087 trip(c("Beta")),
1088 ProofExpr::Not(Box::new(ProofExpr::Identity(c("Alpha"), c("Beta")))),
1089 closure,
1090 exactly_one_fl,
1091 in_(c("Alpha"), fl()),
1092 ];
1093 let goal = in_(c("Beta"), me_());
1094 let (gp, gg) = ground_problem(&premises, &goal);
1095 let r = crate::verify::prove_certify_check(&gp, &gg);
1096 assert!(
1097 r.verified,
1098 "grounded grid must prove via our kernel; err: {:?}",
1099 r.verification_error
1100 );
1101 }
1102
1103 #[test]
1111 fn grounded_exactly_one_existential_grid_proves_with_our_kernel() {
1112 let trip = |t: ProofTerm| pred("Trip", vec![t]);
1113 let in_ = |t: ProofTerm, s: ProofTerm| pred("In", vec![t, s]);
1114 let fl = || c("Florida");
1115 let me_ = || c("Maine");
1116 let closure = forall(
1117 "x",
1118 ProofExpr::Implies(
1119 Box::new(trip(v("x"))),
1120 Box::new(ProofExpr::Or(
1121 Box::new(in_(v("x"), fl())),
1122 Box::new(in_(v("x"), me_())),
1123 )),
1124 ),
1125 );
1126 let phi = |t: ProofTerm| {
1128 ProofExpr::And(Box::new(trip(t.clone())), Box::new(in_(t, fl())))
1129 };
1130 let exactly_one_fl = exists(
1131 "x",
1132 ProofExpr::And(
1133 Box::new(phi(v("x"))),
1134 Box::new(forall(
1135 "y",
1136 ProofExpr::Implies(
1137 Box::new(phi(v("y"))),
1138 Box::new(ProofExpr::Identity(v("y"), v("x"))),
1139 ),
1140 )),
1141 ),
1142 );
1143 let premises = vec![
1144 trip(c("Alpha")),
1145 trip(c("Beta")),
1146 ProofExpr::Not(Box::new(ProofExpr::Identity(c("Alpha"), c("Beta")))),
1147 closure,
1148 exactly_one_fl,
1149 in_(c("Alpha"), fl()),
1150 ];
1151 let goal = in_(c("Beta"), me_());
1152 let mut prem = premises;
1155 prem.extend(at_most_one_lemmas(&prem));
1156 let (gp, gg) = ground_problem_sorted(&prem, &goal);
1157 let r = crate::verify::prove_certify_check(&gp, &gg);
1158 assert!(
1159 r.verified,
1160 "grounded exactly-one (∃∀) grid must prove via our kernel; err: {:?}",
1161 r.verification_error
1162 );
1163 }
1164
1165 #[test]
1166 #[test]
1173 fn multi_category_grid_cell_forced_by_propagation() {
1174 let trips = ["Alpha", "Beta", "Gamma", "Delta"];
1175 let and = |l: ProofExpr, r: ProofExpr| ProofExpr::And(Box::new(l), Box::new(r));
1176 let implies = |l: ProofExpr, r: ProofExpr| ProofExpr::Implies(Box::new(l), Box::new(r));
1177 let neq = |a: &str, b: &str| ProofExpr::Not(Box::new(ProofExpr::Identity(c(a), c(b))));
1178 let category = |rel: &str, vals: &[&str], out: &mut Vec<ProofExpr>| {
1180 for t in trips {
1181 out.push(fold_disj(vals.iter().map(|val| pred(rel, vec![c(t), c(val)]))));
1182 }
1183 for val in vals {
1184 for (i, t) in trips.iter().enumerate() {
1185 for u in &trips[i + 1..] {
1186 out.push(implies(
1187 and(pred(rel, vec![c(t), c(val)]), pred(rel, vec![c(u), c(val)])),
1188 ProofExpr::Identity(c(t), c(u)),
1189 ));
1190 }
1191 }
1192 }
1193 };
1194 let mut premises = Vec::new();
1195 for (i, t) in trips.iter().enumerate() {
1197 for u in &trips[i + 1..] {
1198 premises.push(neq(t, u));
1199 }
1200 }
1201 category("In", &["2001", "2002", "2003", "2004"], &mut premises); category("In", &["CT", "FL", "KY", "ME"], &mut premises); category("With", &["Bill", "Lillie", "Neal", "Yvonne"], &mut premises); premises.push(pred("In", vec![c("Alpha"), c("FL")]));
1206 premises.push(pred("In", vec![c("Beta"), c("KY")]));
1207 premises.push(pred("In", vec![c("Gamma"), c("CT")]));
1208 let goal = pred("In", vec![c("Delta"), c("ME")]);
1209 let r = crate::verify::prove_certify_check(&premises, &goal);
1210 assert!(
1211 r.verified,
1212 "Delta must be forced into ME by propagation (no Z3); err: {:?}",
1213 r.verification_error
1214 );
1215 }
1216
1217 #[test]
1223 fn of_pair_clue_forces_cell_by_case_analysis() {
1224 let and = |l: ProofExpr, r: ProofExpr| ProofExpr::And(Box::new(l), Box::new(r));
1225 let implies = |l: ProofExpr, r: ProofExpr| ProofExpr::Implies(Box::new(l), Box::new(r));
1226 let in_fl = |t: &str| pred("In", vec![c(t), c("FL")]);
1227 let neq = |a: &str, b: &str| ProofExpr::Not(Box::new(ProofExpr::Identity(c(a), c(b))));
1228 let amo = |t: &str, u: &str| {
1229 implies(and(in_fl(t), in_fl(u)), ProofExpr::Identity(c(t), c(u)))
1230 };
1231 let of_pair = ProofExpr::Or(
1232 Box::new(and(in_fl("A"), pred("With", vec![c("B"), c("Neal")]))),
1233 Box::new(and(in_fl("B"), pred("With", vec![c("A"), c("Neal")]))),
1234 );
1235 let premises = vec![
1236 of_pair,
1237 amo("A", "C"),
1238 amo("B", "C"),
1239 neq("A", "C"),
1240 neq("B", "C"),
1241 neq("A", "B"),
1242 ];
1243 let goal = ProofExpr::Not(Box::new(in_fl("C")));
1244 let r = crate::verify::prove_certify_check(&premises, &goal);
1245 assert!(
1246 r.verified,
1247 "the of-pair clue must force ¬In(C, FL) by case analysis (no Z3); err: {:?}",
1248 r.verification_error
1249 );
1250 }
1251
1252 #[test]
1260 fn compound_of_pair_disjunction_resolves() {
1261 let and = |l: ProofExpr, r: ProofExpr| ProofExpr::And(Box::new(l), Box::new(r));
1262 let or = |l: ProofExpr, r: ProofExpr| ProofExpr::Or(Box::new(l), Box::new(r));
1263 let implies = |l: ProofExpr, r: ProofExpr| ProofExpr::Implies(Box::new(l), Box::new(r));
1264 let not = |e: ProofExpr| ProofExpr::Not(Box::new(e));
1265 let a = pred("A", vec![c("Obj")]);
1266 let p = pred("P", vec![c("Obj")]);
1267 let q = pred("Q", vec![c("Obj")]);
1268 let bad = pred("Bad", vec![c("Obj")]);
1269 let cc = pred("C", vec![c("Obj")]);
1270 let r = pred("R", vec![c("Obj")]);
1271 let of_pair = or(and(a, or(p.clone(), q.clone())), and(bad.clone(), cc));
1272 let premises = vec![
1273 of_pair,
1274 not(bad),
1275 implies(p, not(r.clone())),
1276 implies(q, not(r.clone())),
1277 ];
1278 let goal = not(r);
1279 let res = crate::verify::prove_certify_check(&premises, &goal);
1280 assert!(
1281 res.verified,
1282 "compound of-pair must resolve by refute + decompose + case analysis; err: {:?}",
1283 res.verification_error
1284 );
1285 }
1286
1287 #[test]
1288 fn at_most_one_lemma_extracted_from_exactly_one() {
1289 let phi = |t: ProofTerm| {
1291 ProofExpr::And(
1292 Box::new(pred("Trip", vec![t.clone()])),
1293 Box::new(pred("In", vec![t, c("Florida")])),
1294 )
1295 };
1296 let exactly_one = exists(
1297 "x",
1298 ProofExpr::And(
1299 Box::new(phi(v("x"))),
1300 Box::new(forall(
1301 "y",
1302 ProofExpr::Implies(
1303 Box::new(phi(v("y"))),
1304 Box::new(ProofExpr::Identity(v("y"), v("x"))),
1305 ),
1306 )),
1307 ),
1308 );
1309 let lemmas = at_most_one_lemmas(&[exactly_one]);
1310 assert_eq!(lemmas.len(), 1, "one exactly-one ⇒ one at-most-one lemma");
1311 assert!(
1313 matches!(&lemmas[0], ProofExpr::ForAll { body, .. }
1314 if matches!(body.as_ref(), ProofExpr::ForAll { .. })),
1315 "lemma must be ∀x∀y(…); got {:?}",
1316 lemmas[0]
1317 );
1318 assert!(at_most_one_lemmas(&[pred("Trip", vec![c("Alpha")])]).is_empty());
1320 }
1321
1322 fn conjuncts(e: &ProofExpr) -> usize {
1323 match e {
1324 ProofExpr::And(l, r) => conjuncts(l) + conjuncts(r),
1325 _ => 1,
1326 }
1327 }
1328
1329 #[test]
1330 fn sort_domains_collects_declared_members() {
1331 let premises = vec![
1332 pred("Trip", vec![c("Alpha")]),
1333 ProofExpr::And(
1334 Box::new(pred("Trip", vec![c("Beta")])),
1335 Box::new(pred("Year", vec![c("2001")])),
1336 ),
1337 ];
1338 let sorts = sort_domains(&premises);
1339 assert_eq!(sorts.get("Trip"), Some(&vec![c("Alpha"), c("Beta")]));
1340 assert_eq!(sorts.get("Year"), Some(&vec![c("2001")]));
1341 }
1342
1343 #[test]
1344 fn sort_aware_grounds_over_guard_sort_only() {
1345 let e = forall(
1348 "x",
1349 ProofExpr::Implies(
1350 Box::new(pred("Trip", vec![v("x")])),
1351 Box::new(pred("In", vec![v("x"), c("Maine")])),
1352 ),
1353 );
1354 let mut sorts = std::collections::HashMap::new();
1355 sorts.insert("Trip".to_string(), vec![c("Alpha"), c("Beta")]);
1356 let fallback = vec![c("Alpha"), c("Beta"), c("Maine"), c("Florida")];
1357 let g = ground_sorted(&e, &sorts, &fallback);
1358 assert_eq!(conjuncts(&g), 2, "should ground over the 2 trips only: {g:?}");
1359 }
1360
1361 #[test]
1362 fn ground_problem_removes_all_quantifiers() {
1363 let premises = vec![
1364 pred("Trip", vec![c("Alpha")]),
1365 forall(
1366 "x",
1367 ProofExpr::Implies(
1368 Box::new(pred("Trip", vec![v("x")])),
1369 Box::new(pred("In", vec![v("x"), c("Maine")])),
1370 ),
1371 ),
1372 ];
1373 let goal = pred("In", vec![c("Alpha"), c("Maine")]);
1374 let (gp, gg) = ground_problem(&premises, &goal);
1375 assert!(
1376 gp.iter().all(is_quantifier_free) && is_quantifier_free(&gg),
1377 "ground_problem must remove every quantifier"
1378 );
1379 }
1380}