1use crate::expr::TLExpr;
74use crate::term::Term;
75use crate::unification::Substitution;
76use serde::{Deserialize, Serialize};
77use std::collections::HashSet;
78
79fn substitute_in_expr(expr: &TLExpr, subst: &Substitution) -> TLExpr {
83 match expr {
84 TLExpr::Pred { name, args } => {
85 let new_args = args.iter().map(|term| subst.apply(term)).collect();
87 TLExpr::Pred {
88 name: name.clone(),
89 args: new_args,
90 }
91 }
92 TLExpr::And(left, right) => TLExpr::And(
93 Box::new(substitute_in_expr(left, subst)),
94 Box::new(substitute_in_expr(right, subst)),
95 ),
96 TLExpr::Or(left, right) => TLExpr::Or(
97 Box::new(substitute_in_expr(left, subst)),
98 Box::new(substitute_in_expr(right, subst)),
99 ),
100 TLExpr::Not(inner) => TLExpr::Not(Box::new(substitute_in_expr(inner, subst))),
101 TLExpr::Imply(left, right) => TLExpr::Imply(
102 Box::new(substitute_in_expr(left, subst)),
103 Box::new(substitute_in_expr(right, subst)),
104 ),
105 TLExpr::Exists { var, domain, body } => {
106 if subst.domain().contains(var) {
108 expr.clone()
110 } else {
111 TLExpr::Exists {
112 var: var.clone(),
113 domain: domain.clone(),
114 body: Box::new(substitute_in_expr(body, subst)),
115 }
116 }
117 }
118 TLExpr::ForAll { var, domain, body } => {
119 if subst.domain().contains(var) {
121 expr.clone()
123 } else {
124 TLExpr::ForAll {
125 var: var.clone(),
126 domain: domain.clone(),
127 body: Box::new(substitute_in_expr(body, subst)),
128 }
129 }
130 }
131 _ => expr.clone(),
133 }
134}
135
136#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
144pub struct Sequent {
145 pub antecedents: Vec<TLExpr>,
147 pub consequents: Vec<TLExpr>,
149}
150
151impl Sequent {
152 pub fn new(antecedents: Vec<TLExpr>, consequents: Vec<TLExpr>) -> Self {
154 Sequent {
155 antecedents,
156 consequents,
157 }
158 }
159
160 pub fn identity(formula: TLExpr) -> Self {
162 Sequent::new(vec![formula.clone()], vec![formula])
163 }
164
165 pub fn is_axiom(&self) -> bool {
167 for ant in &self.antecedents {
168 for cons in &self.consequents {
169 if ant == cons {
170 return true;
171 }
172 }
173 }
174 false
175 }
176
177 pub fn weaken_left(mut self, formula: TLExpr) -> Self {
179 self.antecedents.push(formula);
180 self
181 }
182
183 pub fn weaken_right(mut self, formula: TLExpr) -> Self {
185 self.consequents.push(formula);
186 self
187 }
188
189 pub fn contract_left(mut self, index: usize) -> Option<Self> {
191 if index >= self.antecedents.len() {
192 return None;
193 }
194 let formula = self.antecedents[index].clone();
195 for i in 0..self.antecedents.len() {
197 if i != index && self.antecedents[i] == formula {
198 self.antecedents.remove(index);
199 return Some(self);
200 }
201 }
202 None
203 }
204
205 pub fn contract_right(mut self, index: usize) -> Option<Self> {
207 if index >= self.consequents.len() {
208 return None;
209 }
210 let formula = self.consequents[index].clone();
211 for i in 0..self.consequents.len() {
213 if i != index && self.consequents[i] == formula {
214 self.consequents.remove(index);
215 return Some(self);
216 }
217 }
218 None
219 }
220
221 pub fn free_vars(&self) -> HashSet<String> {
223 let mut vars = HashSet::new();
224 for ant in &self.antecedents {
225 vars.extend(ant.free_vars());
226 }
227 for cons in &self.consequents {
228 vars.extend(cons.free_vars());
229 }
230 vars
231 }
232
233 pub fn substitute(&self, var: &str, term: &Term) -> Self {
253 let mut subst = Substitution::empty();
254 subst.bind(var.to_string(), term.clone());
255
256 let new_antecedents = self
257 .antecedents
258 .iter()
259 .map(|expr| substitute_in_expr(expr, &subst))
260 .collect();
261
262 let new_consequents = self
263 .consequents
264 .iter()
265 .map(|expr| substitute_in_expr(expr, &subst))
266 .collect();
267
268 Sequent::new(new_antecedents, new_consequents)
269 }
270}
271
272#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
274pub enum InferenceRule {
275 Identity,
277
278 WeakeningLeft,
280
281 WeakeningRight,
283
284 ContractionLeft { index: usize },
286
287 ContractionRight { index: usize },
289
290 Exchange,
292
293 Cut { index: usize },
295
296 AndLeft { index: usize },
298
299 AndRight { index: usize },
301
302 OrLeft { index: usize },
304
305 OrRight { index: usize },
307
308 NotLeft { index: usize },
310
311 NotRight { index: usize },
313
314 ImplyLeft { index: usize },
316
317 ImplyRight { index: usize },
319
320 ExistsLeft { index: usize, witness: Term },
322
323 ExistsRight { index: usize, witness: Term },
325
326 ForAllLeft { index: usize, term: Term },
328
329 ForAllRight { index: usize, witness: Term },
331}
332
333#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
338pub struct ProofTree {
339 pub conclusion: Sequent,
341 pub rule: InferenceRule,
343 pub premises: Vec<ProofTree>,
345}
346
347impl ProofTree {
348 pub fn identity(formula: TLExpr) -> Self {
350 ProofTree {
351 conclusion: Sequent::identity(formula),
352 rule: InferenceRule::Identity,
353 premises: vec![],
354 }
355 }
356
357 pub fn new(conclusion: Sequent, rule: InferenceRule, premises: Vec<ProofTree>) -> Self {
359 ProofTree {
360 conclusion,
361 rule,
362 premises,
363 }
364 }
365
366 pub fn is_valid(&self) -> bool {
368 match &self.rule {
370 InferenceRule::Identity => {
371 self.premises.is_empty() && self.conclusion.is_axiom()
373 }
374 InferenceRule::WeakeningLeft | InferenceRule::WeakeningRight => {
375 if self.premises.len() != 1 {
377 return false;
378 }
379 true
382 }
383 InferenceRule::AndLeft { index } => {
384 if self.premises.len() != 1 {
386 return false;
387 }
388 if *index >= self.conclusion.antecedents.len() {
389 return false;
390 }
391 matches!(self.conclusion.antecedents[*index], TLExpr::And(_, _))
393 }
394 InferenceRule::AndRight { .. } => {
395 self.premises.len() == 2
397 }
398 InferenceRule::OrLeft { .. } => {
399 self.premises.len() == 2
401 }
402 InferenceRule::OrRight { index } => {
403 if self.premises.len() != 1 {
405 return false;
406 }
407 if *index >= self.conclusion.consequents.len() {
408 return false;
409 }
410 matches!(self.conclusion.consequents[*index], TLExpr::Or(_, _))
412 }
413 InferenceRule::NotLeft { .. } | InferenceRule::NotRight { .. } => {
414 self.premises.len() == 1
416 }
417 InferenceRule::ImplyLeft { .. } => {
418 self.premises.len() == 2
420 }
421 InferenceRule::ImplyRight { .. } => {
422 self.premises.len() == 1
424 }
425 InferenceRule::Cut { .. } => {
426 self.premises.len() == 2
428 }
429 _ => true, }
431 }
432
433 pub fn depth(&self) -> usize {
435 if self.premises.is_empty() {
436 1
437 } else {
438 1 + self.premises.iter().map(|p| p.depth()).max().unwrap_or(0)
439 }
440 }
441
442 pub fn size(&self) -> usize {
444 1 + self.premises.iter().map(|p| p.size()).sum::<usize>()
445 }
446
447 pub fn uses_cut(&self) -> bool {
449 matches!(self.rule, InferenceRule::Cut { .. }) || self.premises.iter().any(|p| p.uses_cut())
450 }
451}
452
453#[derive(Clone, Debug, PartialEq, Eq)]
455pub enum ProofSearchStrategy {
456 DepthFirst { max_depth: usize },
458 BreadthFirst { max_depth: usize },
460 BestFirst { max_depth: usize },
462 IterativeDeepening { max_depth: usize },
464}
465
466pub struct ProofSearchEngine {
468 strategy: ProofSearchStrategy,
469 max_steps: usize,
471 pub stats: ProofSearchStats,
473}
474
475#[derive(Clone, Debug, Default, Serialize, Deserialize)]
477pub struct ProofSearchStats {
478 pub sequents_explored: usize,
480 pub proofs_generated: usize,
482 pub backtracks: usize,
484 pub proof_depth: Option<usize>,
486}
487
488impl ProofSearchEngine {
489 pub fn new(strategy: ProofSearchStrategy, max_steps: usize) -> Self {
491 ProofSearchEngine {
492 strategy,
493 max_steps,
494 stats: ProofSearchStats::default(),
495 }
496 }
497
498 pub fn search(&mut self, sequent: &Sequent) -> Option<ProofTree> {
502 match self.strategy {
503 ProofSearchStrategy::DepthFirst { max_depth } => self.dfs_search(sequent, 0, max_depth),
504 ProofSearchStrategy::BreadthFirst { max_depth } => self.bfs_search(sequent, max_depth),
505 ProofSearchStrategy::BestFirst { max_depth } => {
506 self.best_first_search(sequent, max_depth)
507 }
508 ProofSearchStrategy::IterativeDeepening { max_depth } => {
509 self.iterative_deepening_search(sequent, max_depth)
510 }
511 }
512 }
513
514 fn dfs_search(
516 &mut self,
517 sequent: &Sequent,
518 depth: usize,
519 max_depth: usize,
520 ) -> Option<ProofTree> {
521 self.stats.sequents_explored += 1;
522
523 if depth >= max_depth || self.stats.sequents_explored >= self.max_steps {
524 self.stats.backtracks += 1;
525 return None;
526 }
527
528 if sequent.is_axiom() {
530 for ant in &sequent.antecedents {
532 if sequent.consequents.contains(ant) {
533 self.stats.proofs_generated += 1;
534 let proof = ProofTree::identity(ant.clone());
535 self.stats.proof_depth = Some(depth);
536 return Some(proof);
537 }
538 }
539 }
540
541 for (i, ant) in sequent.antecedents.iter().enumerate() {
546 if let TLExpr::And(a, b) = ant {
547 let mut new_ant = sequent.antecedents.clone();
548 new_ant.remove(i);
549 new_ant.push((**a).clone());
550 new_ant.push((**b).clone());
551
552 let new_sequent = Sequent::new(new_ant, sequent.consequents.clone());
553 if let Some(premise) = self.dfs_search(&new_sequent, depth + 1, max_depth) {
554 self.stats.proofs_generated += 1;
555 return Some(ProofTree::new(
556 sequent.clone(),
557 InferenceRule::AndLeft { index: i },
558 vec![premise],
559 ));
560 }
561 }
562 }
563
564 for (i, cons) in sequent.consequents.iter().enumerate() {
566 if let TLExpr::Or(a, b) = cons {
567 let mut new_cons = sequent.consequents.clone();
568 new_cons.remove(i);
569 new_cons.push((**a).clone());
570 new_cons.push((**b).clone());
571
572 let new_sequent = Sequent::new(sequent.antecedents.clone(), new_cons);
573 if let Some(premise) = self.dfs_search(&new_sequent, depth + 1, max_depth) {
574 self.stats.proofs_generated += 1;
575 return Some(ProofTree::new(
576 sequent.clone(),
577 InferenceRule::OrRight { index: i },
578 vec![premise],
579 ));
580 }
581 }
582 }
583
584 for (i, ant) in sequent.antecedents.iter().enumerate() {
586 if let TLExpr::Not(a) = ant {
587 let mut new_ant = sequent.antecedents.clone();
588 new_ant.remove(i);
589
590 let mut new_cons = sequent.consequents.clone();
591 new_cons.push((**a).clone());
592
593 let new_sequent = Sequent::new(new_ant, new_cons);
594 if let Some(premise) = self.dfs_search(&new_sequent, depth + 1, max_depth) {
595 self.stats.proofs_generated += 1;
596 return Some(ProofTree::new(
597 sequent.clone(),
598 InferenceRule::NotLeft { index: i },
599 vec![premise],
600 ));
601 }
602 }
603 }
604
605 for (i, cons) in sequent.consequents.iter().enumerate() {
607 if let TLExpr::Not(a) = cons {
608 let mut new_cons = sequent.consequents.clone();
609 new_cons.remove(i);
610
611 let mut new_ant = sequent.antecedents.clone();
612 new_ant.push((**a).clone());
613
614 let new_sequent = Sequent::new(new_ant, new_cons);
615 if let Some(premise) = self.dfs_search(&new_sequent, depth + 1, max_depth) {
616 self.stats.proofs_generated += 1;
617 return Some(ProofTree::new(
618 sequent.clone(),
619 InferenceRule::NotRight { index: i },
620 vec![premise],
621 ));
622 }
623 }
624 }
625
626 self.stats.backtracks += 1;
627 None
628 }
629
630 fn bfs_search(&mut self, sequent: &Sequent, max_depth: usize) -> Option<ProofTree> {
632 self.dfs_search(sequent, 0, max_depth)
634 }
635
636 fn best_first_search(&mut self, sequent: &Sequent, max_depth: usize) -> Option<ProofTree> {
638 self.dfs_search(sequent, 0, max_depth)
640 }
641
642 fn iterative_deepening_search(
644 &mut self,
645 sequent: &Sequent,
646 max_depth: usize,
647 ) -> Option<ProofTree> {
648 for depth in 1..=max_depth {
649 if let Some(proof) = self.dfs_search(sequent, 0, depth) {
650 return Some(proof);
651 }
652 }
653 None
654 }
655}
656
657pub struct CutElimination;
663
664impl CutElimination {
665 pub fn eliminate(proof: ProofTree) -> ProofTree {
670 if !proof.uses_cut() {
671 return proof;
672 }
673
674 let premises: Vec<ProofTree> = proof.premises.into_iter().map(Self::eliminate).collect();
676
677 if let InferenceRule::Cut { index } = proof.rule {
679 if premises.len() == 2 {
680 return ProofTree::new(proof.conclusion, InferenceRule::Cut { index }, premises);
688 }
689 }
690
691 ProofTree::new(proof.conclusion, proof.rule, premises)
693 }
694
695 pub fn is_cut_free(proof: &ProofTree) -> bool {
697 !proof.uses_cut()
698 }
699}
700
701#[cfg(test)]
702mod tests {
703 use super::*;
704 use crate::TLExpr;
705
706 #[test]
707 fn test_identity_sequent() {
708 let p = TLExpr::pred("P", vec![Term::var("x")]);
709 let seq = Sequent::identity(p);
710 assert!(seq.is_axiom());
711 assert_eq!(seq.antecedents.len(), 1);
712 assert_eq!(seq.consequents.len(), 1);
713 }
714
715 #[test]
716 fn test_weakening_left() {
717 let p = TLExpr::pred("P", vec![]);
718 let q = TLExpr::pred("Q", vec![]);
719 let seq = Sequent::identity(p.clone()).weaken_left(q.clone());
720 assert_eq!(seq.antecedents.len(), 2);
721 assert!(seq.antecedents.contains(&q));
722 }
723
724 #[test]
725 fn test_weakening_right() {
726 let p = TLExpr::pred("P", vec![]);
727 let q = TLExpr::pred("Q", vec![]);
728 let seq = Sequent::identity(p.clone()).weaken_right(q.clone());
729 assert_eq!(seq.consequents.len(), 2);
730 assert!(seq.consequents.contains(&q));
731 }
732
733 #[test]
734 fn test_contraction_left() {
735 let p = TLExpr::pred("P", vec![]);
736 let mut seq = Sequent::identity(p.clone());
737 seq.antecedents.push(p.clone());
738 assert_eq!(seq.antecedents.len(), 2);
739
740 let contracted = seq.contract_left(0);
741 assert!(contracted.is_some());
742 assert_eq!(contracted.unwrap().antecedents.len(), 1);
743 }
744
745 #[test]
746 fn test_free_vars() {
747 let p = TLExpr::pred("P", vec![Term::var("x")]);
748 let q = TLExpr::pred("Q", vec![Term::var("y")]);
749 let seq = Sequent::new(vec![p], vec![q]);
750
751 let vars = seq.free_vars();
752 assert_eq!(vars.len(), 2);
753 assert!(vars.contains("x"));
754 assert!(vars.contains("y"));
755 }
756
757 #[test]
758 fn test_sequent_substitute() {
759 let p_x = TLExpr::pred("P", vec![Term::var("x")]);
760 let seq = Sequent::identity(p_x.clone());
761
762 let substituted = seq.substitute("x", &Term::constant("a"));
764 let p_a = TLExpr::pred("P", vec![Term::constant("a")]);
765 assert_eq!(substituted.antecedents[0], p_a);
766 assert_eq!(substituted.consequents[0], p_a);
767
768 assert_eq!(seq.antecedents[0], p_x);
770 }
771
772 #[test]
773 fn test_sequent_substitute_capture_avoiding() {
774 let p_x = TLExpr::pred("P", vec![Term::var("x")]);
777 let exists_p = TLExpr::exists("x", "Domain", p_x);
778 let q_x = TLExpr::pred("Q", vec![Term::var("x")]);
779 let seq = Sequent::new(vec![exists_p.clone()], vec![q_x]);
780
781 let substituted = seq.substitute("x", &Term::constant("a"));
783
784 assert_eq!(substituted.antecedents[0], exists_p);
786 let q_a = TLExpr::pred("Q", vec![Term::constant("a")]);
788 assert_eq!(substituted.consequents[0], q_a);
789 }
790
791 #[test]
792 fn test_sequent_substitute_multiple() {
793 let p_x = TLExpr::pred("P", vec![Term::var("x")]);
795 let q_x = TLExpr::pred("Q", vec![Term::var("x")]);
796 let and_pq = TLExpr::and(p_x, q_x);
797 let r_x = TLExpr::pred("R", vec![Term::var("x")]);
798 let seq = Sequent::new(vec![and_pq], vec![r_x]);
799
800 let substituted = seq.substitute("x", &Term::constant("b"));
802
803 let p_b = TLExpr::pred("P", vec![Term::constant("b")]);
805 let q_b = TLExpr::pred("Q", vec![Term::constant("b")]);
806 let and_pq_b = TLExpr::and(p_b, q_b);
807 let r_b = TLExpr::pred("R", vec![Term::constant("b")]);
808
809 assert_eq!(substituted.antecedents[0], and_pq_b);
810 assert_eq!(substituted.consequents[0], r_b);
811 }
812
813 #[test]
814 fn test_substitution() {
815 let p = TLExpr::pred("P", vec![Term::var("x")]);
816 let seq = Sequent::identity(p.clone());
817
818 let substituted = seq.substitute("x", &Term::constant("a"));
820 let p_a = TLExpr::pred("P", vec![Term::constant("a")]);
821 assert_eq!(substituted.antecedents[0], p_a);
822 assert_eq!(substituted.consequents[0], p_a);
823 }
824
825 #[test]
826 fn test_identity_proof_tree() {
827 let p = TLExpr::pred("P", vec![]);
828 let proof = ProofTree::identity(p);
829 assert!(proof.is_valid());
830 assert_eq!(proof.depth(), 1);
831 assert_eq!(proof.size(), 1);
832 assert!(!proof.uses_cut());
833 }
834
835 #[test]
836 fn test_and_left_proof() {
837 let p = TLExpr::pred("P", vec![]);
838 let q = TLExpr::pred("Q", vec![]);
839 let and_pq = TLExpr::and(p.clone(), q.clone());
840
841 let _premise_seq = Sequent::new(vec![p.clone(), q], vec![p.clone()]);
843 let premise = ProofTree::identity(p.clone());
844
845 let conclusion_seq = Sequent::new(vec![and_pq], vec![p]);
846 let proof = ProofTree::new(
847 conclusion_seq,
848 InferenceRule::AndLeft { index: 0 },
849 vec![premise],
850 );
851
852 assert!(proof.is_valid());
853 assert_eq!(proof.depth(), 2);
854 }
855
856 #[test]
857 fn test_proof_search_simple() {
858 let p = TLExpr::pred("P", vec![]);
859 let sequent = Sequent::identity(p);
860
861 let mut engine =
862 ProofSearchEngine::new(ProofSearchStrategy::DepthFirst { max_depth: 10 }, 1000);
863
864 let proof = engine.search(&sequent);
865 assert!(proof.is_some());
866 assert!(proof.unwrap().is_valid());
867 assert!(engine.stats.proofs_generated > 0);
868 }
869
870 #[test]
871 fn test_proof_search_and() {
872 let p = TLExpr::pred("P", vec![]);
873 let q = TLExpr::pred("Q", vec![]);
874 let and_pq = TLExpr::and(p.clone(), q.clone());
875
876 let sequent = Sequent::new(vec![and_pq], vec![p]);
878
879 let mut engine =
880 ProofSearchEngine::new(ProofSearchStrategy::DepthFirst { max_depth: 10 }, 1000);
881
882 let proof = engine.search(&sequent);
883 assert!(proof.is_some());
884 let proof = proof.unwrap();
885 assert!(proof.is_valid());
886 assert!(engine.stats.proofs_generated > 0);
887 }
888
889 #[test]
890 fn test_proof_search_not() {
891 let p = TLExpr::pred("P", vec![]);
892 let not_p = TLExpr::negate(p.clone());
893
894 let sequent = Sequent::identity(not_p);
896
897 let mut engine =
898 ProofSearchEngine::new(ProofSearchStrategy::DepthFirst { max_depth: 10 }, 1000);
899
900 let proof = engine.search(&sequent);
901 assert!(proof.is_some());
902 assert!(proof.unwrap().is_valid());
903 }
904
905 #[test]
906 fn test_cut_elimination_no_cut() {
907 let p = TLExpr::pred("P", vec![]);
908 let proof = ProofTree::identity(p);
909
910 assert!(CutElimination::is_cut_free(&proof));
911 let eliminated = CutElimination::eliminate(proof.clone());
912 assert_eq!(eliminated, proof);
913 }
914
915 #[test]
916 fn test_iterative_deepening_search() {
917 let p = TLExpr::pred("P", vec![]);
918 let q = TLExpr::pred("Q", vec![]);
919 let and_pq = TLExpr::and(p.clone(), q.clone());
920
921 let sequent = Sequent::new(vec![and_pq], vec![p]);
922
923 let mut engine = ProofSearchEngine::new(
924 ProofSearchStrategy::IterativeDeepening { max_depth: 10 },
925 1000,
926 );
927
928 let proof = engine.search(&sequent);
929 assert!(proof.is_some());
930 assert!(proof.unwrap().is_valid());
931 }
932}