1#[allow(unused_imports)]
4use crate::prelude::*;
5use num_rational::Rational64;
6use oxiz_core::ast::{RoundingMode, TermId, TermKind, TermManager};
7use oxiz_sat::{Lit, RestartStrategy, Var};
8use smallvec::SmallVec;
9
10#[derive(Debug, Clone)]
12pub enum ProofStep {
13 Input {
15 index: u32,
17 clause: Vec<Lit>,
19 },
20 Resolution {
22 index: u32,
24 left: u32,
26 right: u32,
28 pivot: Var,
30 clause: Vec<Lit>,
32 },
33 TheoryLemma {
35 index: u32,
37 theory: String,
39 clause: Vec<Lit>,
41 explanation: Vec<TermId>,
43 },
44}
45
46#[derive(Debug, Clone)]
48pub struct Proof {
49 steps: Vec<ProofStep>,
51 empty_clause_index: Option<u32>,
53}
54
55impl Proof {
56 #[must_use]
58 pub fn new() -> Self {
59 Self {
60 steps: Vec::new(),
61 empty_clause_index: None,
62 }
63 }
64
65 pub fn add_step(&mut self, step: ProofStep) {
67 self.steps.push(step);
68 }
69
70 pub fn set_empty_clause(&mut self, index: u32) {
72 self.empty_clause_index = Some(index);
73 }
74
75 #[must_use]
77 pub fn is_complete(&self) -> bool {
78 self.empty_clause_index.is_some()
79 }
80
81 #[must_use]
83 pub fn len(&self) -> usize {
84 self.steps.len()
85 }
86
87 #[must_use]
89 pub fn is_empty(&self) -> bool {
90 self.steps.is_empty()
91 }
92
93 pub fn steps(&self) -> impl Iterator<Item = &ProofStep> {
95 self.steps.iter()
96 }
97
98 #[must_use]
100 pub fn format(&self) -> String {
101 let mut result = String::from("(proof\n");
102 for step in &self.steps {
103 match step {
104 ProofStep::Input { index, clause } => {
105 result.push_str(&format!(" (input {} {:?})\n", index, clause));
106 }
107 ProofStep::Resolution {
108 index,
109 left,
110 right,
111 pivot,
112 clause,
113 } => {
114 result.push_str(&format!(
115 " (resolution {} {} {} {:?} {:?})\n",
116 index, left, right, pivot, clause
117 ));
118 }
119 ProofStep::TheoryLemma {
120 index,
121 theory,
122 clause,
123 ..
124 } => {
125 result.push_str(&format!(
126 " (theory-lemma {} {} {:?})\n",
127 index, theory, clause
128 ));
129 }
130 }
131 }
132 if let Some(idx) = self.empty_clause_index {
133 result.push_str(&format!(" (empty-clause {})\n", idx));
134 }
135 result.push_str(")\n");
136 result
137 }
138}
139
140impl Default for Proof {
141 fn default() -> Self {
142 Self::new()
143 }
144}
145
146#[derive(Debug, Clone)]
148#[allow(dead_code)]
149pub(crate) enum Constraint {
150 Eq(TermId, TermId),
152 Diseq(TermId, TermId),
154 Lt(TermId, TermId),
156 Le(TermId, TermId),
158 Gt(TermId, TermId),
160 Ge(TermId, TermId),
162 BoolApp(TermId),
167}
168
169#[derive(Debug, Clone, Copy, PartialEq, Eq)]
171pub(crate) enum ArithConstraintType {
172 Lt,
174 Le,
176 Gt,
178 Ge,
180}
181
182#[derive(Debug, Clone)]
185pub(crate) struct ParsedArithConstraint {
186 pub(crate) terms: SmallVec<[(TermId, Rational64); 4]>,
188 pub(crate) constant: Rational64,
190 pub(crate) constraint_type: ArithConstraintType,
192 pub(crate) reason_term: TermId,
194}
195
196#[derive(Debug, Clone, Copy, PartialEq, Eq)]
198pub(crate) enum Polarity {
199 Positive,
201 Negative,
203 Both,
205}
206
207#[derive(Debug, Clone, Copy, PartialEq, Eq)]
209pub enum SolverResult {
210 Sat,
212 Unsat,
214 Unknown,
216}
217
218#[derive(Debug, Clone, Copy, PartialEq, Eq)]
220pub enum TheoryMode {
221 Eager,
223 Lazy,
225}
226
227#[derive(Debug, Clone)]
229pub struct SolverConfig {
230 pub timeout_ms: u64,
232 pub parallel: bool,
234 pub num_threads: usize,
236 pub proof: bool,
238 pub model: bool,
240 pub theory_mode: TheoryMode,
242 pub simplify: bool,
244 pub max_conflicts: u64,
246 pub max_decisions: u64,
248 pub restart_strategy: RestartStrategy,
250 pub enable_clause_minimization: bool,
252 pub enable_clause_subsumption: bool,
254 pub enable_variable_elimination: bool,
256 pub variable_elimination_limit: usize,
258 pub enable_blocked_clause_elimination: bool,
260 pub enable_symmetry_breaking: bool,
262 pub enable_inprocessing: bool,
264 pub inprocessing_interval: u64,
266}
267
268impl Default for SolverConfig {
269 fn default() -> Self {
270 Self::balanced()
271 }
272}
273
274impl SolverConfig {
275 #[must_use]
278 pub fn fast() -> Self {
279 Self {
280 timeout_ms: 0,
281 parallel: false,
282 num_threads: 4,
283 proof: false,
284 model: true,
285 theory_mode: TheoryMode::Eager,
286 simplify: true, max_conflicts: 0,
288 max_decisions: 0,
289 restart_strategy: RestartStrategy::Geometric, enable_clause_minimization: true, enable_clause_subsumption: false, enable_variable_elimination: false, variable_elimination_limit: 0,
294 enable_blocked_clause_elimination: false, enable_symmetry_breaking: false,
296 enable_inprocessing: false, inprocessing_interval: 0,
298 }
299 }
300
301 #[must_use]
304 pub fn balanced() -> Self {
305 Self {
306 timeout_ms: 0,
307 parallel: false,
308 num_threads: 4,
309 proof: false,
310 model: true,
311 theory_mode: TheoryMode::Eager,
312 simplify: true,
313 max_conflicts: 0,
314 max_decisions: 0,
315 restart_strategy: RestartStrategy::Glucose, enable_clause_minimization: true,
317 enable_clause_subsumption: true,
318 enable_variable_elimination: true,
319 variable_elimination_limit: 1000, enable_blocked_clause_elimination: true,
321 enable_symmetry_breaking: false, enable_inprocessing: true,
323 inprocessing_interval: 10000,
324 }
325 }
326
327 #[must_use]
330 pub fn thorough() -> Self {
331 Self {
332 timeout_ms: 0,
333 parallel: false,
334 num_threads: 4,
335 proof: false,
336 model: true,
337 theory_mode: TheoryMode::Eager,
338 simplify: true,
339 max_conflicts: 0,
340 max_decisions: 0,
341 restart_strategy: RestartStrategy::Glucose,
342 enable_clause_minimization: true,
343 enable_clause_subsumption: true,
344 enable_variable_elimination: true,
345 variable_elimination_limit: 5000, enable_blocked_clause_elimination: true,
347 enable_symmetry_breaking: true, enable_inprocessing: true,
349 inprocessing_interval: 5000, }
351 }
352
353 #[must_use]
356 pub fn minimal() -> Self {
357 Self {
358 timeout_ms: 0,
359 parallel: false,
360 num_threads: 1,
361 proof: false,
362 model: true,
363 theory_mode: TheoryMode::Lazy, simplify: false,
365 max_conflicts: 0,
366 max_decisions: 0,
367 restart_strategy: RestartStrategy::Geometric,
368 enable_clause_minimization: false,
369 enable_clause_subsumption: false,
370 enable_variable_elimination: false,
371 variable_elimination_limit: 0,
372 enable_blocked_clause_elimination: false,
373 enable_symmetry_breaking: false,
374 enable_inprocessing: false,
375 inprocessing_interval: 0,
376 }
377 }
378
379 #[must_use]
381 pub fn with_proof(mut self) -> Self {
382 self.proof = true;
383 self
384 }
385
386 #[must_use]
388 pub fn with_timeout(mut self, timeout_ms: u64) -> Self {
389 self.timeout_ms = timeout_ms;
390 self
391 }
392
393 #[must_use]
395 pub fn with_max_conflicts(mut self, max_conflicts: u64) -> Self {
396 self.max_conflicts = max_conflicts;
397 self
398 }
399
400 #[must_use]
402 pub fn with_max_decisions(mut self, max_decisions: u64) -> Self {
403 self.max_decisions = max_decisions;
404 self
405 }
406
407 #[must_use]
409 pub fn with_parallel(mut self, num_threads: usize) -> Self {
410 self.parallel = true;
411 self.num_threads = num_threads;
412 self
413 }
414
415 #[must_use]
417 pub fn with_restart_strategy(mut self, strategy: RestartStrategy) -> Self {
418 self.restart_strategy = strategy;
419 self
420 }
421
422 #[must_use]
424 pub fn with_theory_mode(mut self, mode: TheoryMode) -> Self {
425 self.theory_mode = mode;
426 self
427 }
428}
429
430#[derive(Debug, Clone, Default)]
432pub struct Statistics {
433 pub decisions: u64,
435 pub conflicts: u64,
437 pub propagations: u64,
439 pub restarts: u64,
441 pub learned_clauses: u64,
443 pub theory_propagations: u64,
445 pub theory_conflicts: u64,
447}
448
449impl Statistics {
450 #[must_use]
452 pub fn new() -> Self {
453 Self::default()
454 }
455
456 pub fn reset(&mut self) {
458 *self = Self::default();
459 }
460}
461
462#[derive(Debug, Clone)]
464pub struct Model {
465 assignments: FxHashMap<TermId, TermId>,
467}
468
469impl Model {
470 #[must_use]
472 pub fn new() -> Self {
473 Self {
474 assignments: FxHashMap::default(),
475 }
476 }
477
478 #[must_use]
480 pub fn get(&self, term: TermId) -> Option<TermId> {
481 self.assignments.get(&term).copied()
482 }
483
484 pub fn set(&mut self, term: TermId, value: TermId) {
486 self.assignments.insert(term, value);
487 }
488
489 pub fn minimize(&self, essential_vars: &[TermId]) -> Model {
492 let mut minimized = Model::new();
493
494 for &var in essential_vars {
496 if let Some(&value) = self.assignments.get(&var) {
497 minimized.set(var, value);
498 }
499 }
500
501 minimized
502 }
503
504 #[must_use]
506 pub fn size(&self) -> usize {
507 self.assignments.len()
508 }
509
510 #[must_use]
512 pub fn assignments(&self) -> &FxHashMap<TermId, TermId> {
513 &self.assignments
514 }
515
516 pub fn eval(&self, term: TermId, manager: &mut TermManager) -> TermId {
519 if let Some(val) = self.get(term) {
521 return val;
522 }
523
524 let Some(t) = manager.get(term).cloned() else {
526 return term;
527 };
528
529 match t.kind {
530 TermKind::True
532 | TermKind::False
533 | TermKind::IntConst(_)
534 | TermKind::RealConst(_)
535 | TermKind::BitVecConst { .. } => term,
536
537 TermKind::Var(_) => self.get(term).unwrap_or(term),
539
540 TermKind::Not(arg) => {
542 let arg_val = self.eval(arg, manager);
543 if let Some(t) = manager.get(arg_val) {
544 match t.kind {
545 TermKind::True => manager.mk_false(),
546 TermKind::False => manager.mk_true(),
547 _ => manager.mk_not(arg_val),
548 }
549 } else {
550 manager.mk_not(arg_val)
551 }
552 }
553
554 TermKind::And(ref args) => {
555 let mut eval_args = Vec::new();
556 for &arg in args {
557 let val = self.eval(arg, manager);
558 if let Some(t) = manager.get(val) {
559 if matches!(t.kind, TermKind::False) {
560 return manager.mk_false();
561 }
562 if !matches!(t.kind, TermKind::True) {
563 eval_args.push(val);
564 }
565 } else {
566 eval_args.push(val);
567 }
568 }
569 if eval_args.is_empty() {
570 manager.mk_true()
571 } else if eval_args.len() == 1 {
572 eval_args[0]
573 } else {
574 manager.mk_and(eval_args)
575 }
576 }
577
578 TermKind::Or(ref args) => {
579 let mut eval_args = Vec::new();
580 for &arg in args {
581 let val = self.eval(arg, manager);
582 if let Some(t) = manager.get(val) {
583 if matches!(t.kind, TermKind::True) {
584 return manager.mk_true();
585 }
586 if !matches!(t.kind, TermKind::False) {
587 eval_args.push(val);
588 }
589 } else {
590 eval_args.push(val);
591 }
592 }
593 if eval_args.is_empty() {
594 manager.mk_false()
595 } else if eval_args.len() == 1 {
596 eval_args[0]
597 } else {
598 manager.mk_or(eval_args)
599 }
600 }
601
602 TermKind::Implies(lhs, rhs) => {
603 let lhs_val = self.eval(lhs, manager);
604 let rhs_val = self.eval(rhs, manager);
605
606 if let Some(t) = manager.get(lhs_val) {
607 if matches!(t.kind, TermKind::False) {
608 return manager.mk_true();
609 }
610 if matches!(t.kind, TermKind::True) {
611 return rhs_val;
612 }
613 }
614
615 if let Some(t) = manager.get(rhs_val)
616 && matches!(t.kind, TermKind::True)
617 {
618 return manager.mk_true();
619 }
620
621 manager.mk_implies(lhs_val, rhs_val)
622 }
623
624 TermKind::Ite(cond, then_br, else_br) => {
625 let cond_val = self.eval(cond, manager);
626
627 if let Some(t) = manager.get(cond_val) {
628 match t.kind {
629 TermKind::True => return self.eval(then_br, manager),
630 TermKind::False => return self.eval(else_br, manager),
631 _ => {}
632 }
633 }
634
635 let then_val = self.eval(then_br, manager);
636 let else_val = self.eval(else_br, manager);
637 manager.mk_ite(cond_val, then_val, else_val)
638 }
639
640 TermKind::Eq(lhs, rhs) => {
641 let lhs_val = self.eval(lhs, manager);
642 let rhs_val = self.eval(rhs, manager);
643
644 if lhs_val == rhs_val {
645 return manager.mk_true();
646 }
647
648 if let Some(lhs_term) = manager.get(lhs_val)
654 && lhs_term.sort == manager.sorts.bool_sort
655 {
656 if let Some(rhs_term) = manager.get(rhs_val) {
658 match rhs_term.kind {
659 TermKind::True => return lhs_val,
660 TermKind::False => return manager.mk_not(lhs_val),
661 _ => {}
662 }
663 }
664 match lhs_term.kind {
666 TermKind::True => return rhs_val,
667 TermKind::False => return manager.mk_not(rhs_val),
668 _ => {}
669 }
670 }
671
672 manager.mk_eq(lhs_val, rhs_val)
673 }
674
675 TermKind::Neg(arg) => {
677 let arg_val = self.eval(arg, manager);
678 if let Some(t) = manager.get(arg_val) {
679 match &t.kind {
680 TermKind::IntConst(n) => return manager.mk_int(-n),
681 TermKind::RealConst(r) => return manager.mk_real(-r),
682 _ => {}
683 }
684 }
685 manager.mk_not(arg_val)
686 }
687
688 TermKind::Add(ref args) => {
689 let eval_args: Vec<_> = args.iter().map(|&a| self.eval(a, manager)).collect();
690 manager.mk_add(eval_args)
691 }
692
693 TermKind::Sub(lhs, rhs) => {
694 let lhs_val = self.eval(lhs, manager);
695 let rhs_val = self.eval(rhs, manager);
696 manager.mk_sub(lhs_val, rhs_val)
697 }
698
699 TermKind::Mul(ref args) => {
700 let eval_args: Vec<_> = args.iter().map(|&a| self.eval(a, manager)).collect();
701 manager.mk_mul(eval_args)
702 }
703
704 _ => self.get(term).unwrap_or(term),
706 }
707 }
708}
709
710impl Default for Model {
711 fn default() -> Self {
712 Self::new()
713 }
714}
715
716impl Model {
717 #[cfg(feature = "std")]
719 pub fn pretty_print(&self, manager: &TermManager) -> String {
720 if self.assignments.is_empty() {
721 return "(model)".to_string();
722 }
723
724 let mut lines = vec!["(model".to_string()];
725 let printer = oxiz_core::smtlib::Printer::new(manager);
726
727 for (&var, &value) in &self.assignments {
728 if let Some(term) = manager.get(var) {
729 if let TermKind::Var(name) = &term.kind {
731 let sort_str = Self::format_sort(term.sort, manager);
732 let value_str = printer.print_term(value);
733 let name_str = format!("{:?}", name);
735 lines.push(format!(
736 " (define-fun {} () {} {})",
737 name_str, sort_str, value_str
738 ));
739 }
740 }
741 }
742 lines.push(")".to_string());
743 lines.join("\n")
744 }
745
746 fn format_sort(sort: oxiz_core::sort::SortId, manager: &TermManager) -> String {
748 if sort == manager.sorts.bool_sort {
749 "Bool".to_string()
750 } else if sort == manager.sorts.int_sort {
751 "Int".to_string()
752 } else if sort == manager.sorts.real_sort {
753 "Real".to_string()
754 } else if let Some(s) = manager.sorts.get(sort) {
755 if let Some(w) = s.bitvec_width() {
756 format!("(_ BitVec {})", w)
757 } else {
758 "Unknown".to_string()
759 }
760 } else {
761 "Unknown".to_string()
762 }
763 }
764}
765
766#[derive(Debug, Clone)]
768pub struct NamedAssertion {
769 #[allow(dead_code)]
771 pub term: TermId,
772 pub name: Option<String>,
774 pub index: u32,
776}
777
778#[derive(Debug, Clone)]
780pub struct UnsatCore {
781 pub names: Vec<String>,
783 pub indices: Vec<u32>,
785}
786
787impl UnsatCore {
788 #[must_use]
790 pub fn new() -> Self {
791 Self {
792 names: Vec::new(),
793 indices: Vec::new(),
794 }
795 }
796
797 #[must_use]
799 pub fn is_empty(&self) -> bool {
800 self.indices.is_empty()
801 }
802
803 #[must_use]
805 pub fn len(&self) -> usize {
806 self.indices.len()
807 }
808}
809
810impl Default for UnsatCore {
811 fn default() -> Self {
812 Self::new()
813 }
814}
815
816#[derive(Debug, Clone)]
818pub struct FpConstraintData {
819 pub additions: Vec<(TermId, TermId, TermId, TermId, RoundingMode)>,
820 pub divisions: Vec<(TermId, TermId, TermId, TermId, RoundingMode)>,
821 pub multiplications: Vec<(TermId, TermId, TermId, TermId, RoundingMode)>,
822 pub comparisons: Vec<(TermId, TermId, bool)>,
823 pub equalities: Vec<(TermId, TermId)>,
824 pub literals: FxHashMap<TermId, f64>,
825 pub rounding_add_results: FxHashMap<(TermId, TermId, RoundingMode), TermId>,
826 pub is_zero: FxHashSet<TermId>,
827 pub is_positive: FxHashSet<TermId>,
828 pub is_negative: FxHashSet<TermId>,
829 pub not_nan: FxHashSet<TermId>,
830 pub gt_comparisons: Vec<(TermId, TermId)>,
831 pub lt_comparisons: Vec<(TermId, TermId)>,
832 pub conversions: Vec<(TermId, u32, u32, TermId)>,
833 pub real_to_fp_conversions: Vec<(TermId, u32, u32, TermId)>,
834 pub subtractions: Vec<(TermId, TermId, TermId)>,
835}
836
837impl FpConstraintData {
838 #[must_use]
839 pub fn new() -> Self {
840 Self {
841 additions: Vec::new(),
842 divisions: Vec::new(),
843 multiplications: Vec::new(),
844 comparisons: Vec::new(),
845 equalities: Vec::new(),
846 literals: FxHashMap::default(),
847 rounding_add_results: FxHashMap::default(),
848 is_zero: FxHashSet::default(),
849 is_positive: FxHashSet::default(),
850 is_negative: FxHashSet::default(),
851 not_nan: FxHashSet::default(),
852 gt_comparisons: Vec::new(),
853 lt_comparisons: Vec::new(),
854 conversions: Vec::new(),
855 real_to_fp_conversions: Vec::new(),
856 subtractions: Vec::new(),
857 }
858 }
859
860 #[must_use]
861 pub fn is_empty(&self) -> bool {
862 self.additions.is_empty()
863 && self.divisions.is_empty()
864 && self.multiplications.is_empty()
865 && self.comparisons.is_empty()
866 && self.equalities.is_empty()
867 }
868
869 pub fn merge(&mut self, other: &FpConstraintData) {
870 self.additions.extend_from_slice(&other.additions);
871 self.divisions.extend_from_slice(&other.divisions);
872 self.multiplications
873 .extend_from_slice(&other.multiplications);
874 self.comparisons.extend_from_slice(&other.comparisons);
875 self.equalities.extend_from_slice(&other.equalities);
876 for (&k, &v) in &other.literals {
877 self.literals.insert(k, v);
878 }
879 for (&k, &v) in &other.rounding_add_results {
880 self.rounding_add_results.insert(k, v);
881 }
882 self.is_zero.extend(other.is_zero.iter().copied());
883 self.is_positive.extend(other.is_positive.iter().copied());
884 self.is_negative.extend(other.is_negative.iter().copied());
885 self.not_nan.extend(other.not_nan.iter().copied());
886 self.gt_comparisons.extend_from_slice(&other.gt_comparisons);
887 self.lt_comparisons.extend_from_slice(&other.lt_comparisons);
888 self.conversions.extend_from_slice(&other.conversions);
889 self.real_to_fp_conversions
890 .extend_from_slice(&other.real_to_fp_conversions);
891 self.subtractions.extend_from_slice(&other.subtractions);
892 }
893}
894
895impl Default for FpConstraintData {
896 fn default() -> Self {
897 Self::new()
898 }
899}
900
901#[derive(Debug)]
903pub struct ModelCache {
904 model: Model,
905 eval_cache: FxHashMap<TermId, TermId>,
906 cache_hits: u64,
907 cache_misses: u64,
908}
909
910impl ModelCache {
911 #[must_use]
912 pub fn new(model: Model) -> Self {
913 Self {
914 model,
915 eval_cache: FxHashMap::default(),
916 cache_hits: 0,
917 cache_misses: 0,
918 }
919 }
920
921 #[must_use]
922 pub fn model(&self) -> &Model {
923 &self.model
924 }
925
926 #[must_use]
927 pub fn get_direct(&self, term: TermId) -> Option<TermId> {
928 self.model.get(term)
929 }
930
931 pub fn eval_lazy(&mut self, term: TermId, manager: &mut TermManager) -> TermId {
932 if let Some(&cached) = self.eval_cache.get(&term) {
933 self.cache_hits += 1;
934 return cached;
935 }
936 self.cache_misses += 1;
937 let result = self.model.eval(term, manager);
938 self.eval_cache.insert(term, result);
939 result
940 }
941
942 pub fn eval_batch(
943 &mut self,
944 terms: &[TermId],
945 manager: &mut TermManager,
946 ) -> SmallVec<[TermId; 8]> {
947 terms
948 .iter()
949 .map(|&t| {
950 if let Some(&cached) = self.eval_cache.get(&t) {
951 self.cache_hits += 1;
952 cached
953 } else {
954 self.cache_misses += 1;
955 let result = self.model.eval(t, manager);
956 self.eval_cache.insert(t, result);
957 result
958 }
959 })
960 .collect()
961 }
962
963 pub fn invalidate(&mut self) {
964 self.eval_cache.clear();
965 }
966
967 pub fn invalidate_term(&mut self, term: TermId) {
968 self.eval_cache.remove(&term);
969 }
970
971 #[must_use]
972 pub fn cache_stats(&self) -> (u64, u64) {
973 (self.cache_hits, self.cache_misses)
974 }
975
976 #[must_use]
977 pub fn cache_size(&self) -> usize {
978 self.eval_cache.len()
979 }
980
981 #[must_use]
982 pub fn model_size(&self) -> usize {
983 self.model.size()
984 }
985
986 #[must_use]
987 pub fn is_cached(&self, term: TermId) -> bool {
988 self.eval_cache.contains_key(&term)
989 }
990
991 #[must_use]
992 pub fn into_model(self) -> Model {
993 self.model
994 }
995}