1use crate::premise::{PremiseId, PremiseTracker};
21use crate::proof::{Proof, ProofNodeId, ProofStep};
22use num_rational::BigRational;
23use rustc_hash::{FxHashMap, FxHashSet};
24use std::fmt;
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
32pub enum InterpolationAlgorithm {
33 McMillan,
36 #[default]
38 Pudlak,
39 Huang,
41}
42
43#[derive(Debug, Clone)]
45pub struct InterpolationConfig {
46 pub algorithm: InterpolationAlgorithm,
48 pub use_theory_interpolants: bool,
50 pub simplify_interpolants: bool,
52 pub max_simplify_depth: usize,
54 pub enable_caching: bool,
56 pub deduplicate_terms: bool,
58}
59
60impl Default for InterpolationConfig {
61 fn default() -> Self {
62 Self {
63 algorithm: InterpolationAlgorithm::Pudlak,
64 use_theory_interpolants: true,
65 simplify_interpolants: true,
66 max_simplify_depth: 100,
67 enable_caching: true,
68 deduplicate_terms: true,
69 }
70 }
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
75pub enum InterpolantColor {
76 A,
78 B,
80 AB,
82}
83
84impl fmt::Display for InterpolantColor {
85 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86 match self {
87 Self::A => write!(f, "A"),
88 Self::B => write!(f, "B"),
89 Self::AB => write!(f, "AB"),
90 }
91 }
92}
93
94#[derive(Debug, Clone)]
96pub struct InterpolantPartition {
97 a_premises: FxHashSet<PremiseId>,
99 b_premises: FxHashSet<PremiseId>,
101 shared_symbols: FxHashSet<Symbol>,
103}
104
105#[derive(Debug, Clone, PartialEq, Eq, Hash)]
107pub struct Symbol {
108 pub name: String,
110 pub arity: usize,
112}
113
114impl Symbol {
115 #[must_use]
117 pub fn new(name: impl Into<String>, arity: usize) -> Self {
118 Self {
119 name: name.into(),
120 arity,
121 }
122 }
123
124 #[must_use]
126 pub fn var(name: impl Into<String>) -> Self {
127 Self::new(name, 0)
128 }
129}
130
131impl InterpolantPartition {
132 #[must_use]
134 pub fn new(
135 a_premises: impl IntoIterator<Item = PremiseId>,
136 b_premises: impl IntoIterator<Item = PremiseId>,
137 ) -> Self {
138 Self {
139 a_premises: a_premises.into_iter().collect(),
140 b_premises: b_premises.into_iter().collect(),
141 shared_symbols: FxHashSet::default(),
142 }
143 }
144
145 pub fn set_shared_symbols(&mut self, symbols: impl IntoIterator<Item = Symbol>) {
147 self.shared_symbols = symbols.into_iter().collect();
148 }
149
150 #[must_use]
152 pub fn is_a_premise(&self, premise: PremiseId) -> bool {
153 self.a_premises.contains(&premise)
154 }
155
156 #[must_use]
158 pub fn is_b_premise(&self, premise: PremiseId) -> bool {
159 self.b_premises.contains(&premise)
160 }
161
162 #[must_use]
164 pub fn is_shared(&self, symbol: &Symbol) -> bool {
165 self.shared_symbols.contains(symbol)
166 }
167
168 #[must_use]
170 pub fn a_premises(&self) -> &FxHashSet<PremiseId> {
171 &self.a_premises
172 }
173
174 #[must_use]
176 pub fn b_premises(&self) -> &FxHashSet<PremiseId> {
177 &self.b_premises
178 }
179}
180
181#[derive(Debug, Clone, PartialEq, Eq, Hash)]
187pub enum InterpolantTerm {
188 Bool(bool),
190 Var(Symbol),
192 Not(Box<InterpolantTerm>),
194 And(Vec<InterpolantTerm>),
196 Or(Vec<InterpolantTerm>),
198 Implies(Box<InterpolantTerm>, Box<InterpolantTerm>),
200 Eq(Box<InterpolantTerm>, Box<InterpolantTerm>),
202 Lt(Box<InterpolantTerm>, Box<InterpolantTerm>),
204 Le(Box<InterpolantTerm>, Box<InterpolantTerm>),
206 Num(BigRational),
208 Add(Vec<InterpolantTerm>),
210 Sub(Box<InterpolantTerm>, Box<InterpolantTerm>),
212 Mul(Vec<InterpolantTerm>),
214 App(Symbol, Vec<InterpolantTerm>),
216 Select(Box<InterpolantTerm>, Box<InterpolantTerm>),
218 Store(
220 Box<InterpolantTerm>,
221 Box<InterpolantTerm>,
222 Box<InterpolantTerm>,
223 ),
224}
225
226impl InterpolantTerm {
227 #[must_use]
229 pub fn true_val() -> Self {
230 Self::Bool(true)
231 }
232
233 #[must_use]
235 pub fn false_val() -> Self {
236 Self::Bool(false)
237 }
238
239 #[must_use]
241 pub fn var(name: impl Into<String>) -> Self {
242 Self::Var(Symbol::var(name))
243 }
244
245 #[must_use]
247 #[allow(clippy::should_implement_trait)]
248 pub fn not(term: Self) -> Self {
249 match term {
250 Self::Bool(b) => Self::Bool(!b),
251 Self::Not(inner) => *inner,
252 _ => Self::Not(Box::new(term)),
253 }
254 }
255
256 #[must_use]
258 pub fn and(terms: Vec<Self>) -> Self {
259 let mut flat = Vec::new();
260 for t in terms {
261 match t {
262 Self::Bool(true) => continue,
263 Self::Bool(false) => return Self::Bool(false),
264 Self::And(inner) => flat.extend(inner),
265 other => flat.push(other),
266 }
267 }
268 if flat.is_empty() {
269 Self::Bool(true)
270 } else if flat.len() == 1 {
271 flat.pop().unwrap_or(Self::Bool(true))
272 } else {
273 Self::And(flat)
274 }
275 }
276
277 #[must_use]
279 pub fn or(terms: Vec<Self>) -> Self {
280 let mut flat = Vec::new();
281 for t in terms {
282 match t {
283 Self::Bool(false) => continue,
284 Self::Bool(true) => return Self::Bool(true),
285 Self::Or(inner) => flat.extend(inner),
286 other => flat.push(other),
287 }
288 }
289 if flat.is_empty() {
290 Self::Bool(false)
291 } else if flat.len() == 1 {
292 flat.pop().unwrap_or(Self::Bool(false))
293 } else {
294 Self::Or(flat)
295 }
296 }
297
298 #[must_use]
300 pub fn implies(lhs: Self, rhs: Self) -> Self {
301 match (&lhs, &rhs) {
302 (Self::Bool(false), _) => Self::Bool(true),
303 (Self::Bool(true), _) => rhs,
304 (_, Self::Bool(true)) => Self::Bool(true),
305 (_, Self::Bool(false)) => Self::not(lhs),
306 _ => Self::Implies(Box::new(lhs), Box::new(rhs)),
307 }
308 }
309
310 #[must_use]
312 pub fn is_true(&self) -> bool {
313 matches!(self, Self::Bool(true))
314 }
315
316 #[must_use]
318 pub fn is_false(&self) -> bool {
319 matches!(self, Self::Bool(false))
320 }
321
322 pub fn collect_symbols(&self, symbols: &mut FxHashSet<Symbol>) {
324 match self {
325 Self::Bool(_) | Self::Num(_) => {}
326 Self::Var(s) => {
327 symbols.insert(s.clone());
328 }
329 Self::Not(t) => t.collect_symbols(symbols),
330 Self::And(ts) | Self::Or(ts) | Self::Add(ts) | Self::Mul(ts) => {
331 for t in ts {
332 t.collect_symbols(symbols);
333 }
334 }
335 Self::Implies(a, b)
336 | Self::Eq(a, b)
337 | Self::Lt(a, b)
338 | Self::Le(a, b)
339 | Self::Sub(a, b)
340 | Self::Select(a, b) => {
341 a.collect_symbols(symbols);
342 b.collect_symbols(symbols);
343 }
344 Self::App(f, args) => {
345 symbols.insert(f.clone());
346 for arg in args {
347 arg.collect_symbols(symbols);
348 }
349 }
350 Self::Store(a, i, v) => {
351 a.collect_symbols(symbols);
352 i.collect_symbols(symbols);
353 v.collect_symbols(symbols);
354 }
355 }
356 }
357
358 #[must_use]
360 pub fn simplify(&self) -> Self {
361 match self {
362 Self::Bool(_) | Self::Num(_) | Self::Var(_) => self.clone(),
363 Self::Not(t) => Self::not(t.simplify()),
364 Self::And(ts) => Self::and(ts.iter().map(|t| t.simplify()).collect()),
365 Self::Or(ts) => Self::or(ts.iter().map(|t| t.simplify()).collect()),
366 Self::Implies(a, b) => Self::implies(a.simplify(), b.simplify()),
367 Self::Eq(a, b) => {
368 let sa = a.simplify();
369 let sb = b.simplify();
370 if sa == sb {
371 Self::Bool(true)
372 } else {
373 Self::Eq(Box::new(sa), Box::new(sb))
374 }
375 }
376 _ => self.clone(),
377 }
378 }
379}
380
381impl fmt::Display for InterpolantTerm {
382 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
383 match self {
384 Self::Bool(b) => write!(f, "{}", b),
385 Self::Var(s) => write!(f, "{}", s.name),
386 Self::Not(t) => write!(f, "(not {})", t),
387 Self::And(ts) => {
388 write!(f, "(and")?;
389 for t in ts {
390 write!(f, " {}", t)?;
391 }
392 write!(f, ")")
393 }
394 Self::Or(ts) => {
395 write!(f, "(or")?;
396 for t in ts {
397 write!(f, " {}", t)?;
398 }
399 write!(f, ")")
400 }
401 Self::Implies(a, b) => write!(f, "(=> {} {})", a, b),
402 Self::Eq(a, b) => write!(f, "(= {} {})", a, b),
403 Self::Lt(a, b) => write!(f, "(< {} {})", a, b),
404 Self::Le(a, b) => write!(f, "(<= {} {})", a, b),
405 Self::Num(n) => write!(f, "{}", n),
406 Self::Add(ts) => {
407 write!(f, "(+")?;
408 for t in ts {
409 write!(f, " {}", t)?;
410 }
411 write!(f, ")")
412 }
413 Self::Sub(a, b) => write!(f, "(- {} {})", a, b),
414 Self::Mul(ts) => {
415 write!(f, "(*")?;
416 for t in ts {
417 write!(f, " {}", t)?;
418 }
419 write!(f, ")")
420 }
421 Self::App(s, args) => {
422 write!(f, "({}", s.name)?;
423 for arg in args {
424 write!(f, " {}", arg)?;
425 }
426 write!(f, ")")
427 }
428 Self::Select(a, i) => write!(f, "(select {} {})", a, i),
429 Self::Store(a, i, v) => write!(f, "(store {} {} {})", a, i, v),
430 }
431 }
432}
433
434#[derive(Debug)]
440pub struct CraigInterpolator {
441 config: InterpolationConfig,
443 #[allow(dead_code)]
445 partition: InterpolantPartition,
446 #[allow(dead_code)]
448 premise_tracker: PremiseTracker,
449 colors: FxHashMap<ProofNodeId, InterpolantColor>,
451 interpolants: FxHashMap<ProofNodeId, InterpolantTerm>,
453 stats: InterpolationStats,
455}
456
457#[derive(Debug, Default, Clone)]
459pub struct InterpolationStats {
460 pub nodes_processed: usize,
462 pub a_nodes: usize,
464 pub b_nodes: usize,
466 pub ab_nodes: usize,
468 pub resolution_steps: usize,
470 pub theory_lemmas: usize,
472 pub cache_hits: usize,
474 pub time_us: u64,
476}
477
478impl CraigInterpolator {
479 #[must_use]
481 pub fn new(
482 config: InterpolationConfig,
483 partition: InterpolantPartition,
484 premise_tracker: PremiseTracker,
485 ) -> Self {
486 Self {
487 config,
488 partition,
489 premise_tracker,
490 colors: FxHashMap::default(),
491 interpolants: FxHashMap::default(),
492 stats: InterpolationStats::default(),
493 }
494 }
495
496 #[must_use]
498 pub fn with_partition(partition: InterpolantPartition) -> Self {
499 Self::new(
500 InterpolationConfig::default(),
501 partition,
502 PremiseTracker::new(),
503 )
504 }
505
506 #[must_use]
508 pub fn stats(&self) -> &InterpolationStats {
509 &self.stats
510 }
511
512 pub fn extract(&mut self, proof: &Proof) -> Result<InterpolantTerm, InterpolationError> {
514 let start = std::time::Instant::now();
515
516 let root = proof.root().ok_or(InterpolationError::NoRoot)?;
517
518 self.compute_colors(proof, root)?;
520
521 let result = self.compute_interpolant(proof, root)?;
523
524 let final_result = if self.config.simplify_interpolants {
526 result.simplify()
527 } else {
528 result
529 };
530
531 self.stats.time_us = start.elapsed().as_micros() as u64;
532
533 Ok(final_result)
534 }
535
536 fn compute_colors(
538 &mut self,
539 proof: &Proof,
540 node_id: ProofNodeId,
541 ) -> Result<InterpolantColor, InterpolationError> {
542 if let Some(&color) = self.colors.get(&node_id) {
543 self.stats.cache_hits += 1;
544 return Ok(color);
545 }
546
547 let node = proof
548 .get_node(node_id)
549 .ok_or(InterpolationError::NodeNotFound(node_id))?;
550
551 self.stats.nodes_processed += 1;
552
553 let color = match &node.step {
554 ProofStep::Axiom { .. } => {
555 InterpolantColor::A }
559 ProofStep::Inference { premises, rule, .. } => {
560 if rule == "resolution" {
562 self.stats.resolution_steps += 1;
563 } else if rule.starts_with("theory") {
564 self.stats.theory_lemmas += 1;
565 }
566
567 let mut has_a = false;
569 let mut has_b = false;
570
571 for &premise_id in premises {
572 let premise_color = self.compute_colors(proof, premise_id)?;
573 match premise_color {
574 InterpolantColor::A => has_a = true,
575 InterpolantColor::B => has_b = true,
576 InterpolantColor::AB => {
577 has_a = true;
578 has_b = true;
579 }
580 }
581 }
582
583 if has_a && has_b {
584 InterpolantColor::AB
585 } else if has_a {
586 InterpolantColor::A
587 } else if has_b {
588 InterpolantColor::B
589 } else {
590 InterpolantColor::A
591 }
592 }
593 };
594
595 match color {
597 InterpolantColor::A => self.stats.a_nodes += 1,
598 InterpolantColor::B => self.stats.b_nodes += 1,
599 InterpolantColor::AB => self.stats.ab_nodes += 1,
600 }
601
602 self.colors.insert(node_id, color);
603 Ok(color)
604 }
605
606 fn compute_interpolant(
608 &mut self,
609 proof: &Proof,
610 node_id: ProofNodeId,
611 ) -> Result<InterpolantTerm, InterpolationError> {
612 if let Some(interp) = self.interpolants.get(&node_id) {
613 return Ok(interp.clone());
614 }
615
616 let node = proof
617 .get_node(node_id)
618 .ok_or(InterpolationError::NodeNotFound(node_id))?;
619 let color = *self
620 .colors
621 .get(&node_id)
622 .ok_or(InterpolationError::NoColor(node_id))?;
623
624 let interpolant = match &node.step {
625 ProofStep::Axiom { conclusion } => self.compute_axiom_interpolant(color, conclusion),
626 ProofStep::Inference {
627 rule,
628 premises,
629 conclusion,
630 ..
631 } => {
632 let mut premise_interpolants = Vec::new();
634 let mut premise_colors = Vec::new();
635
636 for &p in premises {
637 premise_interpolants.push(self.compute_interpolant(proof, p)?);
638 premise_colors.push(*self.colors.get(&p).unwrap_or(&InterpolantColor::A));
639 }
640
641 self.compute_inference_interpolant(
642 rule,
643 &premise_interpolants,
644 &premise_colors,
645 conclusion,
646 color,
647 )
648 }
649 };
650
651 if self.config.enable_caching {
652 self.interpolants.insert(node_id, interpolant.clone());
653 }
654
655 Ok(interpolant)
656 }
657
658 fn compute_axiom_interpolant(
660 &self,
661 color: InterpolantColor,
662 _conclusion: &str,
663 ) -> InterpolantTerm {
664 match color {
665 InterpolantColor::A => {
666 InterpolantTerm::true_val()
668 }
669 InterpolantColor::B => {
670 InterpolantTerm::false_val()
672 }
673 InterpolantColor::AB => {
674 InterpolantTerm::true_val()
676 }
677 }
678 }
679
680 fn compute_inference_interpolant(
682 &self,
683 rule: &str,
684 premise_interpolants: &[InterpolantTerm],
685 premise_colors: &[InterpolantColor],
686 _conclusion: &str,
687 color: InterpolantColor,
688 ) -> InterpolantTerm {
689 match self.config.algorithm {
690 InterpolationAlgorithm::McMillan => {
691 self.mcmillan_interpolant(rule, premise_interpolants, premise_colors, color)
692 }
693 InterpolationAlgorithm::Pudlak => {
694 self.pudlak_interpolant(rule, premise_interpolants, premise_colors, color)
695 }
696 InterpolationAlgorithm::Huang => {
697 self.huang_interpolant(rule, premise_interpolants, premise_colors, color)
698 }
699 }
700 }
701
702 fn mcmillan_interpolant(
709 &self,
710 rule: &str,
711 premise_interpolants: &[InterpolantTerm],
712 premise_colors: &[InterpolantColor],
713 color: InterpolantColor,
714 ) -> InterpolantTerm {
715 match color {
716 InterpolantColor::A => InterpolantTerm::true_val(),
717 InterpolantColor::B => InterpolantTerm::false_val(),
718 InterpolantColor::AB => {
719 if rule == "resolution" && premise_interpolants.len() == 2 {
720 let i1 = &premise_interpolants[0];
721 let i2 = &premise_interpolants[1];
722 let c1 = premise_colors[0];
723 let c2 = premise_colors[1];
724
725 match (c1, c2) {
726 (InterpolantColor::A, InterpolantColor::B) => {
727 InterpolantTerm::or(vec![i1.clone(), i2.clone()])
729 }
730 (InterpolantColor::B, InterpolantColor::A) => {
731 InterpolantTerm::or(vec![i1.clone(), i2.clone()])
732 }
733 (InterpolantColor::A, InterpolantColor::AB) => {
734 i2.clone()
736 }
737 (InterpolantColor::AB, InterpolantColor::A) => i1.clone(),
738 (InterpolantColor::B, InterpolantColor::AB) => i2.clone(),
739 (InterpolantColor::AB, InterpolantColor::B) => i1.clone(),
740 (InterpolantColor::AB, InterpolantColor::AB) => {
741 InterpolantTerm::or(vec![i1.clone(), i2.clone()])
743 }
744 _ => InterpolantTerm::or(vec![i1.clone(), i2.clone()]),
745 }
746 } else {
747 InterpolantTerm::or(premise_interpolants.to_vec())
749 }
750 }
751 }
752 }
753
754 fn pudlak_interpolant(
758 &self,
759 rule: &str,
760 premise_interpolants: &[InterpolantTerm],
761 premise_colors: &[InterpolantColor],
762 color: InterpolantColor,
763 ) -> InterpolantTerm {
764 match color {
765 InterpolantColor::A => InterpolantTerm::true_val(),
766 InterpolantColor::B => InterpolantTerm::false_val(),
767 InterpolantColor::AB => {
768 if rule == "resolution" && premise_interpolants.len() == 2 {
769 let i1 = &premise_interpolants[0];
770 let i2 = &premise_interpolants[1];
771 let c1 = premise_colors[0];
772 let c2 = premise_colors[1];
773
774 match (c1, c2) {
776 (InterpolantColor::A, InterpolantColor::B)
777 | (InterpolantColor::B, InterpolantColor::A) => {
778 InterpolantTerm::or(vec![i1.clone(), i2.clone()])
780 }
781 (InterpolantColor::A, InterpolantColor::AB) => i2.clone(),
782 (InterpolantColor::AB, InterpolantColor::A) => i1.clone(),
783 (InterpolantColor::B, InterpolantColor::AB) => i2.clone(),
784 (InterpolantColor::AB, InterpolantColor::B) => i1.clone(),
785 (InterpolantColor::AB, InterpolantColor::AB) => {
786 InterpolantTerm::or(vec![i1.clone(), i2.clone()])
788 }
789 _ => InterpolantTerm::or(vec![i1.clone(), i2.clone()]),
790 }
791 } else if rule == "transitivity" && premise_interpolants.len() >= 2 {
792 InterpolantTerm::and(premise_interpolants.to_vec())
794 } else if rule == "congruence" {
795 InterpolantTerm::and(premise_interpolants.to_vec())
797 } else {
798 InterpolantTerm::or(premise_interpolants.to_vec())
800 }
801 }
802 }
803 }
804
805 fn huang_interpolant(
809 &self,
810 rule: &str,
811 premise_interpolants: &[InterpolantTerm],
812 premise_colors: &[InterpolantColor],
813 color: InterpolantColor,
814 ) -> InterpolantTerm {
815 match color {
816 InterpolantColor::A => InterpolantTerm::true_val(),
817 InterpolantColor::B => InterpolantTerm::false_val(),
818 InterpolantColor::AB => {
819 if rule == "resolution" && premise_interpolants.len() == 2 {
820 let i1 = &premise_interpolants[0];
821 let i2 = &premise_interpolants[1];
822 let c1 = premise_colors[0];
823 let c2 = premise_colors[1];
824
825 match (c1, c2) {
826 (InterpolantColor::A, InterpolantColor::B)
827 | (InterpolantColor::B, InterpolantColor::A) => {
828 InterpolantTerm::and(vec![i1.clone(), i2.clone()])
830 }
831 (InterpolantColor::A, InterpolantColor::AB) => i2.clone(),
832 (InterpolantColor::AB, InterpolantColor::A) => i1.clone(),
833 (InterpolantColor::B, InterpolantColor::AB) => i2.clone(),
834 (InterpolantColor::AB, InterpolantColor::B) => i1.clone(),
835 (InterpolantColor::AB, InterpolantColor::AB) => {
836 InterpolantTerm::and(vec![i1.clone(), i2.clone()])
838 }
839 _ => InterpolantTerm::and(vec![i1.clone(), i2.clone()]),
840 }
841 } else {
842 InterpolantTerm::and(premise_interpolants.to_vec())
844 }
845 }
846 }
847 }
848}
849
850pub trait TheoryInterpolator: Send + Sync {
856 fn name(&self) -> &'static str;
858
859 fn can_handle(&self, literals: &[&str]) -> bool;
861
862 fn interpolate(
864 &self,
865 a_literals: &[InterpolantTerm],
866 b_literals: &[InterpolantTerm],
867 shared_symbols: &FxHashSet<Symbol>,
868 ) -> Option<InterpolantTerm>;
869}
870
871#[derive(Debug, Default)]
873pub struct LiaInterpolator;
874
875impl TheoryInterpolator for LiaInterpolator {
876 fn name(&self) -> &'static str {
877 "LIA"
878 }
879
880 fn can_handle(&self, literals: &[&str]) -> bool {
881 literals.iter().any(|l| {
882 l.contains('+')
883 || l.contains('-')
884 || l.contains('*')
885 || l.contains("<=")
886 || l.contains(">=")
887 || l.contains('<')
888 || l.contains('>')
889 })
890 }
891
892 fn interpolate(
893 &self,
894 a_literals: &[InterpolantTerm],
895 b_literals: &[InterpolantTerm],
896 _shared_symbols: &FxHashSet<Symbol>,
897 ) -> Option<InterpolantTerm> {
898 if a_literals.is_empty() || b_literals.is_empty() {
903 return None;
904 }
905
906 Some(InterpolantTerm::and(a_literals.to_vec()))
909 }
910}
911
912#[derive(Debug, Default)]
914pub struct EufInterpolator;
915
916impl TheoryInterpolator for EufInterpolator {
917 fn name(&self) -> &'static str {
918 "EUF"
919 }
920
921 fn can_handle(&self, literals: &[&str]) -> bool {
922 literals.iter().any(|l| l.contains('=') || l.contains('('))
923 }
924
925 fn interpolate(
926 &self,
927 a_literals: &[InterpolantTerm],
928 _b_literals: &[InterpolantTerm],
929 _shared_symbols: &FxHashSet<Symbol>,
930 ) -> Option<InterpolantTerm> {
931 if a_literals.is_empty() {
935 return Some(InterpolantTerm::true_val());
936 }
937
938 Some(InterpolantTerm::and(a_literals.to_vec()))
940 }
941}
942
943#[derive(Debug, Default)]
945pub struct ArrayInterpolator;
946
947impl TheoryInterpolator for ArrayInterpolator {
948 fn name(&self) -> &'static str {
949 "Array"
950 }
951
952 fn can_handle(&self, literals: &[&str]) -> bool {
953 literals
954 .iter()
955 .any(|l| l.contains("select") || l.contains("store"))
956 }
957
958 fn interpolate(
959 &self,
960 a_literals: &[InterpolantTerm],
961 _b_literals: &[InterpolantTerm],
962 _shared_symbols: &FxHashSet<Symbol>,
963 ) -> Option<InterpolantTerm> {
964 if a_literals.is_empty() {
968 return Some(InterpolantTerm::true_val());
969 }
970
971 Some(InterpolantTerm::and(a_literals.to_vec()))
972 }
973}
974
975#[derive(Debug)]
987pub struct SequenceInterpolator {
988 config: InterpolationConfig,
989}
990
991impl SequenceInterpolator {
992 #[must_use]
994 pub fn new(config: InterpolationConfig) -> Self {
995 Self { config }
996 }
997
998 pub fn interpolate_sequence(
1002 &self,
1003 proofs: &[Proof],
1004 ) -> Result<Vec<InterpolantTerm>, InterpolationError> {
1005 if proofs.len() < 2 {
1006 return Err(InterpolationError::TooFewFormulas);
1007 }
1008
1009 let mut interpolants = Vec::with_capacity(proofs.len() - 1);
1010
1011 for i in 0..proofs.len() - 1 {
1013 let a_ids: FxHashSet<_> = (0..=i).map(|j| PremiseId(j as u32)).collect();
1015 let b_ids: FxHashSet<_> = (i + 1..proofs.len()).map(|j| PremiseId(j as u32)).collect();
1016
1017 let partition = InterpolantPartition::new(a_ids, b_ids);
1018 let mut interpolator =
1019 CraigInterpolator::new(self.config.clone(), partition, PremiseTracker::new());
1020
1021 if let Some(proof) = proofs.first() {
1023 let interp = interpolator.extract(proof)?;
1024 interpolants.push(interp);
1025 } else {
1026 interpolants.push(InterpolantTerm::true_val());
1027 }
1028 }
1029
1030 Ok(interpolants)
1031 }
1032}
1033
1034impl Default for SequenceInterpolator {
1035 fn default() -> Self {
1036 Self::new(InterpolationConfig::default())
1037 }
1038}
1039
1040#[derive(Debug)]
1049pub struct TreeInterpolator {
1050 config: InterpolationConfig,
1051}
1052
1053#[derive(Debug, Clone)]
1055pub struct TreeNode {
1056 pub id: usize,
1058 pub formula: InterpolantTerm,
1060 pub children: Vec<usize>,
1062 pub parent: Option<usize>,
1064}
1065
1066impl TreeInterpolator {
1067 #[must_use]
1069 pub fn new(config: InterpolationConfig) -> Self {
1070 Self { config }
1071 }
1072
1073 pub fn interpolate_tree(
1077 &self,
1078 nodes: &[TreeNode],
1079 ) -> Result<FxHashMap<usize, InterpolantTerm>, InterpolationError> {
1080 let mut interpolants = FxHashMap::default();
1081
1082 let mut order = self.topological_order(nodes);
1084 order.reverse();
1085
1086 for node_id in order {
1087 if let Some(node) = nodes.get(node_id) {
1088 if node.children.is_empty() {
1089 interpolants.insert(node_id, node.formula.clone());
1091 } else {
1092 let child_interps: Vec<_> = node
1094 .children
1095 .iter()
1096 .filter_map(|&c| interpolants.get(&c).cloned())
1097 .collect();
1098
1099 let combined = if self.config.algorithm == InterpolationAlgorithm::McMillan {
1100 InterpolantTerm::or(child_interps)
1101 } else {
1102 InterpolantTerm::and(child_interps)
1103 };
1104
1105 let interp = InterpolantTerm::and(vec![node.formula.clone(), combined]);
1106 interpolants.insert(node_id, interp.simplify());
1107 }
1108 }
1109 }
1110
1111 Ok(interpolants)
1112 }
1113
1114 fn topological_order(&self, nodes: &[TreeNode]) -> Vec<usize> {
1116 let mut order = Vec::new();
1117 let mut visited = FxHashSet::default();
1118
1119 fn visit(
1120 node_id: usize,
1121 nodes: &[TreeNode],
1122 visited: &mut FxHashSet<usize>,
1123 order: &mut Vec<usize>,
1124 ) {
1125 if visited.contains(&node_id) {
1126 return;
1127 }
1128 visited.insert(node_id);
1129
1130 if let Some(node) = nodes.get(node_id) {
1131 for &child in &node.children {
1132 visit(child, nodes, visited, order);
1133 }
1134 }
1135 order.push(node_id);
1136 }
1137
1138 for (i, node) in nodes.iter().enumerate() {
1140 if node.parent.is_none() {
1141 visit(i, nodes, &mut visited, &mut order);
1142 }
1143 }
1144
1145 order
1146 }
1147}
1148
1149impl Default for TreeInterpolator {
1150 fn default() -> Self {
1151 Self::new(InterpolationConfig::default())
1152 }
1153}
1154
1155#[derive(Debug, Clone)]
1161pub enum InterpolationError {
1162 NoRoot,
1164 NodeNotFound(ProofNodeId),
1166 NoColor(ProofNodeId),
1168 TooFewFormulas,
1170 ValidationFailed(String),
1172 TheoryNotSupported(String),
1174}
1175
1176impl fmt::Display for InterpolationError {
1177 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1178 match self {
1179 Self::NoRoot => write!(f, "Proof has no root"),
1180 Self::NodeNotFound(id) => write!(f, "Node {} not found", id),
1181 Self::NoColor(id) => write!(f, "Node {} has no computed color", id),
1182 Self::TooFewFormulas => write!(f, "Need at least 2 formulas for interpolation"),
1183 Self::ValidationFailed(msg) => write!(f, "Validation failed: {}", msg),
1184 Self::TheoryNotSupported(theory) => {
1185 write!(f, "Theory {} not supported for interpolation", theory)
1186 }
1187 }
1188 }
1189}
1190
1191impl std::error::Error for InterpolationError {}
1192
1193#[cfg(test)]
1198mod tests {
1199 use super::*;
1200
1201 #[test]
1202 fn test_interpolant_term_creation() {
1203 let t = InterpolantTerm::true_val();
1204 assert!(t.is_true());
1205
1206 let f = InterpolantTerm::false_val();
1207 assert!(f.is_false());
1208
1209 let x = InterpolantTerm::var("x");
1210 assert!(!x.is_true());
1211 assert!(!x.is_false());
1212 }
1213
1214 #[test]
1215 fn test_interpolant_term_and() {
1216 let t = InterpolantTerm::true_val();
1217 let x = InterpolantTerm::var("x");
1218 let y = InterpolantTerm::var("y");
1219
1220 let and1 = InterpolantTerm::and(vec![t.clone(), x.clone()]);
1222 assert_eq!(and1, x);
1223
1224 let f = InterpolantTerm::false_val();
1226 let and2 = InterpolantTerm::and(vec![f.clone(), x.clone()]);
1227 assert!(and2.is_false());
1228
1229 let and3 = InterpolantTerm::and(vec![x.clone(), y.clone()]);
1231 match and3 {
1232 InterpolantTerm::And(args) => assert_eq!(args.len(), 2),
1233 _ => panic!("Expected And"),
1234 }
1235 }
1236
1237 #[test]
1238 fn test_interpolant_term_or() {
1239 let t = InterpolantTerm::true_val();
1240 let f = InterpolantTerm::false_val();
1241 let x = InterpolantTerm::var("x");
1242
1243 let or1 = InterpolantTerm::or(vec![f.clone(), x.clone()]);
1245 assert_eq!(or1, x);
1246
1247 let or2 = InterpolantTerm::or(vec![t.clone(), x.clone()]);
1249 assert!(or2.is_true());
1250 }
1251
1252 #[test]
1253 fn test_interpolant_term_not() {
1254 let t = InterpolantTerm::true_val();
1255 let f = InterpolantTerm::false_val();
1256 let x = InterpolantTerm::var("x");
1257
1258 let not_t = InterpolantTerm::not(t);
1260 assert!(not_t.is_false());
1261
1262 let not_f = InterpolantTerm::not(f);
1264 assert!(not_f.is_true());
1265
1266 let not_x = InterpolantTerm::not(x.clone());
1268 let not_not_x = InterpolantTerm::not(not_x);
1269 assert_eq!(not_not_x, x);
1270 }
1271
1272 #[test]
1273 fn test_interpolant_term_implies() {
1274 let t = InterpolantTerm::true_val();
1275 let f = InterpolantTerm::false_val();
1276 let x = InterpolantTerm::var("x");
1277
1278 let imp1 = InterpolantTerm::implies(f.clone(), x.clone());
1280 assert!(imp1.is_true());
1281
1282 let imp2 = InterpolantTerm::implies(t.clone(), x.clone());
1284 assert_eq!(imp2, x);
1285
1286 let imp3 = InterpolantTerm::implies(x.clone(), t);
1288 assert!(imp3.is_true());
1289 }
1290
1291 #[test]
1292 fn test_interpolant_term_display() {
1293 let x = InterpolantTerm::var("x");
1294 let y = InterpolantTerm::var("y");
1295 let and = InterpolantTerm::and(vec![x.clone(), y.clone()]);
1296
1297 assert_eq!(format!("{}", and), "(and x y)");
1298
1299 let or = InterpolantTerm::or(vec![x, y]);
1300 assert_eq!(format!("{}", or), "(or x y)");
1301 }
1302
1303 #[test]
1304 fn test_symbol_collection() {
1305 let x = InterpolantTerm::var("x");
1306 let y = InterpolantTerm::var("y");
1307 let and = InterpolantTerm::and(vec![x, y]);
1308
1309 let mut symbols = FxHashSet::default();
1310 and.collect_symbols(&mut symbols);
1311
1312 assert_eq!(symbols.len(), 2);
1313 assert!(symbols.contains(&Symbol::var("x")));
1314 assert!(symbols.contains(&Symbol::var("y")));
1315 }
1316
1317 #[test]
1318 fn test_interpolant_simplify() {
1319 let x = InterpolantTerm::var("x");
1320 let t = InterpolantTerm::true_val();
1321
1322 let term = InterpolantTerm::and(vec![t, x.clone()]);
1323 let simplified = term.simplify();
1324 assert_eq!(simplified, x);
1325 }
1326
1327 #[test]
1328 fn test_partition_creation() {
1329 let partition = InterpolantPartition::new(
1330 vec![PremiseId(0), PremiseId(1)],
1331 vec![PremiseId(2), PremiseId(3)],
1332 );
1333
1334 assert!(partition.is_a_premise(PremiseId(0)));
1335 assert!(partition.is_a_premise(PremiseId(1)));
1336 assert!(!partition.is_a_premise(PremiseId(2)));
1337
1338 assert!(partition.is_b_premise(PremiseId(2)));
1339 assert!(partition.is_b_premise(PremiseId(3)));
1340 assert!(!partition.is_b_premise(PremiseId(0)));
1341 }
1342
1343 #[test]
1344 fn test_interpolation_config_default() {
1345 let config = InterpolationConfig::default();
1346
1347 assert_eq!(config.algorithm, InterpolationAlgorithm::Pudlak);
1348 assert!(config.use_theory_interpolants);
1349 assert!(config.simplify_interpolants);
1350 assert!(config.enable_caching);
1351 }
1352
1353 #[test]
1354 fn test_interpolation_stats_default() {
1355 let stats = InterpolationStats::default();
1356
1357 assert_eq!(stats.nodes_processed, 0);
1358 assert_eq!(stats.a_nodes, 0);
1359 assert_eq!(stats.b_nodes, 0);
1360 assert_eq!(stats.ab_nodes, 0);
1361 }
1362
1363 #[test]
1364 fn test_lia_interpolator() {
1365 let interp = LiaInterpolator;
1366
1367 assert_eq!(interp.name(), "LIA");
1368 assert!(interp.can_handle(&["x + y <= 10"]));
1369 assert!(!interp.can_handle(&["p and q"]));
1370 }
1371
1372 #[test]
1373 fn test_euf_interpolator() {
1374 let interp = EufInterpolator;
1375
1376 assert_eq!(interp.name(), "EUF");
1377 assert!(interp.can_handle(&["f(x) = y"]));
1378 assert!(interp.can_handle(&["x = y"]));
1379 }
1380
1381 #[test]
1382 fn test_array_interpolator() {
1383 let interp = ArrayInterpolator;
1384
1385 assert_eq!(interp.name(), "Array");
1386 assert!(interp.can_handle(&["select(a, i)"]));
1387 assert!(interp.can_handle(&["store(a, i, v)"]));
1388 }
1389
1390 #[test]
1391 fn test_tree_node() {
1392 let node = TreeNode {
1393 id: 0,
1394 formula: InterpolantTerm::var("x"),
1395 children: vec![1, 2],
1396 parent: None,
1397 };
1398
1399 assert_eq!(node.id, 0);
1400 assert_eq!(node.children.len(), 2);
1401 assert!(node.parent.is_none());
1402 }
1403
1404 #[test]
1405 fn test_sequence_interpolator_too_few() {
1406 let seq = SequenceInterpolator::default();
1407 let result = seq.interpolate_sequence(&[]);
1408
1409 assert!(matches!(result, Err(InterpolationError::TooFewFormulas)));
1410 }
1411
1412 #[test]
1413 fn test_interpolation_error_display() {
1414 let err = InterpolationError::NoRoot;
1415 assert_eq!(format!("{}", err), "Proof has no root");
1416
1417 let err2 = InterpolationError::NodeNotFound(ProofNodeId(5));
1418 assert!(format!("{}", err2).contains("not found"));
1419 }
1420
1421 #[test]
1422 fn test_color_display() {
1423 assert_eq!(format!("{}", InterpolantColor::A), "A");
1424 assert_eq!(format!("{}", InterpolantColor::B), "B");
1425 assert_eq!(format!("{}", InterpolantColor::AB), "AB");
1426 }
1427
1428 #[test]
1429 fn test_mcmillan_basic() {
1430 let config = InterpolationConfig {
1431 algorithm: InterpolationAlgorithm::McMillan,
1432 ..Default::default()
1433 };
1434 let partition = InterpolantPartition::new(vec![PremiseId(0)], vec![PremiseId(1)]);
1435 let interpolator = CraigInterpolator::new(config, partition, PremiseTracker::new());
1436
1437 let a_interp = interpolator.compute_axiom_interpolant(InterpolantColor::A, "p");
1439 assert!(a_interp.is_true());
1440
1441 let b_interp = interpolator.compute_axiom_interpolant(InterpolantColor::B, "q");
1442 assert!(b_interp.is_false());
1443 }
1444
1445 #[test]
1446 fn test_pudlak_basic() {
1447 let config = InterpolationConfig {
1448 algorithm: InterpolationAlgorithm::Pudlak,
1449 ..Default::default()
1450 };
1451 let partition = InterpolantPartition::new(vec![PremiseId(0)], vec![PremiseId(1)]);
1452 let interpolator = CraigInterpolator::new(config, partition, PremiseTracker::new());
1453
1454 let a_interp = interpolator.compute_axiom_interpolant(InterpolantColor::A, "p");
1455 assert!(a_interp.is_true());
1456 }
1457
1458 #[test]
1459 fn test_huang_basic() {
1460 let config = InterpolationConfig {
1461 algorithm: InterpolationAlgorithm::Huang,
1462 ..Default::default()
1463 };
1464 let partition = InterpolantPartition::new(vec![PremiseId(0)], vec![PremiseId(1)]);
1465 let interpolator = CraigInterpolator::new(config, partition, PremiseTracker::new());
1466
1467 let a_interp = interpolator.compute_axiom_interpolant(InterpolantColor::A, "p");
1468 assert!(a_interp.is_true());
1469 }
1470
1471 #[test]
1472 fn test_tree_interpolator_empty() {
1473 let tree_interp = TreeInterpolator::default();
1474 let result = tree_interp.interpolate_tree(&[]);
1475
1476 assert!(result.is_ok());
1477 let interps = result.expect("Should succeed");
1478 assert!(interps.is_empty());
1479 }
1480
1481 #[test]
1482 fn test_tree_interpolator_single_leaf() {
1483 let tree_interp = TreeInterpolator::default();
1484 let nodes = vec![TreeNode {
1485 id: 0,
1486 formula: InterpolantTerm::var("x"),
1487 children: vec![],
1488 parent: None,
1489 }];
1490
1491 let result = tree_interp.interpolate_tree(&nodes);
1492 assert!(result.is_ok());
1493
1494 let interps = result.expect("Should succeed");
1495 assert_eq!(interps.len(), 1);
1496 assert!(interps.contains_key(&0));
1497 }
1498
1499 #[test]
1500 fn test_nested_and_or() {
1501 let x = InterpolantTerm::var("x");
1502 let y = InterpolantTerm::var("y");
1503 let z = InterpolantTerm::var("z");
1504
1505 let inner = InterpolantTerm::and(vec![x.clone(), y.clone()]);
1507 let outer = InterpolantTerm::and(vec![inner, z.clone()]);
1508
1509 match outer {
1510 InterpolantTerm::And(args) => assert_eq!(args.len(), 3),
1511 _ => panic!("Expected flattened And"),
1512 }
1513 }
1514
1515 #[test]
1516 fn test_num_term() {
1517 use num_bigint::BigInt;
1518
1519 let one = InterpolantTerm::Num(BigRational::from_integer(BigInt::from(1)));
1520 let two = InterpolantTerm::Num(BigRational::from_integer(BigInt::from(2)));
1521
1522 let add = InterpolantTerm::Add(vec![one.clone(), two.clone()]);
1523 assert_eq!(format!("{}", add), "(+ 1 2)");
1524
1525 let mul = InterpolantTerm::Mul(vec![one, two]);
1526 assert_eq!(format!("{}", mul), "(* 1 2)");
1527 }
1528
1529 #[test]
1530 fn test_select_store_display() {
1531 let a = InterpolantTerm::var("a");
1532 let i = InterpolantTerm::var("i");
1533 let v = InterpolantTerm::var("v");
1534
1535 let select = InterpolantTerm::Select(Box::new(a.clone()), Box::new(i.clone()));
1536 assert_eq!(format!("{}", select), "(select a i)");
1537
1538 let store = InterpolantTerm::Store(Box::new(a), Box::new(i), Box::new(v));
1539 assert_eq!(format!("{}", store), "(store a i v)");
1540 }
1541
1542 #[test]
1543 fn test_shared_symbols() {
1544 let mut partition = InterpolantPartition::new(vec![PremiseId(0)], vec![PremiseId(1)]);
1545
1546 let x = Symbol::var("x");
1547 let y = Symbol::var("y");
1548
1549 partition.set_shared_symbols(vec![x.clone()]);
1550
1551 assert!(partition.is_shared(&x));
1552 assert!(!partition.is_shared(&y));
1553 }
1554
1555 #[test]
1556 fn test_interpolation_algorithms() {
1557 assert_ne!(
1559 InterpolationAlgorithm::McMillan,
1560 InterpolationAlgorithm::Pudlak
1561 );
1562 assert_ne!(
1563 InterpolationAlgorithm::Pudlak,
1564 InterpolationAlgorithm::Huang
1565 );
1566 assert_ne!(
1567 InterpolationAlgorithm::McMillan,
1568 InterpolationAlgorithm::Huang
1569 );
1570 }
1571
1572 #[test]
1573 fn test_mcmillan_inference_ab_ab() {
1574 let config = InterpolationConfig {
1575 algorithm: InterpolationAlgorithm::McMillan,
1576 ..Default::default()
1577 };
1578 let partition = InterpolantPartition::new(vec![PremiseId(0)], vec![PremiseId(1)]);
1579 let interpolator = CraigInterpolator::new(config, partition, PremiseTracker::new());
1580
1581 let x = InterpolantTerm::var("x");
1582 let y = InterpolantTerm::var("y");
1583 let premises = vec![x.clone(), y.clone()];
1584 let colors = vec![InterpolantColor::AB, InterpolantColor::AB];
1585
1586 let result = interpolator.mcmillan_interpolant(
1587 "resolution",
1588 &premises,
1589 &colors,
1590 InterpolantColor::AB,
1591 );
1592
1593 match result {
1595 InterpolantTerm::Or(args) => assert_eq!(args.len(), 2),
1596 _ => panic!("Expected Or"),
1597 }
1598 }
1599
1600 #[test]
1601 fn test_huang_inference_ab_ab() {
1602 let config = InterpolationConfig {
1603 algorithm: InterpolationAlgorithm::Huang,
1604 ..Default::default()
1605 };
1606 let partition = InterpolantPartition::new(vec![PremiseId(0)], vec![PremiseId(1)]);
1607 let interpolator = CraigInterpolator::new(config, partition, PremiseTracker::new());
1608
1609 let x = InterpolantTerm::var("x");
1610 let y = InterpolantTerm::var("y");
1611 let premises = vec![x.clone(), y.clone()];
1612 let colors = vec![InterpolantColor::AB, InterpolantColor::AB];
1613
1614 let result =
1615 interpolator.huang_interpolant("resolution", &premises, &colors, InterpolantColor::AB);
1616
1617 match result {
1619 InterpolantTerm::And(args) => assert_eq!(args.len(), 2),
1620 _ => panic!("Expected And"),
1621 }
1622 }
1623}