1#[allow(unused_imports)]
33use crate::prelude::*;
34pub type TermId = u32;
36
37pub type TheoryId = u32;
39
40pub type DecisionLevel = u32;
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
45pub struct Literal {
46 pub term: TermId,
48 pub polarity: bool,
50}
51
52impl Literal {
53 pub fn positive(term: TermId) -> Self {
55 Self {
56 term,
57 polarity: true,
58 }
59 }
60
61 pub fn negative(term: TermId) -> Self {
63 Self {
64 term,
65 polarity: false,
66 }
67 }
68
69 pub fn negate(self) -> Self {
71 Self {
72 term: self.term,
73 polarity: !self.polarity,
74 }
75 }
76}
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
80pub struct Equality {
81 pub lhs: TermId,
83 pub rhs: TermId,
85}
86
87impl Equality {
88 pub fn new(lhs: TermId, rhs: TermId) -> Self {
90 if lhs <= rhs {
91 Self { lhs, rhs }
92 } else {
93 Self { lhs: rhs, rhs: lhs }
94 }
95 }
96}
97
98#[derive(Debug, Clone)]
100pub enum Explanation {
101 Given,
103
104 TheoryPropagation {
106 theory: TheoryId,
108 antecedents: Vec<Literal>,
110 },
111
112 EqualityPropagation {
114 equalities: Vec<Equality>,
116 support: Vec<Literal>,
118 },
119
120 Transitivity {
122 chain: Vec<Equality>,
124 },
125
126 Congruence {
128 function: TermId,
130 arg_equalities: Vec<Equality>,
132 },
133}
134
135#[derive(Debug, Clone)]
137pub struct TheoryConflict {
138 pub theory: TheoryId,
140
141 pub literals: Vec<Literal>,
143
144 pub explanation: Explanation,
146
147 pub level: DecisionLevel,
149}
150
151#[derive(Debug, Clone)]
153pub struct ConflictClause {
154 pub literals: Vec<Literal>,
156
157 pub uip: Option<Literal>,
159
160 pub backtrack_level: DecisionLevel,
162
163 pub theories: FxHashSet<TheoryId>,
165
166 pub activity: f64,
168}
169
170#[derive(Debug, Clone)]
172pub struct ConflictAnalysis {
173 pub clause: ConflictClause,
175
176 pub explanation: Explanation,
178
179 pub blamed_theories: FxHashSet<TheoryId>,
181}
182
183#[derive(Debug, Clone)]
185pub struct ConflictResolutionConfig {
186 pub enable_minimization: bool,
188
189 pub enable_uip: bool,
191
192 pub minimization_algorithm: MinimizationAlgorithm,
194
195 pub max_resolution_steps: usize,
197
198 pub track_theory_blame: bool,
200
201 pub enable_learning: bool,
203}
204
205impl Default for ConflictResolutionConfig {
206 fn default() -> Self {
207 Self {
208 enable_minimization: true,
209 enable_uip: true,
210 minimization_algorithm: MinimizationAlgorithm::Recursive,
211 max_resolution_steps: 1000,
212 track_theory_blame: true,
213 enable_learning: true,
214 }
215 }
216}
217
218#[derive(Debug, Clone, Copy, PartialEq, Eq)]
220pub enum MinimizationAlgorithm {
221 None,
223 Simple,
225 Recursive,
227 BinaryResolution,
229}
230
231#[derive(Debug, Clone, Default)]
233pub struct ConflictResolutionStats {
234 pub conflicts_analyzed: u64,
236 pub clauses_learned: u64,
238 pub literals_minimized: u64,
240 pub uip_conflicts: u64,
242 pub resolution_steps: u64,
244 pub theory_blames: u64,
246}
247
248pub struct ConflictResolver {
250 config: ConflictResolutionConfig,
252
253 stats: ConflictResolutionStats,
255
256 trail: Vec<(Literal, DecisionLevel, Explanation)>,
258
259 literal_position: FxHashMap<Literal, usize>,
261
262 level_boundaries: FxHashMap<DecisionLevel, usize>,
264
265 current_level: DecisionLevel,
267
268 learned_clauses: Vec<ConflictClause>,
270
271 theory_blame: FxHashMap<TheoryId, u64>,
273}
274
275impl ConflictResolver {
276 pub fn new() -> Self {
278 Self::with_config(ConflictResolutionConfig::default())
279 }
280
281 pub fn with_config(config: ConflictResolutionConfig) -> Self {
283 let mut level_boundaries = FxHashMap::default();
284 level_boundaries.insert(0, 0);
286
287 Self {
288 config,
289 stats: ConflictResolutionStats::default(),
290 trail: Vec::new(),
291 literal_position: FxHashMap::default(),
292 level_boundaries,
293 current_level: 0,
294 learned_clauses: Vec::new(),
295 theory_blame: FxHashMap::default(),
296 }
297 }
298
299 pub fn stats(&self) -> &ConflictResolutionStats {
301 &self.stats
302 }
303
304 pub fn add_assignment(
306 &mut self,
307 literal: Literal,
308 level: DecisionLevel,
309 explanation: Explanation,
310 ) {
311 let position = self.trail.len();
312 self.trail.push((literal, level, explanation));
313 self.literal_position.insert(literal, position);
314
315 self.level_boundaries.entry(level).or_insert(position);
316 }
317
318 pub fn push_decision_level(&mut self) {
320 self.current_level += 1;
321 }
322
323 pub fn backtrack(&mut self, level: DecisionLevel) -> Result<(), String> {
325 if level > self.current_level {
326 return Err("Cannot backtrack to future level".to_string());
327 }
328
329 let backtrack_pos = self
331 .level_boundaries
332 .get(&level)
333 .copied()
334 .unwrap_or(self.trail.len());
335
336 self.trail.truncate(backtrack_pos);
338
339 self.literal_position.clear();
341 for (i, &(literal, _, _)) in self.trail.iter().enumerate() {
342 self.literal_position.insert(literal, i);
343 }
344
345 self.level_boundaries.retain(|&l, _| l <= level);
347
348 self.current_level = level;
349 Ok(())
350 }
351
352 pub fn analyze_conflict(
354 &mut self,
355 conflict: TheoryConflict,
356 ) -> Result<ConflictAnalysis, String> {
357 self.stats.conflicts_analyzed += 1;
358
359 if self.config.track_theory_blame {
360 *self.theory_blame.entry(conflict.theory).or_insert(0) += 1;
361 self.stats.theory_blames += 1;
362 }
363
364 let mut conflict_literals = conflict.literals.clone();
366
367 if self.config.enable_uip {
369 conflict_literals = self.find_uip(&conflict_literals, conflict.level)?;
370 self.stats.uip_conflicts += 1;
371 }
372
373 if self.config.enable_minimization {
375 let before_size = conflict_literals.len();
376 conflict_literals = self.minimize_conflict(&conflict_literals)?;
377 let after_size = conflict_literals.len();
378 self.stats.literals_minimized += (before_size - after_size) as u64;
379 }
380
381 let backtrack_level = self.compute_backtrack_level(&conflict_literals, conflict.level)?;
383
384 let clause = ConflictClause {
386 literals: conflict_literals.clone(),
387 uip: self.find_uip_literal(&conflict_literals),
388 backtrack_level,
389 theories: {
390 let mut theories = FxHashSet::default();
391 theories.insert(conflict.theory);
392 theories
393 },
394 activity: 1.0,
395 };
396
397 if self.config.enable_learning {
399 self.learned_clauses.push(clause.clone());
400 self.stats.clauses_learned += 1;
401 }
402
403 Ok(ConflictAnalysis {
404 clause,
405 explanation: conflict.explanation,
406 blamed_theories: {
407 let mut theories = FxHashSet::default();
408 theories.insert(conflict.theory);
409 theories
410 },
411 })
412 }
413
414 fn find_uip(
416 &mut self,
417 literals: &[Literal],
418 level: DecisionLevel,
419 ) -> Result<Vec<Literal>, String> {
420 let mut current_clause: FxHashSet<Literal> = literals.iter().copied().collect();
421 let mut seen = FxHashSet::default();
422 let mut counter = 0;
423
424 for &lit in ¤t_clause {
426 if self.get_decision_level(lit) == Some(level) {
427 counter += 1;
428 }
429 }
430
431 for _ in 0..self.config.max_resolution_steps {
433 self.stats.resolution_steps += 1;
434
435 if counter <= 1 {
436 break; }
438
439 let resolve_lit = self.find_resolution_literal(¤t_clause, level, &seen)?;
441 seen.insert(resolve_lit);
442
443 let reason = self.get_reason(resolve_lit)?;
445
446 current_clause.remove(&resolve_lit);
448 counter -= 1;
449
450 for &lit in &reason {
451 if !current_clause.contains(&lit) {
452 current_clause.insert(lit);
453 if self.get_decision_level(lit) == Some(level) {
454 counter += 1;
455 }
456 }
457 }
458 }
459
460 Ok(current_clause.into_iter().collect())
461 }
462
463 fn find_resolution_literal(
465 &self,
466 clause: &FxHashSet<Literal>,
467 level: DecisionLevel,
468 seen: &FxHashSet<Literal>,
469 ) -> Result<Literal, String> {
470 for &(literal, lit_level, _) in self.trail.iter().rev() {
472 if lit_level == level && clause.contains(&literal) && !seen.contains(&literal) {
473 return Ok(literal);
474 }
475 }
476
477 Err("No resolution literal found".to_string())
478 }
479
480 fn get_decision_level(&self, literal: Literal) -> Option<DecisionLevel> {
482 self.literal_position
483 .get(&literal)
484 .and_then(|&pos| self.trail.get(pos))
485 .map(|(_, level, _)| *level)
486 }
487
488 fn get_reason(&self, literal: Literal) -> Result<Vec<Literal>, String> {
490 let position = self
491 .literal_position
492 .get(&literal)
493 .ok_or("Literal not in trail")?;
494
495 let (_, _, explanation) = &self.trail[*position];
496
497 match explanation {
498 Explanation::TheoryPropagation { antecedents, .. } => Ok(antecedents.clone()),
499 Explanation::EqualityPropagation { support, .. } => Ok(support.clone()),
500 _ => Ok(Vec::new()),
501 }
502 }
503
504 fn minimize_conflict(&self, literals: &[Literal]) -> Result<Vec<Literal>, String> {
506 match self.config.minimization_algorithm {
507 MinimizationAlgorithm::None => Ok(literals.to_vec()),
508 MinimizationAlgorithm::Simple => self.minimize_simple(literals),
509 MinimizationAlgorithm::Recursive => self.minimize_recursive(literals),
510 MinimizationAlgorithm::BinaryResolution => self.minimize_binary_resolution(literals),
511 }
512 }
513
514 fn minimize_simple(&self, literals: &[Literal]) -> Result<Vec<Literal>, String> {
516 let mut minimal = Vec::new();
518 let mut seen = FxHashSet::default();
519
520 for &lit in literals {
521 if !seen.contains(&lit) {
522 seen.insert(lit);
523 minimal.push(lit);
524 }
525 }
526
527 Ok(minimal)
528 }
529
530 fn minimize_recursive(&self, literals: &[Literal]) -> Result<Vec<Literal>, String> {
532 let mut minimal = Vec::new();
533 let mut redundant = FxHashSet::default();
534
535 for &lit in literals {
536 if self.is_redundant(lit, literals, &mut redundant)? {
537 continue;
538 }
539 minimal.push(lit);
540 }
541
542 Ok(minimal)
543 }
544
545 fn is_redundant(
547 &self,
548 literal: Literal,
549 clause: &[Literal],
550 redundant: &mut FxHashSet<Literal>,
551 ) -> Result<bool, String> {
552 if redundant.contains(&literal) {
553 return Ok(true);
554 }
555
556 let reason = self.get_reason(literal).ok().unwrap_or_default();
557
558 for &reason_lit in &reason {
559 if !clause.contains(&reason_lit)
560 && !redundant.contains(&reason_lit)
561 && !self.is_redundant(reason_lit, clause, redundant)?
562 {
563 return Ok(false);
564 }
565 }
566
567 redundant.insert(literal);
568 Ok(true)
569 }
570
571 fn minimize_binary_resolution(&self, literals: &[Literal]) -> Result<Vec<Literal>, String> {
573 self.minimize_simple(literals)
575 }
576
577 fn compute_backtrack_level(
579 &self,
580 literals: &[Literal],
581 _conflict_level: DecisionLevel,
582 ) -> Result<DecisionLevel, String> {
583 let mut levels: Vec<DecisionLevel> = literals
585 .iter()
586 .filter_map(|&lit| self.get_decision_level(lit))
587 .collect();
588
589 levels.sort_unstable();
590 levels.dedup();
591
592 if levels.len() >= 2 {
593 Ok(levels[levels.len() - 2])
594 } else if !levels.is_empty() {
595 Ok(levels[0].saturating_sub(1))
596 } else {
597 Ok(0)
598 }
599 }
600
601 fn find_uip_literal(&self, literals: &[Literal]) -> Option<Literal> {
603 literals
605 .iter()
606 .max_by_key(|&&lit| self.get_decision_level(lit).unwrap_or(0))
607 .copied()
608 }
609
610 pub fn learned_clauses(&self) -> &[ConflictClause] {
612 &self.learned_clauses
613 }
614
615 pub fn theory_blame(&self) -> &FxHashMap<TheoryId, u64> {
617 &self.theory_blame
618 }
619
620 pub fn clear(&mut self) {
622 self.trail.clear();
623 self.literal_position.clear();
624 self.level_boundaries.clear();
625 self.current_level = 0;
626 self.learned_clauses.clear();
627 self.theory_blame.clear();
628 }
629
630 pub fn reset_stats(&mut self) {
632 self.stats = ConflictResolutionStats::default();
633 }
634}
635
636impl Default for ConflictResolver {
637 fn default() -> Self {
638 Self::new()
639 }
640}
641
642pub struct ExplanationGenerator {
644 cache: FxHashMap<Literal, Explanation>,
646}
647
648impl ExplanationGenerator {
649 pub fn new() -> Self {
651 Self {
652 cache: FxHashMap::default(),
653 }
654 }
655
656 pub fn add_explanation(&mut self, literal: Literal, explanation: Explanation) {
658 self.cache.insert(literal, explanation);
659 }
660
661 pub fn get_explanation(&self, literal: Literal) -> Option<&Explanation> {
663 self.cache.get(&literal)
664 }
665
666 pub fn build_chain(&self, literals: &[Literal]) -> Explanation {
668 let mut antecedents = Vec::new();
669
670 for &lit in literals {
671 if let Some(explanation) = self.cache.get(&lit)
672 && let Explanation::TheoryPropagation {
673 antecedents: ants, ..
674 } = explanation
675 {
676 antecedents.extend_from_slice(ants);
677 }
678 }
679
680 Explanation::TheoryPropagation {
681 theory: 0,
682 antecedents,
683 }
684 }
685
686 pub fn clear(&mut self) {
688 self.cache.clear();
689 }
690}
691
692impl Default for ExplanationGenerator {
693 fn default() -> Self {
694 Self::new()
695 }
696}
697
698pub struct MultiTheoryConflictAnalyzer {
700 resolvers: FxHashMap<TheoryId, ConflictResolver>,
702
703 combined_stats: ConflictResolutionStats,
705}
706
707impl MultiTheoryConflictAnalyzer {
708 pub fn new() -> Self {
710 Self {
711 resolvers: FxHashMap::default(),
712 combined_stats: ConflictResolutionStats::default(),
713 }
714 }
715
716 pub fn register_theory(&mut self, theory: TheoryId, config: ConflictResolutionConfig) {
718 self.resolvers
719 .insert(theory, ConflictResolver::with_config(config));
720 }
721
722 pub fn analyze(&mut self, conflict: TheoryConflict) -> Result<ConflictAnalysis, String> {
724 let resolver = self
725 .resolvers
726 .get_mut(&conflict.theory)
727 .ok_or("Theory not registered")?;
728
729 let analysis = resolver.analyze_conflict(conflict)?;
730
731 self.combined_stats.conflicts_analyzed += 1;
733
734 Ok(analysis)
735 }
736
737 pub fn combined_stats(&self) -> &ConflictResolutionStats {
739 &self.combined_stats
740 }
741
742 pub fn get_resolver(&self, theory: TheoryId) -> Option<&ConflictResolver> {
744 self.resolvers.get(&theory)
745 }
746
747 pub fn clear(&mut self) {
749 for resolver in self.resolvers.values_mut() {
750 resolver.clear();
751 }
752 self.combined_stats = ConflictResolutionStats::default();
753 }
754}
755
756impl Default for MultiTheoryConflictAnalyzer {
757 fn default() -> Self {
758 Self::new()
759 }
760}
761
762#[cfg(test)]
763mod tests {
764 use super::*;
765
766 #[test]
767 fn test_literal_creation() {
768 let lit = Literal::positive(1);
769 assert_eq!(lit.term, 1);
770 assert!(lit.polarity);
771 }
772
773 #[test]
774 fn test_literal_negation() {
775 let lit = Literal::positive(1);
776 let neg = lit.negate();
777 assert!(!neg.polarity);
778 }
779
780 #[test]
781 fn test_resolver_creation() {
782 let resolver = ConflictResolver::new();
783 assert_eq!(resolver.stats().conflicts_analyzed, 0);
784 }
785
786 #[test]
787 fn test_add_assignment() {
788 let mut resolver = ConflictResolver::new();
789 let lit = Literal::positive(1);
790
791 resolver.add_assignment(lit, 0, Explanation::Given);
792 assert_eq!(resolver.trail.len(), 1);
793 }
794
795 #[test]
796 fn test_decision_level() {
797 let mut resolver = ConflictResolver::new();
798
799 resolver.push_decision_level();
800 assert_eq!(resolver.current_level, 1);
801 }
802
803 #[test]
804 fn test_backtrack() {
805 let mut resolver = ConflictResolver::new();
806
807 resolver.push_decision_level();
808 resolver.add_assignment(Literal::positive(1), 1, Explanation::Given);
809
810 resolver.backtrack(0).expect("Backtrack failed");
811 assert_eq!(resolver.trail.len(), 0);
812 }
813
814 #[test]
815 fn test_conflict_analysis() {
816 let mut resolver = ConflictResolver::new();
817
818 let conflict = TheoryConflict {
819 theory: 0,
820 literals: vec![Literal::positive(1), Literal::negative(2)],
821 explanation: Explanation::Given,
822 level: 0,
823 };
824
825 let analysis = resolver.analyze_conflict(conflict);
826 assert!(analysis.is_ok());
827 }
828
829 #[test]
830 fn test_explanation_generator() {
831 let mut generator = ExplanationGenerator::new();
832 let lit = Literal::positive(1);
833
834 generator.add_explanation(lit, Explanation::Given);
835 assert!(generator.get_explanation(lit).is_some());
836 }
837
838 #[test]
839 fn test_multi_theory_analyzer() {
840 let mut analyzer = MultiTheoryConflictAnalyzer::new();
841 analyzer.register_theory(0, ConflictResolutionConfig::default());
842
843 let conflict = TheoryConflict {
844 theory: 0,
845 literals: vec![Literal::positive(1)],
846 explanation: Explanation::Given,
847 level: 0,
848 };
849
850 let result = analyzer.analyze(conflict);
851 assert!(result.is_ok());
852 }
853
854 #[test]
855 fn test_simple_minimization() {
856 let resolver = ConflictResolver::new();
857
858 let literals = vec![
859 Literal::positive(1),
860 Literal::positive(2),
861 Literal::positive(1), ];
863
864 let minimized = resolver
865 .minimize_simple(&literals)
866 .expect("Minimization failed");
867 assert_eq!(minimized.len(), 2);
868 }
869
870 #[test]
871 fn test_backtrack_level_computation() {
872 let mut resolver = ConflictResolver::new();
873
874 resolver.add_assignment(Literal::positive(1), 0, Explanation::Given);
875 resolver.push_decision_level();
876 resolver.add_assignment(Literal::positive(2), 1, Explanation::Given);
877 resolver.push_decision_level();
878 resolver.add_assignment(Literal::positive(3), 2, Explanation::Given);
879
880 let literals = vec![
881 Literal::positive(1),
882 Literal::positive(2),
883 Literal::positive(3),
884 ];
885
886 let level = resolver.compute_backtrack_level(&literals, 2);
887 assert!(level.is_ok());
888 }
889}