1#![allow(dead_code)]
21
22use crate::{DerivationTree, InferenceRule, ProofExpr};
23
24fn leaf(e: ProofExpr) -> DerivationTree {
27 DerivationTree::leaf(e, InferenceRule::PremiseMatch)
28}
29
30fn neg(e: &ProofExpr) -> ProofExpr {
31 match e {
32 ProofExpr::Not(inner) => (**inner).clone(),
33 _ => ProofExpr::Not(Box::new(e.clone())),
34 }
35}
36
37fn falsum() -> ProofExpr {
38 ProofExpr::Atom("⊥".into())
39}
40
41fn modus_ponens(c: ProofExpr, impl_tree: DerivationTree, ante_tree: DerivationTree) -> DerivationTree {
43 DerivationTree::new(c, InferenceRule::ModusPonens, vec![impl_tree, ante_tree])
44}
45
46fn disjunction_elim(survivor: ProofExpr, or_tree: DerivationTree, neg_tree: DerivationTree) -> DerivationTree {
48 DerivationTree::new(survivor, InferenceRule::DisjunctionElim, vec![or_tree, neg_tree])
49}
50
51fn conj_intro(a: ProofExpr, b: ProofExpr, l: DerivationTree, r: DerivationTree) -> DerivationTree {
53 DerivationTree::new(ProofExpr::And(Box::new(a), Box::new(b)), InferenceRule::ConjunctionIntro, vec![l, r])
54}
55
56fn contradiction(p: DerivationTree, np: DerivationTree) -> DerivationTree {
58 DerivationTree::new(falsum(), InferenceRule::Contradiction, vec![p, np])
59}
60
61fn reductio(assumed: ProofExpr, falsum_tree: DerivationTree) -> DerivationTree {
63 let concl = ProofExpr::Not(Box::new(assumed.clone()));
64 DerivationTree::new(concl, InferenceRule::ReductioAdAbsurdum, vec![leaf(assumed), falsum_tree])
65}
66
67fn eq_sym(a: crate::ProofTerm, b: crate::ProofTerm, tree: DerivationTree) -> DerivationTree {
69 DerivationTree::new(ProofExpr::Identity(b, a), InferenceRule::EqualitySymmetry, vec![tree])
70}
71
72fn disjunction_cases(goal: ProofExpr, or_tree: DerivationTree, left: DerivationTree, right: DerivationTree) -> DerivationTree {
76 DerivationTree::new(goal, InferenceRule::DisjunctionCases, vec![or_tree, left, right])
77}
78
79fn conj_elim(conjunct: ProofExpr, and_tree: DerivationTree) -> DerivationTree {
81 DerivationTree::new(conjunct, InferenceRule::ConjunctionElim, vec![and_tree])
82}
83
84use std::collections::HashMap;
87use crate::ProofTerm;
88
89type Var = usize;
90
91#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
92struct Lit {
93 var: Var,
94 pos: bool,
95}
96
97impl Lit {
98 fn negate(self) -> Lit {
99 Lit { var: self.var, pos: !self.pos }
100 }
101}
102
103fn term_key(t: &ProofTerm) -> String {
104 t.to_string()
105}
106
107fn atom_key(e: &ProofExpr) -> Option<String> {
110 match e {
111 ProofExpr::Predicate { name, args, .. } => {
112 let parts: Vec<String> = args.iter().map(term_key).collect();
113 Some(format!("P:{}({})", name, parts.join(",")))
114 }
115 ProofExpr::Identity(a, b) => {
116 let (ka, kb) = (term_key(a), term_key(b));
117 let (lo, hi) = if ka <= kb { (ka, kb) } else { (kb, ka) };
118 Some(format!("=:{}|{}", lo, hi))
119 }
120 ProofExpr::Atom(s) => Some(format!("A:{}", s)),
121 _ => None,
122 }
123}
124
125fn is_atomish(e: &ProofExpr) -> bool {
126 matches!(
127 e,
128 ProofExpr::Predicate { .. } | ProofExpr::Identity(..) | ProofExpr::Atom(_)
129 )
130}
131
132fn is_literal(e: &ProofExpr) -> bool {
133 match e {
134 ProofExpr::Not(inner) => is_atomish(inner),
135 _ => is_atomish(e),
136 }
137}
138
139fn is_clause_disjunct(e: &ProofExpr) -> bool {
143 match e {
144 ProofExpr::And(l, r) | ProofExpr::Or(l, r) => is_clause_disjunct(l) && is_clause_disjunct(r),
145 _ => is_literal(e),
146 }
147}
148
149fn flatten_and(e: &ProofExpr, out: &mut Vec<ProofExpr>) {
150 match e {
151 ProofExpr::And(l, r) => {
152 flatten_and(l, out);
153 flatten_and(r, out);
154 }
155 _ => out.push(e.clone()),
156 }
157}
158
159fn flatten_or(e: &ProofExpr, out: &mut Vec<ProofExpr>) {
160 match e {
161 ProofExpr::Or(l, r) => {
162 flatten_or(l, out);
163 flatten_or(r, out);
164 }
165 _ => out.push(e.clone()),
166 }
167}
168
169fn intern_all(table: &mut AtomTable, e: &ProofExpr) {
172 match e {
173 ProofExpr::And(l, r) | ProofExpr::Or(l, r) | ProofExpr::Implies(l, r) | ProofExpr::Iff(l, r) => {
174 intern_all(table, l);
175 intern_all(table, r);
176 }
177 ProofExpr::Not(inner) => intern_all(table, inner),
178 _ if is_atomish(e) => {
179 table.intern(e);
180 }
181 _ => {}
182 }
183}
184
185#[derive(Default)]
186struct AtomTable {
187 by_key: HashMap<String, Var>,
188 atoms: Vec<ProofExpr>,
189}
190
191impl AtomTable {
192 fn intern(&mut self, atom: &ProofExpr) -> Option<Var> {
193 let k = atom_key(atom)?;
194 if let Some(&v) = self.by_key.get(&k) {
195 return Some(v);
196 }
197 let v = self.atoms.len();
198 self.atoms.push(atom.clone());
199 self.by_key.insert(k, v);
200 Some(v)
201 }
202
203 fn lit(&mut self, e: &ProofExpr) -> Option<Lit> {
204 match e {
205 ProofExpr::Not(inner) => Some(Lit { var: self.intern(inner)?, pos: false }),
206 _ => Some(Lit { var: self.intern(e)?, pos: true }),
207 }
208 }
209}
210
211enum ClauseSrc {
214 Bare(ProofExpr),
216 FromRule(usize),
218 Assumed(ProofExpr),
221}
222
223struct Clause {
227 or_expr: ProofExpr,
228 disjuncts: Vec<ProofExpr>,
229 lits: Option<Vec<Lit>>,
230 src: ClauseSrc,
231}
232
233enum RuleCons {
234 Lit(Lit),
235 Disj(Vec<Lit>),
236}
237
238#[derive(Clone, Copy, PartialEq, Eq)]
240enum Tri {
241 True,
242 False,
243 Live,
244}
245
246struct Rule {
247 ante: Vec<Lit>,
248 cons: RuleCons,
249 src: ProofExpr,
251}
252
253#[derive(Clone)]
256enum Reason {
257 Premise(ProofExpr),
259 Rule(usize),
261 Exclude(usize),
264 Clause(usize),
266 CompoundClause { clause: usize, disjunct: usize },
272 Assumption,
274}
275
276struct Solver {
279 table: AtomTable,
280 clauses: Vec<Clause>,
281 rules: Vec<Rule>,
282 assign: Vec<Option<bool>>,
283 reason: Vec<Option<Reason>>,
284 trail: Vec<Var>,
285 pos: Vec<usize>,
289 fired_disj: Vec<bool>,
290 deciding: std::collections::HashSet<usize>,
293 at_root: bool,
300}
301
302enum Conflict {
303 Rule(usize),
305 Clause(usize),
307}
308
309fn compile(premises: &[ProofExpr]) -> Option<Solver> {
313 let mut table = AtomTable::default();
314 let mut clauses = Vec::new();
315 let mut rules = Vec::new();
316 let mut units: Vec<(Lit, ProofExpr)> = Vec::new();
317
318 let mut flat: Vec<ProofExpr> = Vec::new();
321 for p in premises {
322 flatten_and(p, &mut flat);
323 }
324
325 for p in &flat {
330 match p {
331 ProofExpr::Or(..) => {
332 let mut ds = Vec::new();
333 flatten_or(p, &mut ds);
334 if !ds.iter().all(is_clause_disjunct) {
335 continue;
336 }
337 intern_all(&mut table, p);
338 let lits = if ds.iter().all(is_literal) {
339 ds.iter().map(|d| table.lit(d)).collect::<Option<Vec<Lit>>>()
340 } else {
341 None
342 };
343 clauses.push(Clause {
344 or_expr: p.clone(),
345 disjuncts: ds,
346 lits,
347 src: ClauseSrc::Bare(p.clone()),
348 });
349 }
350 ProofExpr::Implies(ante, cons) => {
351 let mut as_ = Vec::new();
352 flatten_and(ante, &mut as_);
353 if !as_.iter().all(is_literal) {
354 continue;
355 }
356 let cons_form = if is_literal(cons) {
357 intern_all(&mut table, p);
358 RuleCons::Lit(table.lit(cons).unwrap())
359 } else if matches!(cons.as_ref(), ProofExpr::Or(..)) {
360 let mut ds = Vec::new();
361 flatten_or(cons, &mut ds);
362 if !ds.iter().all(is_literal) {
363 continue;
364 }
365 intern_all(&mut table, p);
366 RuleCons::Disj(ds.iter().map(|d| table.lit(d).unwrap()).collect())
367 } else {
368 continue;
369 };
370 let ante_lits = as_.iter().map(|a| table.lit(a).unwrap()).collect();
371 rules.push(Rule { ante: ante_lits, cons: cons_form, src: p.clone() });
372 }
373 _ if is_literal(p) => {
374 intern_all(&mut table, p);
375 units.push((table.lit(p).unwrap(), p.clone()));
376 }
377 _ => continue,
378 }
379 }
380
381 let n = table.atoms.len();
382 let fired_disj = vec![false; rules.len()];
383 let mut solver = Solver {
384 table,
385 clauses,
386 rules,
387 assign: vec![None; n],
388 reason: vec![None; n],
389 trail: Vec::new(),
390 pos: vec![usize::MAX; n],
391 fired_disj,
392 deciding: std::collections::HashSet::new(),
393 at_root: false,
394 };
395 for (l, expr) in units {
396 let _ = solver.set(l, Reason::Premise(expr));
397 }
398 Some(solver)
399}
400
401impl Solver {
402 fn value(&self, l: Lit) -> Option<bool> {
404 self.assign[l.var].map(|b| b == l.pos)
405 }
406
407 fn var_of_atom(&self, atom: &ProofExpr) -> Option<Var> {
408 self.table.by_key.get(&atom_key(atom)?).copied()
409 }
410
411 fn lit_of(&self, e: &ProofExpr) -> Option<Lit> {
412 match e {
413 ProofExpr::Not(inner) => Some(Lit { var: self.var_of_atom(inner)?, pos: false }),
414 _ => Some(Lit { var: self.var_of_atom(e)?, pos: true }),
415 }
416 }
417
418 fn lit_expr(&self, l: Lit) -> ProofExpr {
419 let atom = self.table.atoms[l.var].clone();
420 if l.pos { atom } else { ProofExpr::Not(Box::new(atom)) }
421 }
422
423 fn set(&mut self, l: Lit, r: Reason) -> Result<(), ()> {
424 match self.assign[l.var] {
425 Some(b) => {
426 if b == l.pos {
427 Ok(())
428 } else {
429 Err(())
430 }
431 }
432 None => {
433 self.assign[l.var] = Some(l.pos);
434 self.reason[l.var] = Some(r);
435 self.pos[l.var] = self.trail.len();
436 self.trail.push(l.var);
437 Ok(())
438 }
439 }
440 }
441
442 fn propagate(&mut self) -> Result<(), Conflict> {
446 loop {
447 let mut changed = false;
448
449 for ri in 0..self.rules.len() {
450 let mut n_nonsat = 0usize;
452 let mut the_nonsat: Option<Lit> = None;
453 let mut any_false = false;
454 for i in 0..self.rules[ri].ante.len() {
455 let a = self.rules[ri].ante[i];
456 match self.value(a) {
457 Some(true) => {}
458 Some(false) => {
459 any_false = true;
460 n_nonsat += 1;
461 the_nonsat = Some(a);
462 }
463 None => {
464 n_nonsat += 1;
465 the_nonsat = Some(a);
466 }
467 }
468 }
469
470 if any_false {
472 continue;
473 }
474
475 if n_nonsat == 0 {
476 match &self.rules[ri].cons {
478 RuleCons::Lit(l) => {
479 let l = *l;
480 match self.value(l) {
481 Some(true) => {}
482 Some(false) => return Err(Conflict::Rule(ri)),
483 None => {
484 let _ = self.set(l, Reason::Rule(ri));
485 changed = true;
486 }
487 }
488 }
489 RuleCons::Disj(lits) => {
490 if !self.fired_disj[ri] {
491 let lits = lits.clone();
492 self.fired_disj[ri] = true;
493 let (_, cons_form) = Self::split_implies(&self.rules[ri].src);
494 let mut ds = Vec::new();
495 flatten_or(&cons_form, &mut ds);
496 self.clauses.push(Clause {
497 or_expr: cons_form,
498 disjuncts: ds,
499 lits: Some(lits),
500 src: ClauseSrc::FromRule(ri),
501 });
502 changed = true;
503 }
504 }
505 }
506 } else if n_nonsat == 1 {
507 if let RuleCons::Lit(l) = &self.rules[ri].cons {
510 if self.value(*l) == Some(false) {
511 let open = the_nonsat.unwrap();
512 if self.value(open).is_none() {
513 let _ = self.set(open.negate(), Reason::Exclude(ri));
514 changed = true;
515 }
516 }
517 }
518 }
519 }
520
521 for ci in 0..self.clauses.len() {
522 let (has_true, live) = self.clause_status(ci);
523 if has_true {
524 continue;
525 }
526 if live.is_empty() {
527 return Err(Conflict::Clause(ci));
528 }
529 if self.clauses[ci].lits.is_some() && live.len() == 1 {
531 if let Some(l) = self.lit_of(&self.clauses[ci].disjuncts[live[0]]) {
532 if self.set(l, Reason::Clause(ci)).is_ok() {
533 changed = true;
534 }
535 }
536 }
537 if self.at_root && self.clauses[ci].lits.is_none() && live.len() == 1 {
545 let di = live[0];
546 let mut conjs = Vec::new();
547 flatten_and(&self.clauses[ci].disjuncts[di].clone(), &mut conjs);
548 for conj in &conjs {
549 if !is_literal(conj) {
552 continue;
553 }
554 let Some(l) = self.lit_of(conj) else { continue };
555 match self.value(l) {
556 Some(true) => {}
557 Some(false) => return Err(Conflict::Clause(ci)),
558 None => {
559 let _ = self.set(l, Reason::CompoundClause { clause: ci, disjunct: di });
560 changed = true;
561 }
562 }
563 }
564 }
565 }
566
567 if !changed {
568 break;
569 }
570 }
571 Ok(())
572 }
573
574 fn prove_var(&self, v: Var) -> DerivationTree {
578 let val = self.assign[v].expect("prove_var on unassigned variable");
579 match self.reason[v].as_ref().expect("assigned variable lacks a reason") {
580 Reason::Premise(expr) => leaf(expr.clone()),
581 Reason::Assumption => leaf(self.lit_expr(Lit { var: v, pos: val })),
582 Reason::Rule(ri) => self.emit_rule_forward(*ri),
583 Reason::Exclude(ri) => self.emit_rule_exclude(*ri, v),
584 Reason::Clause(ci) => self.emit_clause_survivor(*ci, Lit { var: v, pos: val }),
585 Reason::CompoundClause { clause, disjunct } => {
586 self.emit_compound_clause(*clause, *disjunct, Lit { var: v, pos: val })
587 }
588 }
589 }
590
591 fn emit_compound_clause(&self, ci: usize, di: usize, want: Lit) -> DerivationTree {
596 let or_expr = self.clauses[ci].or_expr.clone();
597 let or_tree = self.materialize_clause(ci);
598 let survivor = self.clauses[ci].disjuncts[di].clone();
599 let before = self.pos[want.var];
600 let surv_tree = self.extract_survivor(&or_expr, or_tree, &survivor, before);
601 let proj = self.project_conjunct(&survivor, surv_tree, want);
602 align_to(proj, &self.lit_expr(want))
603 }
604
605 fn project_conjunct(&self, conj: &ProofExpr, conj_tree: DerivationTree, want: Lit) -> DerivationTree {
608 match conj {
609 ProofExpr::And(l, r) => {
610 if self.conjunct_has(l, want) {
611 let lt = conj_elim((**l).clone(), conj_tree);
612 self.project_conjunct(l, lt, want)
613 } else {
614 let rt = conj_elim((**r).clone(), conj_tree);
615 self.project_conjunct(r, rt, want)
616 }
617 }
618 _ => conj_tree,
619 }
620 }
621
622 fn conjunct_has(&self, e: &ProofExpr, want: Lit) -> bool {
624 match e {
625 ProofExpr::And(l, r) => self.conjunct_has(l, want) || self.conjunct_has(r, want),
626 _ => self.lit_of(e) == Some(want),
627 }
628 }
629
630 fn prove_lit_expr(&self, want: &ProofExpr) -> DerivationTree {
632 let lit = self.lit_of(want).expect("prove_lit_expr on an un-interned atom");
633 let base = self.prove_var(lit.var);
634 align_to(base, want)
635 }
636
637 fn split_implies(src: &ProofExpr) -> (ProofExpr, ProofExpr) {
638 match src {
639 ProofExpr::Implies(a, c) => ((**a).clone(), (**c).clone()),
640 _ => unreachable!("rule src is always an implication"),
641 }
642 }
643
644 fn prove_ante(&self, form: &ProofExpr, forced: Option<Var>) -> DerivationTree {
647 match form {
648 ProofExpr::And(l, r) => conj_intro(
649 (**l).clone(),
650 (**r).clone(),
651 self.prove_ante(l, forced),
652 self.prove_ante(r, forced),
653 ),
654 _ => {
655 if let (Some(fv), Some(lit)) = (forced, self.lit_of(form)) {
656 if lit.var == fv {
657 return leaf(form.clone());
658 }
659 }
660 self.prove_lit_expr(form)
661 }
662 }
663 }
664
665 fn emit_rule_forward(&self, ri: usize) -> DerivationTree {
666 let src = self.rules[ri].src.clone();
667 let (ante_form, cons_form) = Self::split_implies(&src);
668 let ante_proof = self.prove_ante(&ante_form, None);
669 modus_ponens(cons_form, leaf(src), ante_proof)
670 }
671
672 fn emit_rule_exclude(&self, ri: usize, forced_v: Var) -> DerivationTree {
673 let src = self.rules[ri].src.clone();
674 let (ante_form, cons_form) = Self::split_implies(&src);
675 let assumed = self.table.atoms[forced_v].clone();
676 let ante_proof = self.prove_ante(&ante_form, Some(forced_v));
677 let cons_tree = modus_ponens(cons_form.clone(), leaf(src), ante_proof);
678 let cons_lit = self.lit_of(&cons_form).expect("exclusion consequent must be a literal");
679 let neg_cons = self.prove_var(cons_lit.var);
680 let bot = make_contradiction(cons_tree, neg_cons);
683 reductio(assumed, bot)
684 }
685
686 fn materialize_clause(&self, ci: usize) -> DerivationTree {
687 match &self.clauses[ci].src {
688 ClauseSrc::Bare(or_expr) | ClauseSrc::Assumed(or_expr) => leaf(or_expr.clone()),
689 ClauseSrc::FromRule(ri) => {
690 let src = self.rules[*ri].src.clone();
691 let (ante_form, cons_form) = Self::split_implies(&src);
692 let ante_proof = self.prove_ante(&ante_form, None);
693 modus_ponens(cons_form, leaf(src), ante_proof)
694 }
695 }
696 }
697
698 fn emit_clause_survivor(&self, ci: usize, survivor: Lit) -> DerivationTree {
699 let or_tree = self.materialize_clause(ci);
700 let or_expr = or_tree.conclusion.clone();
701 let survivor_expr = self.lit_expr(survivor);
702 let before = self.pos[survivor.var];
703 self.extract_survivor(&or_expr, or_tree, &survivor_expr, before)
704 }
705
706 fn extract_survivor(&self, or_expr: &ProofExpr, or_tree: DerivationTree, target: &ProofExpr, before: usize) -> DerivationTree {
710 match or_expr {
711 ProofExpr::Or(l, r) => {
712 if r.as_ref() == target {
713 let lneg = self.prove_neg_disjunct(l, before);
714 disjunction_elim(target.clone(), or_tree, lneg)
715 } else {
716 let rneg = self.prove_neg_disjunct(r, before);
717 let l_tree = disjunction_elim((**l).clone(), or_tree, rneg);
718 self.extract_survivor(l, l_tree, target, before)
719 }
720 }
721 _ => or_tree,
722 }
723 }
724
725 fn prove_neg_disjunct(&self, e: &ProofExpr, before: usize) -> DerivationTree {
729 if is_literal(e) {
730 let lit = self.lit_of(e).expect("disjunct atom must be interned");
731 let proof = self.prove_var(lit.var);
732 align_to(proof, &neg_of(e))
733 } else {
734 let bot = self.falsum_from_disj(e, leaf(e.clone()), before);
735 reductio(e.clone(), bot)
736 }
737 }
738
739 fn falsum_from_disj(&self, e: &ProofExpr, e_tree: DerivationTree, before: usize) -> DerivationTree {
742 match e {
743 ProofExpr::Or(l, r) => {
744 let lb = self.falsum_from_disj(l, leaf((**l).clone()), before);
745 let rb = self.falsum_from_disj(r, leaf((**r).clone()), before);
746 disjunction_cases(falsum(), e_tree, lb, rb)
747 }
748 ProofExpr::And(l, r) => {
749 let key = |x: &ProofExpr| {
756 self.false_at(x, before)
757 .map(|p| (0u8, p))
758 .or_else(|| self.false_at(x, usize::MAX).map(|p| (1u8, p)))
759 };
760 let pick_left = match (key(l), key(r)) {
761 (Some(a), Some(b)) => a <= b,
762 (Some(_), None) => true,
763 (None, Some(_)) => false,
764 (None, None) => true,
765 };
766 let (conjunct, proj) = if pick_left {
767 ((**l).clone(), conj_elim((**l).clone(), e_tree))
768 } else {
769 ((**r).clone(), conj_elim((**r).clone(), e_tree))
770 };
771 self.falsum_from_disj(&conjunct, proj, before)
772 }
773 _ => {
774 let lit = self.lit_of(e).expect("disjunct atom must be interned");
775 let neg = self.prove_var(lit.var);
776 contra_align(e_tree, neg)
777 }
778 }
779 }
780
781 fn false_at(&self, e: &ProofExpr, before: usize) -> Option<usize> {
788 match e {
789 ProofExpr::And(l, r) => match (self.false_at(l, before), self.false_at(r, before)) {
790 (Some(a), Some(b)) => Some(a.min(b)),
791 (a, b) => a.or(b),
792 },
793 ProofExpr::Or(l, r) => match (self.false_at(l, before), self.false_at(r, before)) {
794 (Some(a), Some(b)) => Some(a.max(b)),
795 _ => None,
796 },
797 _ => match self.lit_of(e) {
798 Some(l) if self.value(l) == Some(false) && self.pos[l.var] < before => Some(self.pos[l.var]),
799 _ => None,
800 },
801 }
802 }
803
804 fn eval_disjunct(&self, e: &ProofExpr) -> Tri {
807 match e {
808 ProofExpr::And(l, r) => match (self.eval_disjunct(l), self.eval_disjunct(r)) {
809 (Tri::False, _) | (_, Tri::False) => Tri::False,
810 (Tri::True, Tri::True) => Tri::True,
811 _ => Tri::Live,
812 },
813 ProofExpr::Or(l, r) => match (self.eval_disjunct(l), self.eval_disjunct(r)) {
814 (Tri::True, _) | (_, Tri::True) => Tri::True,
815 (Tri::False, Tri::False) => Tri::False,
816 _ => Tri::Live,
817 },
818 _ => match self.lit_of(e).and_then(|l| self.value(l)) {
819 Some(true) => Tri::True,
820 Some(false) => Tri::False,
821 None => Tri::Live,
822 },
823 }
824 }
825
826 fn clause_status(&self, ci: usize) -> (bool, Vec<usize>) {
828 let mut has_true = false;
829 let mut live = Vec::new();
830 for (i, d) in self.clauses[ci].disjuncts.iter().enumerate() {
831 match self.eval_disjunct(d) {
832 Tri::True => has_true = true,
833 Tri::Live => live.push(i),
834 Tri::False => {}
835 }
836 }
837 (has_true, live)
838 }
839
840 fn pick_split(&self) -> Option<usize> {
844 (0..self.clauses.len())
850 .filter(|&ci| {
851 if self.deciding.contains(&ci) {
852 return false;
853 }
854 let (has_true, live) = self.clause_status(ci);
855 if has_true || live.is_empty() {
856 return false;
857 }
858 !(self.clauses[ci].lits.is_some() && live.len() < 2)
860 })
861 .min_by_key(|&ci| self.clause_status(ci).1.len())
862 }
863
864 fn derive_falsum(&mut self, depth: usize) -> Option<DerivationTree> {
867 if depth > MAX_DEPTH {
868 return None;
869 }
870 if let Err(c) = self.propagate() {
871 return Some(self.emit_conflict(c));
872 }
873 let ci = self.pick_split()?;
874 let or_expr = self.clauses[ci].or_expr.clone();
875 let or_tree = self.materialize_clause(ci);
876 self.deciding.insert(ci);
877 let result = self.split_to_falsum(&or_expr, or_tree, depth);
878 self.deciding.remove(&ci);
879 result
880 }
881
882 fn split_to_falsum(&mut self, or_expr: &ProofExpr, or_tree: DerivationTree, depth: usize) -> Option<DerivationTree> {
883 match or_expr {
884 ProofExpr::Or(l, r) => {
885 let lb = self.branch(l, depth)?;
886 let rb = self.branch(r, depth)?;
887 Some(disjunction_cases(falsum(), or_tree, lb, rb))
888 }
889 single => self.branch(single, depth),
890 }
891 }
892
893 fn branch(&mut self, disjunct: &ProofExpr, depth: usize) -> Option<DerivationTree> {
896 let trail_snap = self.trail.len();
897 let clause_snap = self.clauses.len();
898 let fired_snap = self.fired_disj.clone();
899 let result = match self.assume_disjunct(disjunct) {
900 Some(info) => Some(self.emit_conflict_info(info)),
901 None => self.derive_falsum(depth + 1),
902 };
903 self.backtrack(trail_snap, clause_snap, fired_snap);
904 result
905 }
906
907 fn assume_disjunct(&mut self, d: &ProofExpr) -> Option<ConflictInfo> {
910 match d {
911 ProofExpr::And(l, r) => self.assume_disjunct(l).or_else(|| self.assume_disjunct(r)),
912 ProofExpr::Or(..) => {
913 let mut ds = Vec::new();
914 flatten_or(d, &mut ds);
915 let lits = if ds.iter().all(is_literal) {
916 ds.iter().map(|x| self.lit_of(x)).collect::<Option<Vec<Lit>>>()
917 } else {
918 None
919 };
920 self.clauses.push(Clause {
921 or_expr: d.clone(),
922 disjuncts: ds,
923 lits,
924 src: ClauseSrc::Assumed(d.clone()),
925 });
926 None
927 }
928 _ => {
929 let lit = self.lit_of(d).expect("assumed atom must be interned");
930 if self.set(lit, Reason::Assumption).is_err() {
931 Some(ConflictInfo::Clash { assumed: d.clone() })
932 } else {
933 None
934 }
935 }
936 }
937 }
938
939 fn backtrack(&mut self, trail_snap: usize, clause_snap: usize, fired_snap: Vec<bool>) {
940 while self.trail.len() > trail_snap {
941 let v = self.trail.pop().unwrap();
942 self.assign[v] = None;
943 self.reason[v] = None;
944 }
945 self.clauses.truncate(clause_snap);
946 self.fired_disj = fired_snap;
947 }
948
949 fn emit_conflict(&self, c: Conflict) -> DerivationTree {
950 match c {
951 Conflict::Rule(ri) => {
952 let src = self.rules[ri].src.clone();
953 let (ante_form, cons_form) = Self::split_implies(&src);
954 let ante_proof = self.prove_ante(&ante_form, None);
955 let cons_tree = modus_ponens(cons_form.clone(), leaf(src), ante_proof);
956 let cons_lit = self.lit_of(&cons_form).expect("rule consequent is a literal");
957 let opp = self.prove_var(cons_lit.var);
958 make_contradiction(cons_tree, opp)
959 }
960 Conflict::Clause(ci) => {
961 let or_expr = self.clauses[ci].or_expr.clone();
962 let or_tree = self.materialize_clause(ci);
963 self.falsum_from_disj(&or_expr, or_tree, usize::MAX)
964 }
965 }
966 }
967
968 fn emit_conflict_info(&self, info: ConflictInfo) -> DerivationTree {
969 match info {
970 ConflictInfo::Clash { assumed } => {
971 let lit = self.lit_of(&assumed).expect("clashing atom must be interned");
972 let existing = self.prove_var(lit.var);
973 make_contradiction(leaf(assumed), existing)
974 }
975 ConflictInfo::Rule(ri) => self.emit_conflict(Conflict::Rule(ri)),
976 ConflictInfo::Clause(ci) => self.emit_conflict(Conflict::Clause(ci)),
977 }
978 }
979
980 fn prove_positive_by_closure(&mut self, goal: &ProofExpr) -> Option<DerivationTree> {
986 let goal_lit = self.lit_of(goal)?;
987 let mut candidates: Vec<usize> = (0..self.clauses.len())
995 .filter(|&ci| {
996 self.clauses[ci]
997 .lits
998 .as_ref()
999 .map_or(false, |ls| ls.contains(&goal_lit) && ls.len() >= 2)
1000 })
1001 .collect();
1002 candidates.sort_by_key(|&ci| self.clause_status(ci).1.len());
1003 for ci in candidates {
1004 let or_expr = self.clauses[ci].or_expr.clone();
1005 let or_tree = self.materialize_clause(ci);
1006 if let Some(tree) = self.extract_by_search(&or_expr, or_tree, goal) {
1007 return Some(tree);
1008 }
1009 }
1010 None
1011 }
1012
1013 fn extract_by_search(&mut self, or_expr: &ProofExpr, or_tree: DerivationTree, target: &ProofExpr) -> Option<DerivationTree> {
1016 debug_assert!(&or_tree.conclusion == or_expr, "extract_by_search mismatch:\n tree={:?}\n expr={:?}", or_tree.conclusion, or_expr);
1017 match or_expr {
1018 ProofExpr::Or(l, r) => {
1019 if r.as_ref() == target {
1020 let lneg = self.prove_neg_by_search(l)?;
1021 Some(disjunction_elim(target.clone(), or_tree, lneg))
1022 } else {
1023 let rneg = self.prove_neg_by_search(r)?;
1024 let l_tree = disjunction_elim((**l).clone(), or_tree, rneg);
1025 self.extract_by_search(l, l_tree, target)
1026 }
1027 }
1028 _ => Some(or_tree),
1029 }
1030 }
1031
1032 fn prove_neg_by_search(&mut self, e: &ProofExpr) -> Option<DerivationTree> {
1040 if let ProofExpr::Or(..) = e {
1041 let bot = self.disj_to_falsum(e)?;
1042 return Some(reductio(e.clone(), bot));
1043 }
1044 let lit = self.lit_of(e)?;
1045 match self.value(lit) {
1046 Some(false) => Some(align_to(self.prove_var(lit.var), &neg_of(e))),
1047 Some(true) => None,
1048 None => {
1049 let trail_snap = self.trail.len();
1050 let clause_snap = self.clauses.len();
1051 let fired_snap = self.fired_disj.clone();
1052 let _ = self.set(lit, Reason::Assumption);
1053 let bot = self.derive_falsum(0);
1054 let result = bot.map(|b| reductio(e.clone(), b));
1055 self.backtrack(trail_snap, clause_snap, fired_snap);
1056 result
1057 }
1058 }
1059 }
1060
1061 fn disj_to_falsum(&mut self, e: &ProofExpr) -> Option<DerivationTree> {
1066 if let ProofExpr::Or(l, r) = e {
1067 let lb = self.disj_to_falsum(l)?;
1068 let rb = self.disj_to_falsum(r)?;
1069 return Some(disjunction_cases(falsum(), leaf(e.clone()), lb, rb));
1070 }
1071 let neg = self.prove_neg_by_search(e)?;
1073 Some(make_contradiction(leaf(e.clone()), neg))
1074 }
1075}
1076
1077const MAX_DEPTH: usize = 64;
1078
1079enum ConflictInfo {
1080 Clash { assumed: ProofExpr },
1082 Rule(usize),
1083 Clause(usize),
1084}
1085
1086fn make_contradiction(a: DerivationTree, b: DerivationTree) -> DerivationTree {
1088 if matches!(a.conclusion, ProofExpr::Not(_)) {
1089 contra_align(b, a)
1090 } else {
1091 contra_align(a, b)
1092 }
1093}
1094
1095fn neg_of(e: &ProofExpr) -> ProofExpr {
1097 match e {
1098 ProofExpr::Not(inner) => (**inner).clone(),
1099 _ => ProofExpr::Not(Box::new(e.clone())),
1100 }
1101}
1102
1103fn align_to(tree: DerivationTree, want: &ProofExpr) -> DerivationTree {
1107 if &tree.conclusion == want {
1108 return tree;
1109 }
1110 match (&tree.conclusion, want) {
1111 (ProofExpr::Identity(a, b), ProofExpr::Identity(c, d)) if a == d && b == c => {
1112 eq_sym(a.clone(), b.clone(), tree)
1113 }
1114 (ProofExpr::Not(ti), ProofExpr::Not(wi)) => match (ti.as_ref(), wi.as_ref()) {
1115 (ProofExpr::Identity(a, b), ProofExpr::Identity(c, d)) if a == d && b == c => {
1116 let assumed = ProofExpr::Identity(c.clone(), d.clone());
1117 let flipped = eq_sym(c.clone(), d.clone(), leaf(assumed.clone()));
1118 let bot = contradiction(flipped, tree);
1119 reductio(assumed, bot)
1120 }
1121 _ => tree,
1122 },
1123 _ => tree,
1124 }
1125}
1126
1127fn contra_align(pos_tree: DerivationTree, neg_tree: DerivationTree) -> DerivationTree {
1130 let inner = match &neg_tree.conclusion {
1131 ProofExpr::Not(i) => (**i).clone(),
1132 other => other.clone(),
1133 };
1134 let aligned = align_to(pos_tree, &inner);
1135 contradiction(aligned, neg_tree)
1136}
1137
1138pub fn grid_prove(premises: &[ProofExpr], goal: &ProofExpr) -> Option<DerivationTree> {
1143 let mut solver = compile(premises)?;
1144 solver.at_root = true;
1149 let root = solver.propagate();
1150 solver.at_root = false;
1151 if root.is_err() {
1152 return None;
1153 }
1154 if let Some(lit) = solver.lit_of(goal) {
1156 if solver.value(lit) == Some(true) {
1157 return Some(align_to(solver.prove_var(lit.var), goal));
1158 }
1159 }
1160 if !matches!(goal, ProofExpr::Not(_)) && solver.lit_of(goal).is_some() {
1163 if let Some(tree) = solver.prove_positive_by_closure(goal) {
1164 return Some(align_to(tree, goal));
1165 }
1166 }
1167 if let ProofExpr::Not(inner) = goal {
1169 let x_lit = solver.lit_of(inner)?;
1170 match solver.value(x_lit) {
1171 Some(false) => return Some(align_to(solver.prove_var(x_lit.var), goal)),
1172 Some(true) => return None,
1173 None => {
1174 let _ = solver.set(x_lit, Reason::Assumption);
1175 let bot = solver.derive_falsum(0)?;
1176 return Some(reductio((**inner).clone(), bot));
1177 }
1178 }
1179 }
1180 None
1181}
1182
1183#[cfg(test)]
1184mod tests {
1185 use super::*;
1186 use crate::{ProofExpr, ProofTerm};
1187
1188 fn c(s: &str) -> ProofTerm {
1189 ProofTerm::Constant(s.to_string())
1190 }
1191 fn v(s: &str) -> ProofTerm {
1192 ProofTerm::Variable(s.to_string())
1193 }
1194 fn pred(name: &str, args: Vec<ProofTerm>) -> ProofExpr {
1195 ProofExpr::Predicate { name: name.to_string(), args, world: None }
1196 }
1197 fn imp(a: ProofExpr, b: ProofExpr) -> ProofExpr {
1198 ProofExpr::Implies(Box::new(a), Box::new(b))
1199 }
1200 fn or(a: ProofExpr, b: ProofExpr) -> ProofExpr {
1201 ProofExpr::Or(Box::new(a), Box::new(b))
1202 }
1203 fn and(a: ProofExpr, b: ProofExpr) -> ProofExpr {
1204 ProofExpr::And(Box::new(a), Box::new(b))
1205 }
1206 fn not(e: ProofExpr) -> ProofExpr {
1207 ProofExpr::Not(Box::new(e))
1208 }
1209 fn id(a: ProofTerm, b: ProofTerm) -> ProofExpr {
1210 ProofExpr::Identity(a, b)
1211 }
1212 fn forall(var: &str, body: ProofExpr) -> ProofExpr {
1213 ProofExpr::ForAll { variable: var.to_string(), body: Box::new(body) }
1214 }
1215 fn exists(var: &str, body: ProofExpr) -> ProofExpr {
1216 ProofExpr::Exists { variable: var.to_string(), body: Box::new(body) }
1217 }
1218
1219 fn verify(premises: &[ProofExpr], goal: &ProofExpr, tree: DerivationTree) -> crate::verify::VerifiedProof {
1220 crate::verify::check_derivation(premises, goal, tree)
1221 }
1222
1223 #[test]
1227 fn modus_ponens_shape_certifies() {
1228 let p = pred("P", vec![c("Obj")]);
1229 let q = pred("Q", vec![c("Obj")]);
1230 let premises = vec![p.clone(), imp(p.clone(), q.clone())];
1231 let tree = modus_ponens(q.clone(), leaf(imp(p.clone(), q.clone())), leaf(p));
1232 let r = verify(&premises, &q, tree);
1233 assert!(r.verified, "ModusPonens shape must certify; err: {:?}", r.verification_error);
1234 }
1235
1236 #[test]
1238 fn disjunction_elim_shape_certifies() {
1239 let p = pred("P", vec![c("Obj")]);
1240 let q = pred("Q", vec![c("Obj")]);
1241 let premises = vec![or(p.clone(), q.clone()), not(p.clone())];
1242 let tree = disjunction_elim(q.clone(), leaf(or(p.clone(), q.clone())), leaf(not(p)));
1243 let r = verify(&premises, &q, tree);
1244 assert!(r.verified, "DisjunctionElim shape must certify; err: {:?}", r.verification_error);
1245 }
1246
1247 #[test]
1252 fn two_value_grid_cell_handbuilt_certifies() {
1253 let trip = |t: ProofTerm| pred("Trip", vec![t]);
1254 let in_ = |t: ProofTerm, s: ProofTerm| pred("In", vec![t, s]);
1255 let fl = || c("Florida");
1256 let me_ = || c("Maine");
1257
1258 let closure_beta = imp(trip(c("Beta")), or(in_(c("Beta"), fl()), in_(c("Beta"), me_())));
1261 let amo_beta_alpha = imp(
1264 and(and(trip(c("Beta")), in_(c("Beta"), fl())), and(trip(c("Alpha")), in_(c("Alpha"), fl()))),
1265 id(c("Beta"), c("Alpha")),
1266 );
1267 let premises = vec![
1268 trip(c("Alpha")),
1269 trip(c("Beta")),
1270 not(id(c("Alpha"), c("Beta"))),
1271 closure_beta.clone(),
1272 amo_beta_alpha.clone(),
1273 in_(c("Alpha"), fl()),
1274 ];
1275 let goal = in_(c("Beta"), me_());
1276
1277 let ante = conj_intro(
1280 and(trip(c("Beta")), in_(c("Beta"), fl())),
1281 and(trip(c("Alpha")), in_(c("Alpha"), fl())),
1282 conj_intro(trip(c("Beta")), in_(c("Beta"), fl()), leaf(trip(c("Beta"))), leaf(in_(c("Beta"), fl()))),
1283 conj_intro(trip(c("Alpha")), in_(c("Alpha"), fl()), leaf(trip(c("Alpha"))), leaf(in_(c("Alpha"), fl()))),
1284 );
1285 let beta_eq_alpha = modus_ponens(id(c("Beta"), c("Alpha")), leaf(amo_beta_alpha), ante);
1286 let alpha_eq_beta = eq_sym(c("Beta"), c("Alpha"), beta_eq_alpha);
1287 let contra = contradiction(alpha_eq_beta, leaf(not(id(c("Alpha"), c("Beta")))));
1288 let neg_in_beta_fl = reductio(in_(c("Beta"), fl()), contra);
1289
1290 let beta_disj = modus_ponens(
1292 or(in_(c("Beta"), fl()), in_(c("Beta"), me_())),
1293 leaf(closure_beta),
1294 leaf(trip(c("Beta"))),
1295 );
1296 let tree = disjunction_elim(in_(c("Beta"), me_()), beta_disj, neg_in_beta_fl);
1298
1299 let r = verify(&premises, &goal, tree);
1300 assert!(r.verified, "hand-built 2-value grid cell must certify; err: {:?}", r.verification_error);
1301 }
1302
1303 #[test]
1309 fn two_value_grid_propagates_and_certifies() {
1310 let trip = |t: ProofTerm| pred("Trip", vec![t]);
1311 let in_ = |t: ProofTerm, s: ProofTerm| pred("In", vec![t, s]);
1312 let fl = || c("Florida");
1313 let me_ = || c("Maine");
1314 let closure = forall("x", imp(trip(v("x")), or(in_(v("x"), fl()), in_(v("x"), me_()))));
1315 let exactly_one_fl = forall(
1316 "x",
1317 forall(
1318 "y",
1319 imp(
1320 and(and(trip(v("x")), in_(v("x"), fl())), and(trip(v("y")), in_(v("y"), fl()))),
1321 id(v("x"), v("y")),
1322 ),
1323 ),
1324 );
1325 let premises = vec![
1326 trip(c("Alpha")),
1327 trip(c("Beta")),
1328 not(id(c("Alpha"), c("Beta"))),
1329 closure,
1330 exactly_one_fl,
1331 in_(c("Alpha"), fl()),
1332 ];
1333 let goal = in_(c("Beta"), me_());
1334 let (gp, gg) = crate::grounding::ground_problem(&premises, &goal);
1335 let tree = grid_prove(&gp, &gg).expect("2-value cell must be forced by propagation");
1336 let r = verify(&gp, &gg, tree);
1337 assert!(r.verified, "solver-emitted 2-value proof must certify; err: {:?}", r.verification_error);
1338 }
1339
1340 #[test]
1345 fn multi_category_grid_propagates_and_certifies() {
1346 let trips = ["Alpha", "Beta", "Gamma", "Delta"];
1347 let neq = |a: &str, b: &str| not(id(c(a), c(b)));
1348 let mut premises: Vec<ProofExpr> = Vec::new();
1349 let category = |rel: &str, vals: &[&str], out: &mut Vec<ProofExpr>| {
1350 for t in trips {
1351 let mut it = vals.iter().map(|val| pred(rel, vec![c(t), c(val)]));
1352 let first = it.next().unwrap();
1353 out.push(it.fold(first, or));
1354 }
1355 for val in vals {
1356 for (i, t) in trips.iter().enumerate() {
1357 for u in &trips[i + 1..] {
1358 out.push(imp(
1359 and(pred(rel, vec![c(t), c(val)]), pred(rel, vec![c(u), c(val)])),
1360 id(c(t), c(u)),
1361 ));
1362 }
1363 }
1364 }
1365 };
1366 for (i, t) in trips.iter().enumerate() {
1367 for u in &trips[i + 1..] {
1368 premises.push(neq(t, u));
1369 }
1370 }
1371 category("In", &["2001", "2002", "2003", "2004"], &mut premises);
1372 category("In", &["CT", "FL", "KY", "ME"], &mut premises);
1373 category("With", &["Bill", "Lillie", "Neal", "Yvonne"], &mut premises);
1374 premises.push(pred("In", vec![c("Alpha"), c("FL")]));
1375 premises.push(pred("In", vec![c("Beta"), c("KY")]));
1376 premises.push(pred("In", vec![c("Gamma"), c("CT")]));
1377 let goal = pred("In", vec![c("Delta"), c("ME")]);
1378 let tree = grid_prove(&premises, &goal).expect("Delta→ME must be forced by propagation");
1379 let r = verify(&premises, &goal, tree);
1380 assert!(r.verified, "solver-emitted multi-category proof must certify; err: {:?}", r.verification_error);
1381 }
1382
1383 #[test]
1390 fn of_pair_forces_cell_certifies() {
1391 let in_fl = |t: &str| pred("In", vec![c(t), c("FL")]);
1392 let amo = |t: &str, u: &str| imp(and(in_fl(t), in_fl(u)), id(c(t), c(u)));
1393 let of_pair = or(
1394 and(in_fl("A"), pred("With", vec![c("B"), c("Neal")])),
1395 and(in_fl("B"), pred("With", vec![c("A"), c("Neal")])),
1396 );
1397 let premises = vec![
1398 of_pair,
1399 amo("A", "C"),
1400 amo("B", "C"),
1401 not(id(c("A"), c("C"))),
1402 not(id(c("B"), c("C"))),
1403 not(id(c("A"), c("B"))),
1404 ];
1405 let goal = not(in_fl("C"));
1406 let tree = grid_prove(&premises, &goal).expect("of-pair must force ¬In(C,FL)");
1407 let r = verify(&premises, &goal, tree);
1408 assert!(r.verified, "solver-emitted of-pair proof must certify; err: {:?}", r.verification_error);
1409 }
1410
1411 #[test]
1416 fn compound_of_pair_resolves_certifies() {
1417 let a = pred("A", vec![c("Obj")]);
1418 let p = pred("P", vec![c("Obj")]);
1419 let q = pred("Q", vec![c("Obj")]);
1420 let bad = pred("Bad", vec![c("Obj")]);
1421 let cc = pred("C", vec![c("Obj")]);
1422 let rr = pred("R", vec![c("Obj")]);
1423 let of_pair = or(and(a, or(p.clone(), q.clone())), and(bad.clone(), cc));
1424 let premises = vec![
1425 of_pair,
1426 not(bad),
1427 imp(p, not(rr.clone())),
1428 imp(q, not(rr.clone())),
1429 ];
1430 let goal = not(rr);
1431 let tree = grid_prove(&premises, &goal).expect("compound of-pair must force ¬R");
1432 let r = verify(&premises, &goal, tree);
1433 assert!(r.verified, "solver-emitted compound of-pair proof must certify; err: {:?}", r.verification_error);
1434 }
1435
1436 fn count_rule(t: &DerivationTree, rule_dbg: &str) -> usize {
1437 let here = usize::from(format!("{:?}", t.rule).starts_with(rule_dbg));
1438 here + t.premises.iter().map(|c| count_rule(c, rule_dbg)).sum::<usize>()
1439 }
1440
1441 #[test]
1445 fn compound_clause_unit_propagates_linearly() {
1446 let o = || c("Obj");
1447 let p = pred("P", vec![o()]);
1448 let q = pred("Q", vec![o()]);
1449 let r = pred("R", vec![o()]);
1450 let s = pred("S", vec![o()]);
1451 let clause = or(and(p.clone(), q.clone()), and(r.clone(), s.clone()));
1452 let premises = vec![clause, not(r)];
1453 let tree = grid_prove(&premises, &p).expect("P must be forced by compound-clause propagation");
1454 assert_eq!(
1455 count_rule(&tree, "DisjunctionCases"),
1456 0,
1457 "compound-clause propagation must emit a LINEAR proof (no DisjunctionCases)"
1458 );
1459 let res = verify(&premises, &p, tree);
1460 assert!(res.verified, "linear compound-clause proof must certify; err: {:?}", res.verification_error);
1461 }
1462
1463 #[test]
1471 fn studio_simon_two_category_certifies() {
1472 use crate::grounding::{
1473 at_most_one_lemmas, discharge_unary_facts, domain_constants, ground_sorted, sort_domains,
1474 };
1475 let trip = |t: ProofTerm| pred("Trip", vec![t]);
1476 let in_ = |t: ProofTerm, s: ProofTerm| pred("In", vec![t, s]);
1477 let exactly_one = |val: &str| {
1478 let phi = |t: ProofTerm| and(trip(t.clone()), in_(t, c(val)));
1479 exists(
1480 "x",
1481 and(phi(v("x")), forall("y", imp(phi(v("y")), id(v("y"), v("x"))))),
1482 )
1483 };
1484 let mut premises = vec![
1485 trip(c("Alpha")),
1486 trip(c("Beta")),
1487 not(id(c("Alpha"), c("Beta"))),
1488 forall("x", imp(trip(v("x")), or(in_(v("x"), c("2003")), in_(v("x"), c("2004"))))),
1489 forall("x", imp(trip(v("x")), or(in_(v("x"), c("Florida")), in_(v("x"), c("Maine"))))),
1490 exactly_one("2003"),
1491 exactly_one("Florida"),
1492 in_(c("Alpha"), c("2003")),
1493 in_(c("Alpha"), c("Florida")),
1494 ];
1495 let goal = in_(c("Beta"), c("Maine"));
1496 premises.extend(at_most_one_lemmas(&premises));
1498 let mut all = premises.clone();
1499 all.push(goal.clone());
1500 let fallback = domain_constants(&all);
1501 let sorts = sort_domains(&premises);
1502 let grounded: Vec<ProofExpr> = premises.iter().map(|p| ground_sorted(p, &sorts, &fallback)).collect();
1503 let prepared = discharge_unary_facts(&grounded);
1504 let tree = grid_prove(&prepared, &goal).expect("studio Simon must solve by propagation");
1505 let r = verify(&prepared, &goal, tree);
1506 assert!(r.verified, "studio Simon (2-category) must certify via grid_solver; err: {:?}", r.verification_error);
1507 }
1508}