1use rustc_hash::{FxHashMap, FxHashSet};
32
33pub type TermId = u32;
35
36pub type TheoryId = u32;
38
39pub type DecisionLevel = u32;
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
44pub struct Literal {
45 pub term: TermId,
47 pub polarity: bool,
49}
50
51impl Literal {
52 pub fn positive(term: TermId) -> Self {
54 Self {
55 term,
56 polarity: true,
57 }
58 }
59
60 pub fn negative(term: TermId) -> Self {
62 Self {
63 term,
64 polarity: false,
65 }
66 }
67
68 pub fn negate(self) -> Self {
70 Self {
71 term: self.term,
72 polarity: !self.polarity,
73 }
74 }
75}
76
77#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
79pub struct Equality {
80 pub lhs: TermId,
82 pub rhs: TermId,
84}
85
86impl Equality {
87 pub fn new(lhs: TermId, rhs: TermId) -> Self {
89 if lhs <= rhs {
90 Self { lhs, rhs }
91 } else {
92 Self { lhs: rhs, rhs: lhs }
93 }
94 }
95}
96
97#[derive(Debug, Clone)]
99pub enum Explanation {
100 Given,
102
103 TheoryPropagation {
105 theory: TheoryId,
107 antecedents: Vec<Literal>,
109 },
110
111 EqualityPropagation {
113 equalities: Vec<Equality>,
115 support: Vec<Literal>,
117 },
118
119 Transitivity {
121 chain: Vec<Equality>,
123 },
124
125 Congruence {
127 function: TermId,
129 arg_equalities: Vec<Equality>,
131 },
132}
133
134#[derive(Debug, Clone)]
136pub struct TheoryConflict {
137 pub theory: TheoryId,
139
140 pub literals: Vec<Literal>,
142
143 pub explanation: Explanation,
145
146 pub level: DecisionLevel,
148}
149
150#[derive(Debug, Clone)]
152pub struct ConflictClause {
153 pub literals: Vec<Literal>,
155
156 pub uip: Option<Literal>,
158
159 pub backtrack_level: DecisionLevel,
161
162 pub theories: FxHashSet<TheoryId>,
164
165 pub activity: f64,
167}
168
169#[derive(Debug, Clone)]
171pub struct ConflictAnalysis {
172 pub clause: ConflictClause,
174
175 pub explanation: Explanation,
177
178 pub blamed_theories: FxHashSet<TheoryId>,
180}
181
182#[derive(Debug, Clone)]
184pub struct ConflictResolutionConfig {
185 pub enable_minimization: bool,
187
188 pub enable_uip: bool,
190
191 pub minimization_algorithm: MinimizationAlgorithm,
193
194 pub max_resolution_steps: usize,
196
197 pub track_theory_blame: bool,
199
200 pub enable_learning: bool,
202}
203
204impl Default for ConflictResolutionConfig {
205 fn default() -> Self {
206 Self {
207 enable_minimization: true,
208 enable_uip: true,
209 minimization_algorithm: MinimizationAlgorithm::Recursive,
210 max_resolution_steps: 1000,
211 track_theory_blame: true,
212 enable_learning: true,
213 }
214 }
215}
216
217#[derive(Debug, Clone, Copy, PartialEq, Eq)]
219pub enum MinimizationAlgorithm {
220 None,
222 Simple,
224 Recursive,
226 BinaryResolution,
228}
229
230#[derive(Debug, Clone, Default)]
232pub struct ConflictResolutionStats {
233 pub conflicts_analyzed: u64,
235 pub clauses_learned: u64,
237 pub literals_minimized: u64,
239 pub uip_conflicts: u64,
241 pub resolution_steps: u64,
243 pub theory_blames: u64,
245}
246
247pub struct ConflictResolver {
249 config: ConflictResolutionConfig,
251
252 stats: ConflictResolutionStats,
254
255 trail: Vec<(Literal, DecisionLevel, Explanation)>,
257
258 literal_position: FxHashMap<Literal, usize>,
260
261 level_boundaries: FxHashMap<DecisionLevel, usize>,
263
264 current_level: DecisionLevel,
266
267 learned_clauses: Vec<ConflictClause>,
269
270 theory_blame: FxHashMap<TheoryId, u64>,
272}
273
274impl ConflictResolver {
275 pub fn new() -> Self {
277 Self::with_config(ConflictResolutionConfig::default())
278 }
279
280 pub fn with_config(config: ConflictResolutionConfig) -> Self {
282 let mut level_boundaries = FxHashMap::default();
283 level_boundaries.insert(0, 0);
285
286 Self {
287 config,
288 stats: ConflictResolutionStats::default(),
289 trail: Vec::new(),
290 literal_position: FxHashMap::default(),
291 level_boundaries,
292 current_level: 0,
293 learned_clauses: Vec::new(),
294 theory_blame: FxHashMap::default(),
295 }
296 }
297
298 pub fn stats(&self) -> &ConflictResolutionStats {
300 &self.stats
301 }
302
303 pub fn add_assignment(
305 &mut self,
306 literal: Literal,
307 level: DecisionLevel,
308 explanation: Explanation,
309 ) {
310 let position = self.trail.len();
311 self.trail.push((literal, level, explanation));
312 self.literal_position.insert(literal, position);
313
314 self.level_boundaries.entry(level).or_insert(position);
315 }
316
317 pub fn push_decision_level(&mut self) {
319 self.current_level += 1;
320 }
321
322 pub fn backtrack(&mut self, level: DecisionLevel) -> Result<(), String> {
324 if level > self.current_level {
325 return Err("Cannot backtrack to future level".to_string());
326 }
327
328 let backtrack_pos = self
330 .level_boundaries
331 .get(&level)
332 .copied()
333 .unwrap_or(self.trail.len());
334
335 self.trail.truncate(backtrack_pos);
337
338 self.literal_position.clear();
340 for (i, &(literal, _, _)) in self.trail.iter().enumerate() {
341 self.literal_position.insert(literal, i);
342 }
343
344 self.level_boundaries.retain(|&l, _| l <= level);
346
347 self.current_level = level;
348 Ok(())
349 }
350
351 pub fn analyze_conflict(
353 &mut self,
354 conflict: TheoryConflict,
355 ) -> Result<ConflictAnalysis, String> {
356 self.stats.conflicts_analyzed += 1;
357
358 if self.config.track_theory_blame {
359 *self.theory_blame.entry(conflict.theory).or_insert(0) += 1;
360 self.stats.theory_blames += 1;
361 }
362
363 let mut conflict_literals = conflict.literals.clone();
365
366 if self.config.enable_uip {
368 conflict_literals = self.find_uip(&conflict_literals, conflict.level)?;
369 self.stats.uip_conflicts += 1;
370 }
371
372 if self.config.enable_minimization {
374 let before_size = conflict_literals.len();
375 conflict_literals = self.minimize_conflict(&conflict_literals)?;
376 let after_size = conflict_literals.len();
377 self.stats.literals_minimized += (before_size - after_size) as u64;
378 }
379
380 let backtrack_level = self.compute_backtrack_level(&conflict_literals, conflict.level)?;
382
383 let clause = ConflictClause {
385 literals: conflict_literals.clone(),
386 uip: self.find_uip_literal(&conflict_literals),
387 backtrack_level,
388 theories: {
389 let mut theories = FxHashSet::default();
390 theories.insert(conflict.theory);
391 theories
392 },
393 activity: 1.0,
394 };
395
396 if self.config.enable_learning {
398 self.learned_clauses.push(clause.clone());
399 self.stats.clauses_learned += 1;
400 }
401
402 Ok(ConflictAnalysis {
403 clause,
404 explanation: conflict.explanation,
405 blamed_theories: {
406 let mut theories = FxHashSet::default();
407 theories.insert(conflict.theory);
408 theories
409 },
410 })
411 }
412
413 fn find_uip(
415 &mut self,
416 literals: &[Literal],
417 level: DecisionLevel,
418 ) -> Result<Vec<Literal>, String> {
419 let mut current_clause: FxHashSet<Literal> = literals.iter().copied().collect();
420 let mut seen = FxHashSet::default();
421 let mut counter = 0;
422
423 for &lit in ¤t_clause {
425 if self.get_decision_level(lit) == Some(level) {
426 counter += 1;
427 }
428 }
429
430 for _ in 0..self.config.max_resolution_steps {
432 self.stats.resolution_steps += 1;
433
434 if counter <= 1 {
435 break; }
437
438 let resolve_lit = self.find_resolution_literal(¤t_clause, level, &seen)?;
440 seen.insert(resolve_lit);
441
442 let reason = self.get_reason(resolve_lit)?;
444
445 current_clause.remove(&resolve_lit);
447 counter -= 1;
448
449 for &lit in &reason {
450 if !current_clause.contains(&lit) {
451 current_clause.insert(lit);
452 if self.get_decision_level(lit) == Some(level) {
453 counter += 1;
454 }
455 }
456 }
457 }
458
459 Ok(current_clause.into_iter().collect())
460 }
461
462 fn find_resolution_literal(
464 &self,
465 clause: &FxHashSet<Literal>,
466 level: DecisionLevel,
467 seen: &FxHashSet<Literal>,
468 ) -> Result<Literal, String> {
469 for &(literal, lit_level, _) in self.trail.iter().rev() {
471 if lit_level == level && clause.contains(&literal) && !seen.contains(&literal) {
472 return Ok(literal);
473 }
474 }
475
476 Err("No resolution literal found".to_string())
477 }
478
479 fn get_decision_level(&self, literal: Literal) -> Option<DecisionLevel> {
481 self.literal_position
482 .get(&literal)
483 .and_then(|&pos| self.trail.get(pos))
484 .map(|(_, level, _)| *level)
485 }
486
487 fn get_reason(&self, literal: Literal) -> Result<Vec<Literal>, String> {
489 let position = self
490 .literal_position
491 .get(&literal)
492 .ok_or("Literal not in trail")?;
493
494 let (_, _, explanation) = &self.trail[*position];
495
496 match explanation {
497 Explanation::TheoryPropagation { antecedents, .. } => Ok(antecedents.clone()),
498 Explanation::EqualityPropagation { support, .. } => Ok(support.clone()),
499 _ => Ok(Vec::new()),
500 }
501 }
502
503 fn minimize_conflict(&self, literals: &[Literal]) -> Result<Vec<Literal>, String> {
505 match self.config.minimization_algorithm {
506 MinimizationAlgorithm::None => Ok(literals.to_vec()),
507 MinimizationAlgorithm::Simple => self.minimize_simple(literals),
508 MinimizationAlgorithm::Recursive => self.minimize_recursive(literals),
509 MinimizationAlgorithm::BinaryResolution => self.minimize_binary_resolution(literals),
510 }
511 }
512
513 fn minimize_simple(&self, literals: &[Literal]) -> Result<Vec<Literal>, String> {
515 let mut minimal = Vec::new();
517 let mut seen = FxHashSet::default();
518
519 for &lit in literals {
520 if !seen.contains(&lit) {
521 seen.insert(lit);
522 minimal.push(lit);
523 }
524 }
525
526 Ok(minimal)
527 }
528
529 fn minimize_recursive(&self, literals: &[Literal]) -> Result<Vec<Literal>, String> {
531 let mut minimal = Vec::new();
532 let mut redundant = FxHashSet::default();
533
534 for &lit in literals {
535 if self.is_redundant(lit, literals, &mut redundant)? {
536 continue;
537 }
538 minimal.push(lit);
539 }
540
541 Ok(minimal)
542 }
543
544 fn is_redundant(
546 &self,
547 literal: Literal,
548 clause: &[Literal],
549 redundant: &mut FxHashSet<Literal>,
550 ) -> Result<bool, String> {
551 if redundant.contains(&literal) {
552 return Ok(true);
553 }
554
555 let reason = self.get_reason(literal).ok().unwrap_or_default();
556
557 for &reason_lit in &reason {
558 if !clause.contains(&reason_lit)
559 && !redundant.contains(&reason_lit)
560 && !self.is_redundant(reason_lit, clause, redundant)?
561 {
562 return Ok(false);
563 }
564 }
565
566 redundant.insert(literal);
567 Ok(true)
568 }
569
570 fn minimize_binary_resolution(&self, literals: &[Literal]) -> Result<Vec<Literal>, String> {
572 self.minimize_simple(literals)
574 }
575
576 fn compute_backtrack_level(
578 &self,
579 literals: &[Literal],
580 _conflict_level: DecisionLevel,
581 ) -> Result<DecisionLevel, String> {
582 let mut levels: Vec<DecisionLevel> = literals
584 .iter()
585 .filter_map(|&lit| self.get_decision_level(lit))
586 .collect();
587
588 levels.sort_unstable();
589 levels.dedup();
590
591 if levels.len() >= 2 {
592 Ok(levels[levels.len() - 2])
593 } else if !levels.is_empty() {
594 Ok(levels[0].saturating_sub(1))
595 } else {
596 Ok(0)
597 }
598 }
599
600 fn find_uip_literal(&self, literals: &[Literal]) -> Option<Literal> {
602 literals
604 .iter()
605 .max_by_key(|&&lit| self.get_decision_level(lit).unwrap_or(0))
606 .copied()
607 }
608
609 pub fn learned_clauses(&self) -> &[ConflictClause] {
611 &self.learned_clauses
612 }
613
614 pub fn theory_blame(&self) -> &FxHashMap<TheoryId, u64> {
616 &self.theory_blame
617 }
618
619 pub fn clear(&mut self) {
621 self.trail.clear();
622 self.literal_position.clear();
623 self.level_boundaries.clear();
624 self.current_level = 0;
625 self.learned_clauses.clear();
626 self.theory_blame.clear();
627 }
628
629 pub fn reset_stats(&mut self) {
631 self.stats = ConflictResolutionStats::default();
632 }
633}
634
635impl Default for ConflictResolver {
636 fn default() -> Self {
637 Self::new()
638 }
639}
640
641pub struct ExplanationGenerator {
643 cache: FxHashMap<Literal, Explanation>,
645}
646
647impl ExplanationGenerator {
648 pub fn new() -> Self {
650 Self {
651 cache: FxHashMap::default(),
652 }
653 }
654
655 pub fn add_explanation(&mut self, literal: Literal, explanation: Explanation) {
657 self.cache.insert(literal, explanation);
658 }
659
660 pub fn get_explanation(&self, literal: Literal) -> Option<&Explanation> {
662 self.cache.get(&literal)
663 }
664
665 pub fn build_chain(&self, literals: &[Literal]) -> Explanation {
667 let mut antecedents = Vec::new();
668
669 for &lit in literals {
670 if let Some(explanation) = self.cache.get(&lit)
671 && let Explanation::TheoryPropagation {
672 antecedents: ants, ..
673 } = explanation
674 {
675 antecedents.extend_from_slice(ants);
676 }
677 }
678
679 Explanation::TheoryPropagation {
680 theory: 0,
681 antecedents,
682 }
683 }
684
685 pub fn clear(&mut self) {
687 self.cache.clear();
688 }
689}
690
691impl Default for ExplanationGenerator {
692 fn default() -> Self {
693 Self::new()
694 }
695}
696
697pub struct MultiTheoryConflictAnalyzer {
699 resolvers: FxHashMap<TheoryId, ConflictResolver>,
701
702 combined_stats: ConflictResolutionStats,
704}
705
706impl MultiTheoryConflictAnalyzer {
707 pub fn new() -> Self {
709 Self {
710 resolvers: FxHashMap::default(),
711 combined_stats: ConflictResolutionStats::default(),
712 }
713 }
714
715 pub fn register_theory(&mut self, theory: TheoryId, config: ConflictResolutionConfig) {
717 self.resolvers
718 .insert(theory, ConflictResolver::with_config(config));
719 }
720
721 pub fn analyze(&mut self, conflict: TheoryConflict) -> Result<ConflictAnalysis, String> {
723 let resolver = self
724 .resolvers
725 .get_mut(&conflict.theory)
726 .ok_or("Theory not registered")?;
727
728 let analysis = resolver.analyze_conflict(conflict)?;
729
730 self.combined_stats.conflicts_analyzed += 1;
732
733 Ok(analysis)
734 }
735
736 pub fn combined_stats(&self) -> &ConflictResolutionStats {
738 &self.combined_stats
739 }
740
741 pub fn get_resolver(&self, theory: TheoryId) -> Option<&ConflictResolver> {
743 self.resolvers.get(&theory)
744 }
745
746 pub fn clear(&mut self) {
748 for resolver in self.resolvers.values_mut() {
749 resolver.clear();
750 }
751 self.combined_stats = ConflictResolutionStats::default();
752 }
753}
754
755impl Default for MultiTheoryConflictAnalyzer {
756 fn default() -> Self {
757 Self::new()
758 }
759}
760
761#[cfg(test)]
762mod tests {
763 use super::*;
764
765 #[test]
766 fn test_literal_creation() {
767 let lit = Literal::positive(1);
768 assert_eq!(lit.term, 1);
769 assert!(lit.polarity);
770 }
771
772 #[test]
773 fn test_literal_negation() {
774 let lit = Literal::positive(1);
775 let neg = lit.negate();
776 assert!(!neg.polarity);
777 }
778
779 #[test]
780 fn test_resolver_creation() {
781 let resolver = ConflictResolver::new();
782 assert_eq!(resolver.stats().conflicts_analyzed, 0);
783 }
784
785 #[test]
786 fn test_add_assignment() {
787 let mut resolver = ConflictResolver::new();
788 let lit = Literal::positive(1);
789
790 resolver.add_assignment(lit, 0, Explanation::Given);
791 assert_eq!(resolver.trail.len(), 1);
792 }
793
794 #[test]
795 fn test_decision_level() {
796 let mut resolver = ConflictResolver::new();
797
798 resolver.push_decision_level();
799 assert_eq!(resolver.current_level, 1);
800 }
801
802 #[test]
803 fn test_backtrack() {
804 let mut resolver = ConflictResolver::new();
805
806 resolver.push_decision_level();
807 resolver.add_assignment(Literal::positive(1), 1, Explanation::Given);
808
809 resolver.backtrack(0).expect("Backtrack failed");
810 assert_eq!(resolver.trail.len(), 0);
811 }
812
813 #[test]
814 fn test_conflict_analysis() {
815 let mut resolver = ConflictResolver::new();
816
817 let conflict = TheoryConflict {
818 theory: 0,
819 literals: vec![Literal::positive(1), Literal::negative(2)],
820 explanation: Explanation::Given,
821 level: 0,
822 };
823
824 let analysis = resolver.analyze_conflict(conflict);
825 assert!(analysis.is_ok());
826 }
827
828 #[test]
829 fn test_explanation_generator() {
830 let mut generator = ExplanationGenerator::new();
831 let lit = Literal::positive(1);
832
833 generator.add_explanation(lit, Explanation::Given);
834 assert!(generator.get_explanation(lit).is_some());
835 }
836
837 #[test]
838 fn test_multi_theory_analyzer() {
839 let mut analyzer = MultiTheoryConflictAnalyzer::new();
840 analyzer.register_theory(0, ConflictResolutionConfig::default());
841
842 let conflict = TheoryConflict {
843 theory: 0,
844 literals: vec![Literal::positive(1)],
845 explanation: Explanation::Given,
846 level: 0,
847 };
848
849 let result = analyzer.analyze(conflict);
850 assert!(result.is_ok());
851 }
852
853 #[test]
854 fn test_simple_minimization() {
855 let resolver = ConflictResolver::new();
856
857 let literals = vec![
858 Literal::positive(1),
859 Literal::positive(2),
860 Literal::positive(1), ];
862
863 let minimized = resolver
864 .minimize_simple(&literals)
865 .expect("Minimization failed");
866 assert_eq!(minimized.len(), 2);
867 }
868
869 #[test]
870 fn test_backtrack_level_computation() {
871 let mut resolver = ConflictResolver::new();
872
873 resolver.add_assignment(Literal::positive(1), 0, Explanation::Given);
874 resolver.push_decision_level();
875 resolver.add_assignment(Literal::positive(2), 1, Explanation::Given);
876 resolver.push_decision_level();
877 resolver.add_assignment(Literal::positive(3), 2, Explanation::Given);
878
879 let literals = vec![
880 Literal::positive(1),
881 Literal::positive(2),
882 Literal::positive(3),
883 ];
884
885 let level = resolver.compute_backtrack_level(&literals, 2);
886 assert!(level.is_ok());
887 }
888}