oxiz_solver/
solver.rs

1//! Main CDCL(T) Solver
2
3use crate::mbqi::{MBQIResult, MBQISolver};
4use crate::simplify::Simplifier;
5use num_rational::Rational64;
6use num_traits::{One, ToPrimitive, Zero};
7use oxiz_core::ast::{TermId, TermKind, TermManager};
8use oxiz_sat::{
9    Lit, RestartStrategy, Solver as SatSolver, SolverConfig as SatConfig,
10    SolverResult as SatResult, TheoryCallback, TheoryCheckResult, Var,
11};
12use oxiz_theories::arithmetic::ArithSolver;
13use oxiz_theories::bv::BvSolver;
14use oxiz_theories::euf::EufSolver;
15use oxiz_theories::{EqualityNotification, Theory, TheoryCombination};
16use rustc_hash::{FxHashMap, FxHashSet};
17use smallvec::SmallVec;
18
19/// Proof step for resolution-based proofs
20#[derive(Debug, Clone)]
21pub enum ProofStep {
22    /// Input clause (from the original formula)
23    Input {
24        /// Clause index
25        index: u32,
26        /// The clause (as a disjunction of literals)
27        clause: Vec<Lit>,
28    },
29    /// Resolution step
30    Resolution {
31        /// Index of this proof step
32        index: u32,
33        /// Left parent clause index
34        left: u32,
35        /// Right parent clause index
36        right: u32,
37        /// Pivot variable (the variable resolved on)
38        pivot: Var,
39        /// Resulting clause
40        clause: Vec<Lit>,
41    },
42    /// Theory lemma (from a theory solver)
43    TheoryLemma {
44        /// Index of this proof step
45        index: u32,
46        /// The theory that produced this lemma
47        theory: String,
48        /// The lemma clause
49        clause: Vec<Lit>,
50        /// Explanation terms
51        explanation: Vec<TermId>,
52    },
53}
54
55/// A proof of unsatisfiability
56#[derive(Debug, Clone)]
57pub struct Proof {
58    /// Sequence of proof steps leading to the empty clause
59    steps: Vec<ProofStep>,
60    /// Index of the final empty clause (proving unsat)
61    empty_clause_index: Option<u32>,
62}
63
64impl Proof {
65    /// Create a new empty proof
66    #[must_use]
67    pub fn new() -> Self {
68        Self {
69            steps: Vec::new(),
70            empty_clause_index: None,
71        }
72    }
73
74    /// Add a proof step
75    pub fn add_step(&mut self, step: ProofStep) {
76        self.steps.push(step);
77    }
78
79    /// Set the index of the empty clause (final step proving unsat)
80    pub fn set_empty_clause(&mut self, index: u32) {
81        self.empty_clause_index = Some(index);
82    }
83
84    /// Check if the proof is complete (has an empty clause)
85    #[must_use]
86    pub fn is_complete(&self) -> bool {
87        self.empty_clause_index.is_some()
88    }
89
90    /// Get the number of proof steps
91    #[must_use]
92    pub fn len(&self) -> usize {
93        self.steps.len()
94    }
95
96    /// Check if the proof is empty
97    #[must_use]
98    pub fn is_empty(&self) -> bool {
99        self.steps.is_empty()
100    }
101
102    /// Format the proof as a string (for debugging or output)
103    #[must_use]
104    pub fn format(&self) -> String {
105        let mut result = String::from("(proof\n");
106        for step in &self.steps {
107            match step {
108                ProofStep::Input { index, clause } => {
109                    result.push_str(&format!("  (input {} {:?})\n", index, clause));
110                }
111                ProofStep::Resolution {
112                    index,
113                    left,
114                    right,
115                    pivot,
116                    clause,
117                } => {
118                    result.push_str(&format!(
119                        "  (resolution {} {} {} {:?} {:?})\n",
120                        index, left, right, pivot, clause
121                    ));
122                }
123                ProofStep::TheoryLemma {
124                    index,
125                    theory,
126                    clause,
127                    ..
128                } => {
129                    result.push_str(&format!(
130                        "  (theory-lemma {} {} {:?})\n",
131                        index, theory, clause
132                    ));
133                }
134            }
135        }
136        if let Some(idx) = self.empty_clause_index {
137            result.push_str(&format!("  (empty-clause {})\n", idx));
138        }
139        result.push_str(")\n");
140        result
141    }
142}
143
144impl Default for Proof {
145    fn default() -> Self {
146        Self::new()
147    }
148}
149
150/// Represents a theory constraint associated with a boolean variable
151#[derive(Debug, Clone)]
152#[allow(dead_code)]
153enum Constraint {
154    /// Equality constraint: lhs = rhs
155    Eq(TermId, TermId),
156    /// Disequality constraint: lhs != rhs (negation of equality)
157    Diseq(TermId, TermId),
158    /// Less-than constraint: lhs < rhs
159    Lt(TermId, TermId),
160    /// Less-than-or-equal constraint: lhs <= rhs
161    Le(TermId, TermId),
162    /// Greater-than constraint: lhs > rhs
163    Gt(TermId, TermId),
164    /// Greater-than-or-equal constraint: lhs >= rhs
165    Ge(TermId, TermId),
166}
167
168/// Type of arithmetic constraint
169#[derive(Debug, Clone, Copy, PartialEq, Eq)]
170enum ArithConstraintType {
171    /// Less than (<)
172    Lt,
173    /// Less than or equal (<=)
174    Le,
175    /// Greater than (>)
176    Gt,
177    /// Greater than or equal (>=)
178    Ge,
179}
180
181/// Parsed arithmetic constraint with extracted linear expression
182/// Represents: sum of (term, coefficient) <= constant OR < constant (if strict)
183#[derive(Debug, Clone)]
184struct ParsedArithConstraint {
185    /// Linear terms: (variable_term, coefficient)
186    terms: SmallVec<[(TermId, Rational64); 4]>,
187    /// Constant bound (RHS)
188    constant: Rational64,
189    /// Type of constraint
190    constraint_type: ArithConstraintType,
191    /// The original term (for conflict explanation)
192    reason_term: TermId,
193}
194
195/// Polarity of a term in the formula
196#[derive(Debug, Clone, Copy, PartialEq, Eq)]
197enum Polarity {
198    /// Term appears only positively
199    Positive,
200    /// Term appears only negatively
201    Negative,
202    /// Term appears in both polarities
203    Both,
204}
205
206/// Result of SMT solving
207#[derive(Debug, Clone, Copy, PartialEq, Eq)]
208pub enum SolverResult {
209    /// Satisfiable
210    Sat,
211    /// Unsatisfiable
212    Unsat,
213    /// Unknown (timeout, incomplete, etc.)
214    Unknown,
215}
216
217/// Theory checking mode
218#[derive(Debug, Clone, Copy, PartialEq, Eq)]
219pub enum TheoryMode {
220    /// Eager theory checking (check on every assignment)
221    Eager,
222    /// Lazy theory checking (check only on complete assignments)
223    Lazy,
224}
225
226/// Solver configuration
227#[derive(Debug, Clone)]
228pub struct SolverConfig {
229    /// Timeout in milliseconds (0 = no timeout)
230    pub timeout_ms: u64,
231    /// Enable parallel solving
232    pub parallel: bool,
233    /// Number of threads for parallel solving
234    pub num_threads: usize,
235    /// Enable proof generation
236    pub proof: bool,
237    /// Enable model generation
238    pub model: bool,
239    /// Theory checking mode
240    pub theory_mode: TheoryMode,
241    /// Enable preprocessing/simplification
242    pub simplify: bool,
243    /// Maximum number of conflicts before giving up (0 = unlimited)
244    pub max_conflicts: u64,
245    /// Maximum number of decisions before giving up (0 = unlimited)
246    pub max_decisions: u64,
247    /// Restart strategy for SAT solver
248    pub restart_strategy: RestartStrategy,
249    /// Enable clause minimization (recursive minimization of learned clauses)
250    pub enable_clause_minimization: bool,
251    /// Enable learned clause subsumption
252    pub enable_clause_subsumption: bool,
253    /// Enable variable elimination during preprocessing
254    pub enable_variable_elimination: bool,
255    /// Variable elimination limit (max clauses to produce)
256    pub variable_elimination_limit: usize,
257    /// Enable blocked clause elimination during preprocessing
258    pub enable_blocked_clause_elimination: bool,
259    /// Enable symmetry breaking predicates
260    pub enable_symmetry_breaking: bool,
261    /// Enable inprocessing (periodic preprocessing during search)
262    pub enable_inprocessing: bool,
263    /// Inprocessing interval (number of conflicts between inprocessing)
264    pub inprocessing_interval: u64,
265}
266
267impl Default for SolverConfig {
268    fn default() -> Self {
269        Self::balanced()
270    }
271}
272
273impl SolverConfig {
274    /// Create a configuration optimized for speed (minimal preprocessing)
275    /// Best for easy problems or when quick results are needed
276    #[must_use]
277    pub fn fast() -> Self {
278        Self {
279            timeout_ms: 0,
280            parallel: false,
281            num_threads: 4,
282            proof: false,
283            model: true,
284            theory_mode: TheoryMode::Eager,
285            simplify: true, // Keep basic simplification
286            max_conflicts: 0,
287            max_decisions: 0,
288            restart_strategy: RestartStrategy::Geometric, // Faster than Glucose
289            enable_clause_minimization: true,             // Keep this, it's fast
290            enable_clause_subsumption: false,             // Skip for speed
291            enable_variable_elimination: false,           // Skip preprocessing
292            variable_elimination_limit: 0,
293            enable_blocked_clause_elimination: false, // Skip preprocessing
294            enable_symmetry_breaking: false,
295            enable_inprocessing: false, // No inprocessing for speed
296            inprocessing_interval: 0,
297        }
298    }
299
300    /// Create a balanced configuration (default)
301    /// Good balance between preprocessing and solving speed
302    #[must_use]
303    pub fn balanced() -> Self {
304        Self {
305            timeout_ms: 0,
306            parallel: false,
307            num_threads: 4,
308            proof: false,
309            model: true,
310            theory_mode: TheoryMode::Eager,
311            simplify: true,
312            max_conflicts: 0,
313            max_decisions: 0,
314            restart_strategy: RestartStrategy::Glucose, // Adaptive restarts
315            enable_clause_minimization: true,
316            enable_clause_subsumption: true,
317            enable_variable_elimination: true,
318            variable_elimination_limit: 1000, // Conservative limit
319            enable_blocked_clause_elimination: true,
320            enable_symmetry_breaking: false, // Still expensive
321            enable_inprocessing: true,
322            inprocessing_interval: 10000,
323        }
324    }
325
326    /// Create a configuration optimized for hard problems
327    /// Uses aggressive preprocessing and symmetry breaking
328    #[must_use]
329    pub fn thorough() -> Self {
330        Self {
331            timeout_ms: 0,
332            parallel: false,
333            num_threads: 4,
334            proof: false,
335            model: true,
336            theory_mode: TheoryMode::Eager,
337            simplify: true,
338            max_conflicts: 0,
339            max_decisions: 0,
340            restart_strategy: RestartStrategy::Glucose,
341            enable_clause_minimization: true,
342            enable_clause_subsumption: true,
343            enable_variable_elimination: true,
344            variable_elimination_limit: 5000, // More aggressive
345            enable_blocked_clause_elimination: true,
346            enable_symmetry_breaking: true, // Enable for hard problems
347            enable_inprocessing: true,
348            inprocessing_interval: 5000, // More frequent inprocessing
349        }
350    }
351
352    /// Create a minimal configuration (almost all features disabled)
353    /// Useful for debugging or when you want full control
354    #[must_use]
355    pub fn minimal() -> Self {
356        Self {
357            timeout_ms: 0,
358            parallel: false,
359            num_threads: 1,
360            proof: false,
361            model: true,
362            theory_mode: TheoryMode::Lazy, // Lazy for minimal overhead
363            simplify: false,
364            max_conflicts: 0,
365            max_decisions: 0,
366            restart_strategy: RestartStrategy::Geometric,
367            enable_clause_minimization: false,
368            enable_clause_subsumption: false,
369            enable_variable_elimination: false,
370            variable_elimination_limit: 0,
371            enable_blocked_clause_elimination: false,
372            enable_symmetry_breaking: false,
373            enable_inprocessing: false,
374            inprocessing_interval: 0,
375        }
376    }
377
378    /// Enable proof generation
379    #[must_use]
380    pub fn with_proof(mut self) -> Self {
381        self.proof = true;
382        self
383    }
384
385    /// Set timeout in milliseconds
386    #[must_use]
387    pub fn with_timeout(mut self, timeout_ms: u64) -> Self {
388        self.timeout_ms = timeout_ms;
389        self
390    }
391
392    /// Set maximum number of conflicts
393    #[must_use]
394    pub fn with_max_conflicts(mut self, max_conflicts: u64) -> Self {
395        self.max_conflicts = max_conflicts;
396        self
397    }
398
399    /// Set maximum number of decisions
400    #[must_use]
401    pub fn with_max_decisions(mut self, max_decisions: u64) -> Self {
402        self.max_decisions = max_decisions;
403        self
404    }
405
406    /// Enable parallel solving
407    #[must_use]
408    pub fn with_parallel(mut self, num_threads: usize) -> Self {
409        self.parallel = true;
410        self.num_threads = num_threads;
411        self
412    }
413
414    /// Set restart strategy
415    #[must_use]
416    pub fn with_restart_strategy(mut self, strategy: RestartStrategy) -> Self {
417        self.restart_strategy = strategy;
418        self
419    }
420
421    /// Set theory mode
422    #[must_use]
423    pub fn with_theory_mode(mut self, mode: TheoryMode) -> Self {
424        self.theory_mode = mode;
425        self
426    }
427}
428
429/// Solver statistics
430#[derive(Debug, Clone, Default)]
431pub struct Statistics {
432    /// Number of decisions made
433    pub decisions: u64,
434    /// Number of conflicts encountered
435    pub conflicts: u64,
436    /// Number of propagations performed
437    pub propagations: u64,
438    /// Number of restarts performed
439    pub restarts: u64,
440    /// Number of learned clauses
441    pub learned_clauses: u64,
442    /// Number of theory propagations
443    pub theory_propagations: u64,
444    /// Number of theory conflicts
445    pub theory_conflicts: u64,
446}
447
448impl Statistics {
449    /// Create new statistics with all counters set to zero
450    #[must_use]
451    pub fn new() -> Self {
452        Self::default()
453    }
454
455    /// Reset all statistics
456    pub fn reset(&mut self) {
457        *self = Self::default();
458    }
459}
460
461/// A model (assignment to variables)
462#[derive(Debug, Clone)]
463pub struct Model {
464    /// Variable assignments
465    assignments: FxHashMap<TermId, TermId>,
466}
467
468impl Model {
469    /// Create a new empty model
470    #[must_use]
471    pub fn new() -> Self {
472        Self {
473            assignments: FxHashMap::default(),
474        }
475    }
476
477    /// Get the value of a term in the model
478    #[must_use]
479    pub fn get(&self, term: TermId) -> Option<TermId> {
480        self.assignments.get(&term).copied()
481    }
482
483    /// Set a value in the model
484    pub fn set(&mut self, term: TermId, value: TermId) {
485        self.assignments.insert(term, value);
486    }
487
488    /// Minimize the model by removing redundant assignments
489    /// Returns a new minimized model containing only essential assignments
490    pub fn minimize(&self, essential_vars: &[TermId]) -> Model {
491        let mut minimized = Model::new();
492
493        // Only keep assignments for essential variables
494        for &var in essential_vars {
495            if let Some(&value) = self.assignments.get(&var) {
496                minimized.set(var, value);
497            }
498        }
499
500        minimized
501    }
502
503    /// Get the number of assignments in the model
504    #[must_use]
505    pub fn size(&self) -> usize {
506        self.assignments.len()
507    }
508
509    /// Get the assignments map (for MBQI integration)
510    #[must_use]
511    pub fn assignments(&self) -> &FxHashMap<TermId, TermId> {
512        &self.assignments
513    }
514
515    /// Evaluate a term in this model
516    /// Returns the simplified/evaluated term
517    pub fn eval(&self, term: TermId, manager: &mut TermManager) -> TermId {
518        // First check if we have a direct assignment
519        if let Some(val) = self.get(term) {
520            return val;
521        }
522
523        // Otherwise, recursively evaluate based on term structure
524        let Some(t) = manager.get(term).cloned() else {
525            return term;
526        };
527
528        match t.kind {
529            // Constants evaluate to themselves
530            TermKind::True
531            | TermKind::False
532            | TermKind::IntConst(_)
533            | TermKind::RealConst(_)
534            | TermKind::BitVecConst { .. } => term,
535
536            // Variables: look up in model or return the variable itself
537            TermKind::Var(_) => self.get(term).unwrap_or(term),
538
539            // Boolean operations
540            TermKind::Not(arg) => {
541                let arg_val = self.eval(arg, manager);
542                if let Some(t) = manager.get(arg_val) {
543                    match t.kind {
544                        TermKind::True => manager.mk_false(),
545                        TermKind::False => manager.mk_true(),
546                        _ => manager.mk_not(arg_val),
547                    }
548                } else {
549                    manager.mk_not(arg_val)
550                }
551            }
552
553            TermKind::And(ref args) => {
554                let mut eval_args = Vec::new();
555                for &arg in args {
556                    let val = self.eval(arg, manager);
557                    if let Some(t) = manager.get(val) {
558                        if matches!(t.kind, TermKind::False) {
559                            return manager.mk_false();
560                        }
561                        if !matches!(t.kind, TermKind::True) {
562                            eval_args.push(val);
563                        }
564                    } else {
565                        eval_args.push(val);
566                    }
567                }
568                if eval_args.is_empty() {
569                    manager.mk_true()
570                } else if eval_args.len() == 1 {
571                    eval_args[0]
572                } else {
573                    manager.mk_and(eval_args)
574                }
575            }
576
577            TermKind::Or(ref args) => {
578                let mut eval_args = Vec::new();
579                for &arg in args {
580                    let val = self.eval(arg, manager);
581                    if let Some(t) = manager.get(val) {
582                        if matches!(t.kind, TermKind::True) {
583                            return manager.mk_true();
584                        }
585                        if !matches!(t.kind, TermKind::False) {
586                            eval_args.push(val);
587                        }
588                    } else {
589                        eval_args.push(val);
590                    }
591                }
592                if eval_args.is_empty() {
593                    manager.mk_false()
594                } else if eval_args.len() == 1 {
595                    eval_args[0]
596                } else {
597                    manager.mk_or(eval_args)
598                }
599            }
600
601            TermKind::Implies(lhs, rhs) => {
602                let lhs_val = self.eval(lhs, manager);
603                let rhs_val = self.eval(rhs, manager);
604
605                if let Some(t) = manager.get(lhs_val) {
606                    if matches!(t.kind, TermKind::False) {
607                        return manager.mk_true();
608                    }
609                    if matches!(t.kind, TermKind::True) {
610                        return rhs_val;
611                    }
612                }
613
614                if let Some(t) = manager.get(rhs_val)
615                    && matches!(t.kind, TermKind::True)
616                {
617                    return manager.mk_true();
618                }
619
620                manager.mk_implies(lhs_val, rhs_val)
621            }
622
623            TermKind::Ite(cond, then_br, else_br) => {
624                let cond_val = self.eval(cond, manager);
625
626                if let Some(t) = manager.get(cond_val) {
627                    match t.kind {
628                        TermKind::True => return self.eval(then_br, manager),
629                        TermKind::False => return self.eval(else_br, manager),
630                        _ => {}
631                    }
632                }
633
634                let then_val = self.eval(then_br, manager);
635                let else_val = self.eval(else_br, manager);
636                manager.mk_ite(cond_val, then_val, else_val)
637            }
638
639            TermKind::Eq(lhs, rhs) => {
640                let lhs_val = self.eval(lhs, manager);
641                let rhs_val = self.eval(rhs, manager);
642
643                if lhs_val == rhs_val {
644                    manager.mk_true()
645                } else {
646                    manager.mk_eq(lhs_val, rhs_val)
647                }
648            }
649
650            // Arithmetic operations - basic constant folding
651            TermKind::Neg(arg) => {
652                let arg_val = self.eval(arg, manager);
653                if let Some(t) = manager.get(arg_val) {
654                    match &t.kind {
655                        TermKind::IntConst(n) => return manager.mk_int(-n),
656                        TermKind::RealConst(r) => return manager.mk_real(-r),
657                        _ => {}
658                    }
659                }
660                manager.mk_not(arg_val)
661            }
662
663            TermKind::Add(ref args) => {
664                let eval_args: Vec<_> = args.iter().map(|&a| self.eval(a, manager)).collect();
665                manager.mk_add(eval_args)
666            }
667
668            TermKind::Sub(lhs, rhs) => {
669                let lhs_val = self.eval(lhs, manager);
670                let rhs_val = self.eval(rhs, manager);
671                manager.mk_sub(lhs_val, rhs_val)
672            }
673
674            TermKind::Mul(ref args) => {
675                let eval_args: Vec<_> = args.iter().map(|&a| self.eval(a, manager)).collect();
676                manager.mk_mul(eval_args)
677            }
678
679            // For other operations, just return the term or look it up
680            _ => self.get(term).unwrap_or(term),
681        }
682    }
683}
684
685impl Default for Model {
686    fn default() -> Self {
687        Self::new()
688    }
689}
690
691impl Model {
692    /// Pretty print the model in SMT-LIB2 format
693    pub fn pretty_print(&self, manager: &TermManager) -> String {
694        if self.assignments.is_empty() {
695            return "(model)".to_string();
696        }
697
698        let mut lines = vec!["(model".to_string()];
699        let printer = oxiz_core::smtlib::Printer::new(manager);
700
701        for (&var, &value) in &self.assignments {
702            if let Some(term) = manager.get(var) {
703                // Only print top-level variables, not internal encoding variables
704                if let TermKind::Var(name) = &term.kind {
705                    let sort_str = Self::format_sort(term.sort, manager);
706                    let value_str = printer.print_term(value);
707                    // Use Debug format for the symbol name
708                    let name_str = format!("{:?}", name);
709                    lines.push(format!(
710                        "  (define-fun {} () {} {})",
711                        name_str, sort_str, value_str
712                    ));
713                }
714            }
715        }
716        lines.push(")".to_string());
717        lines.join("\n")
718    }
719
720    /// Format a sort ID to its SMT-LIB2 representation
721    fn format_sort(sort: oxiz_core::sort::SortId, manager: &TermManager) -> String {
722        if sort == manager.sorts.bool_sort {
723            "Bool".to_string()
724        } else if sort == manager.sorts.int_sort {
725            "Int".to_string()
726        } else if sort == manager.sorts.real_sort {
727            "Real".to_string()
728        } else if let Some(s) = manager.sorts.get(sort) {
729            if let Some(w) = s.bitvec_width() {
730                format!("(_ BitVec {})", w)
731            } else {
732                "Unknown".to_string()
733            }
734        } else {
735            "Unknown".to_string()
736        }
737    }
738}
739
740/// A named assertion for unsat core tracking
741#[derive(Debug, Clone)]
742pub struct NamedAssertion {
743    /// The assertion term (kept for potential future use in minimization)
744    #[allow(dead_code)]
745    pub term: TermId,
746    /// The name (if any)
747    pub name: Option<String>,
748    /// Index of this assertion
749    pub index: u32,
750}
751
752/// An unsat core - a minimal set of assertions that are unsatisfiable
753#[derive(Debug, Clone)]
754pub struct UnsatCore {
755    /// The names of assertions in the core
756    pub names: Vec<String>,
757    /// The indices of assertions in the core
758    pub indices: Vec<u32>,
759}
760
761impl UnsatCore {
762    /// Create a new empty unsat core
763    #[must_use]
764    pub fn new() -> Self {
765        Self {
766            names: Vec::new(),
767            indices: Vec::new(),
768        }
769    }
770
771    /// Check if the core is empty
772    #[must_use]
773    pub fn is_empty(&self) -> bool {
774        self.indices.is_empty()
775    }
776
777    /// Get the number of assertions in the core
778    #[must_use]
779    pub fn len(&self) -> usize {
780        self.indices.len()
781    }
782}
783
784impl Default for UnsatCore {
785    fn default() -> Self {
786        Self::new()
787    }
788}
789
790/// Main CDCL(T) SMT Solver
791#[derive(Debug)]
792pub struct Solver {
793    /// Configuration
794    config: SolverConfig,
795    /// SAT solver core
796    sat: SatSolver,
797    /// EUF theory solver
798    euf: EufSolver,
799    /// Arithmetic theory solver
800    arith: ArithSolver,
801    /// Bitvector theory solver
802    bv: BvSolver,
803    /// MBQI solver for quantified formulas
804    mbqi: MBQISolver,
805    /// Whether the formula contains quantifiers
806    has_quantifiers: bool,
807    /// Term to SAT variable mapping
808    term_to_var: FxHashMap<TermId, Var>,
809    /// SAT variable to term mapping
810    var_to_term: Vec<TermId>,
811    /// SAT variable to theory constraint mapping
812    var_to_constraint: FxHashMap<Var, Constraint>,
813    /// SAT variable to parsed arithmetic constraint mapping
814    var_to_parsed_arith: FxHashMap<Var, ParsedArithConstraint>,
815    /// Current logic
816    logic: Option<String>,
817    /// Assertions
818    assertions: Vec<TermId>,
819    /// Named assertions for unsat core tracking
820    named_assertions: Vec<NamedAssertion>,
821    /// Assumption literals for unsat core tracking (maps assertion index to assumption var)
822    /// Reserved for future use with assumption-based unsat core extraction
823    #[allow(dead_code)]
824    assumption_vars: FxHashMap<u32, Var>,
825    /// Model (if sat)
826    model: Option<Model>,
827    /// Unsat core (if unsat)
828    unsat_core: Option<UnsatCore>,
829    /// Context stack for push/pop
830    context_stack: Vec<ContextState>,
831    /// Trail of operations for efficient undo
832    trail: Vec<TrailOp>,
833    /// Tracking which literals have been processed by theories
834    theory_processed_up_to: usize,
835    /// Whether to produce unsat cores
836    produce_unsat_cores: bool,
837    /// Track if we've asserted False (for immediate unsat)
838    has_false_assertion: bool,
839    /// Polarity tracking for optimization
840    polarities: FxHashMap<TermId, Polarity>,
841    /// Whether polarity-aware encoding is enabled
842    polarity_aware: bool,
843    /// Whether theory-aware branching is enabled
844    theory_aware_branching: bool,
845    /// Proof of unsatisfiability (if proof generation is enabled)
846    proof: Option<Proof>,
847    /// Formula simplifier
848    simplifier: Simplifier,
849    /// Solver statistics
850    statistics: Statistics,
851    /// Bitvector terms (for model extraction)
852    bv_terms: FxHashSet<TermId>,
853    /// Arithmetic terms (Int/Real variables for model extraction)
854    arith_terms: FxHashSet<TermId>,
855}
856
857/// Theory decision hint
858#[derive(Debug, Clone, Copy)]
859#[allow(dead_code)]
860pub struct TheoryDecision {
861    /// The variable to branch on
862    pub var: Var,
863    /// Suggested value (true = positive, false = negative)
864    pub value: bool,
865    /// Priority (higher = more important)
866    pub priority: i32,
867}
868
869/// Theory manager that bridges the SAT solver with theory solvers
870struct TheoryManager<'a> {
871    /// Reference to the EUF solver
872    euf: &'a mut EufSolver,
873    /// Reference to the arithmetic solver
874    arith: &'a mut ArithSolver,
875    /// Reference to the bitvector solver
876    bv: &'a mut BvSolver,
877    /// Mapping from SAT variables to constraints
878    var_to_constraint: &'a FxHashMap<Var, Constraint>,
879    /// Mapping from SAT variables to parsed arithmetic constraints
880    var_to_parsed_arith: &'a FxHashMap<Var, ParsedArithConstraint>,
881    /// Mapping from terms to SAT variables (for conflict clause generation)
882    term_to_var: &'a FxHashMap<TermId, Var>,
883    /// Current decision level stack for backtracking
884    level_stack: Vec<usize>,
885    /// Number of processed assignments
886    processed_count: usize,
887    /// Theory checking mode
888    theory_mode: TheoryMode,
889    /// Pending assignments for lazy theory checking
890    pending_assignments: Vec<(Lit, bool)>,
891    /// Theory decision hints for branching
892    #[allow(dead_code)]
893    decision_hints: Vec<TheoryDecision>,
894    /// Pending equality notifications for Nelson-Oppen
895    pending_equalities: Vec<EqualityNotification>,
896    /// Processed equalities (to avoid duplicates)
897    processed_equalities: FxHashMap<(TermId, TermId), bool>,
898    /// Reference to solver statistics (for tracking)
899    statistics: &'a mut Statistics,
900    /// Maximum conflicts allowed (0 = unlimited)
901    max_conflicts: u64,
902    /// Maximum decisions allowed (0 = unlimited)
903    #[allow(dead_code)]
904    max_decisions: u64,
905}
906
907impl<'a> TheoryManager<'a> {
908    #[allow(clippy::too_many_arguments)]
909    fn new(
910        euf: &'a mut EufSolver,
911        arith: &'a mut ArithSolver,
912        bv: &'a mut BvSolver,
913        var_to_constraint: &'a FxHashMap<Var, Constraint>,
914        var_to_parsed_arith: &'a FxHashMap<Var, ParsedArithConstraint>,
915        term_to_var: &'a FxHashMap<TermId, Var>,
916        theory_mode: TheoryMode,
917        statistics: &'a mut Statistics,
918        max_conflicts: u64,
919        max_decisions: u64,
920    ) -> Self {
921        Self {
922            euf,
923            arith,
924            bv,
925            var_to_constraint,
926            var_to_parsed_arith,
927            term_to_var,
928            level_stack: vec![0],
929            processed_count: 0,
930            theory_mode,
931            pending_assignments: Vec::new(),
932            decision_hints: Vec::new(),
933            pending_equalities: Vec::new(),
934            processed_equalities: FxHashMap::default(),
935            statistics,
936            max_conflicts,
937            max_decisions,
938        }
939    }
940
941    /// Process Nelson-Oppen equality sharing
942    /// Propagates equalities between theories until a fixed point is reached
943    #[allow(dead_code)]
944    fn propagate_equalities(&mut self) -> TheoryCheckResult {
945        // Process all pending equalities
946        while let Some(eq) = self.pending_equalities.pop() {
947            // Avoid processing the same equality twice
948            let key = if eq.lhs < eq.rhs {
949                (eq.lhs, eq.rhs)
950            } else {
951                (eq.rhs, eq.lhs)
952            };
953
954            if self.processed_equalities.contains_key(&key) {
955                continue;
956            }
957            self.processed_equalities.insert(key, true);
958
959            // Notify EUF theory
960            let lhs_node = self.euf.intern(eq.lhs);
961            let rhs_node = self.euf.intern(eq.rhs);
962            if let Err(_e) = self
963                .euf
964                .merge(lhs_node, rhs_node, eq.reason.unwrap_or(eq.lhs))
965            {
966                // Merge failed - should not happen
967                continue;
968            }
969
970            // Check for conflicts after merging
971            if let Some(conflict_terms) = self.euf.check_conflicts() {
972                let conflict_lits = self.terms_to_conflict_clause(&conflict_terms);
973                return TheoryCheckResult::Conflict(conflict_lits);
974            }
975
976            // Notify arithmetic theory
977            self.arith.notify_equality(eq);
978        }
979
980        TheoryCheckResult::Sat
981    }
982
983    /// Model-based theory combination
984    /// Checks if theories agree on shared terms in their models
985    /// If they disagree, generates equality constraints to force agreement
986    #[allow(dead_code)]
987    fn model_based_combination(&mut self) -> TheoryCheckResult {
988        // Collect shared terms (terms that appear in multiple theories)
989        let mut shared_terms: Vec<TermId> = Vec::new();
990
991        // For now, we'll consider all terms in the mapping as potentially shared
992        // A full implementation would track which terms belong to which theories
993        for &term in self.term_to_var.keys() {
994            shared_terms.push(term);
995        }
996
997        if shared_terms.len() < 2 {
998            return TheoryCheckResult::Sat;
999        }
1000
1001        // Check if EUF and arithmetic models agree on shared terms
1002        // For each pair of terms that EUF considers equal, check if arithmetic agrees
1003        for i in 0..shared_terms.len() {
1004            for j in (i + 1)..shared_terms.len() {
1005                let t1 = shared_terms[i];
1006                let t2 = shared_terms[j];
1007
1008                // Check if EUF considers them equal
1009                let t1_node = self.euf.intern(t1);
1010                let t2_node = self.euf.intern(t2);
1011
1012                if self.euf.are_equal(t1_node, t2_node) {
1013                    // EUF says they're equal
1014                    // Check if arithmetic solver also considers them equal
1015                    let t1_value = self.arith.value(t1);
1016                    let t2_value = self.arith.value(t2);
1017
1018                    if let (Some(v1), Some(v2)) = (t1_value, t2_value)
1019                        && v1 != v2
1020                    {
1021                        // Disagreement! Generate conflict clause
1022                        // The conflict is that EUF says t1=t2 but arithmetic says t1≠t2
1023                        // We need to find the literals that led to this equality in EUF
1024                        let conflict_lits = self.terms_to_conflict_clause(&[t1, t2]);
1025                        return TheoryCheckResult::Conflict(conflict_lits);
1026                    }
1027                }
1028            }
1029        }
1030
1031        TheoryCheckResult::Sat
1032    }
1033
1034    /// Add an equality to be shared between theories
1035    #[allow(dead_code)]
1036    fn add_shared_equality(&mut self, lhs: TermId, rhs: TermId, reason: Option<TermId>) {
1037        self.pending_equalities
1038            .push(EqualityNotification { lhs, rhs, reason });
1039    }
1040
1041    /// Get theory decision hints for branching
1042    /// Returns suggested variables to branch on, ordered by priority
1043    #[allow(dead_code)]
1044    fn get_decision_hints(&mut self) -> &[TheoryDecision] {
1045        // Clear old hints
1046        self.decision_hints.clear();
1047
1048        // Collect hints from theory solvers
1049        // For now, we can suggest branching on variables that appear in
1050        // unsatisfied constraints or pending equalities
1051
1052        // EUF hints: suggest branching on disequalities that might conflict
1053        // Arithmetic hints: suggest branching on bounds that are close to being violated
1054
1055        // This is a placeholder - full implementation would query theory solvers
1056        // for their preferred branching decisions
1057
1058        &self.decision_hints
1059    }
1060
1061    /// Convert a list of term IDs to a conflict clause
1062    /// Each term ID should correspond to a constraint that was asserted
1063    fn terms_to_conflict_clause(&self, terms: &[TermId]) -> SmallVec<[Lit; 8]> {
1064        let mut conflict = SmallVec::new();
1065        for &term in terms {
1066            if let Some(&var) = self.term_to_var.get(&term) {
1067                // Negate the literal since these are the assertions that led to conflict
1068                conflict.push(Lit::neg(var));
1069            }
1070        }
1071        conflict
1072    }
1073
1074    /// Process a theory constraint
1075    fn process_constraint(
1076        &mut self,
1077        var: Var,
1078        constraint: Constraint,
1079        is_positive: bool,
1080    ) -> TheoryCheckResult {
1081        match constraint {
1082            Constraint::Eq(lhs, rhs) => {
1083                if is_positive {
1084                    // Positive assignment: a = b, tell EUF to merge
1085                    let lhs_node = self.euf.intern(lhs);
1086                    let rhs_node = self.euf.intern(rhs);
1087                    if let Err(_e) = self.euf.merge(lhs_node, rhs_node, lhs) {
1088                        // Merge failed - should not happen in normal operation
1089                        return TheoryCheckResult::Sat;
1090                    }
1091
1092                    // Check for immediate conflicts
1093                    if let Some(conflict_terms) = self.euf.check_conflicts() {
1094                        // Convert term IDs to literals for conflict clause
1095                        let conflict_lits = self.terms_to_conflict_clause(&conflict_terms);
1096                        return TheoryCheckResult::Conflict(conflict_lits);
1097                    }
1098
1099                    // For arithmetic equalities, also send to ArithSolver
1100                    // Use pre-parsed constraint if available
1101                    if let Some(parsed) = self.var_to_parsed_arith.get(&var) {
1102                        let terms: Vec<(TermId, Rational64)> =
1103                            parsed.terms.iter().copied().collect();
1104                        let constant = parsed.constant;
1105                        let reason = parsed.reason_term;
1106
1107                        // a = b means a - b <= 0 AND a - b >= 0
1108                        self.arith.assert_le(&terms, constant, reason);
1109                        self.arith.assert_ge(&terms, constant, reason);
1110
1111                        // Check ArithSolver for conflicts
1112                        use oxiz_theories::Theory;
1113                        use oxiz_theories::TheoryCheckResult as TheoryCheckResultEnum;
1114                        if let Ok(TheoryCheckResultEnum::Unsat(conflict_terms)) = self.arith.check()
1115                        {
1116                            let conflict_lits = self.terms_to_conflict_clause(&conflict_terms);
1117                            return TheoryCheckResult::Conflict(conflict_lits);
1118                        }
1119                    }
1120                } else {
1121                    // Negative assignment: a != b, tell EUF about disequality
1122                    let lhs_node = self.euf.intern(lhs);
1123                    let rhs_node = self.euf.intern(rhs);
1124                    self.euf.assert_diseq(lhs_node, rhs_node, lhs);
1125
1126                    // Check for immediate conflicts (if a = b was already derived)
1127                    if let Some(conflict_terms) = self.euf.check_conflicts() {
1128                        let conflict_lits = self.terms_to_conflict_clause(&conflict_terms);
1129                        return TheoryCheckResult::Conflict(conflict_lits);
1130                    }
1131                }
1132            }
1133            Constraint::Diseq(lhs, rhs) => {
1134                if is_positive {
1135                    // Positive assignment: a != b
1136                    let lhs_node = self.euf.intern(lhs);
1137                    let rhs_node = self.euf.intern(rhs);
1138                    self.euf.assert_diseq(lhs_node, rhs_node, lhs);
1139
1140                    if let Some(conflict_terms) = self.euf.check_conflicts() {
1141                        let conflict_lits = self.terms_to_conflict_clause(&conflict_terms);
1142                        return TheoryCheckResult::Conflict(conflict_lits);
1143                    }
1144                } else {
1145                    // Negative assignment: ~(a != b) means a = b
1146                    let lhs_node = self.euf.intern(lhs);
1147                    let rhs_node = self.euf.intern(rhs);
1148                    if let Err(_e) = self.euf.merge(lhs_node, rhs_node, lhs) {
1149                        return TheoryCheckResult::Sat;
1150                    }
1151
1152                    if let Some(conflict_terms) = self.euf.check_conflicts() {
1153                        let conflict_lits = self.terms_to_conflict_clause(&conflict_terms);
1154                        return TheoryCheckResult::Conflict(conflict_lits);
1155                    }
1156                }
1157            }
1158            // Arithmetic constraints - use parsed linear expressions
1159            Constraint::Lt(_lhs, _rhs)
1160            | Constraint::Le(_lhs, _rhs)
1161            | Constraint::Gt(_lhs, _rhs)
1162            | Constraint::Ge(_lhs, _rhs) => {
1163                // Look up the pre-parsed linear constraint
1164                if let Some(parsed) = self.var_to_parsed_arith.get(&var) {
1165                    // Add constraint to ArithSolver
1166                    let terms: Vec<(TermId, Rational64)> = parsed.terms.iter().copied().collect();
1167                    let reason = parsed.reason_term;
1168                    let constant = parsed.constant;
1169
1170                    if is_positive {
1171                        // Positive assignment: constraint holds
1172                        match parsed.constraint_type {
1173                            ArithConstraintType::Lt => {
1174                                // lhs - rhs < 0, i.e., sum of terms < constant
1175                                self.arith.assert_lt(&terms, constant, reason);
1176                            }
1177                            ArithConstraintType::Le => {
1178                                // lhs - rhs <= 0
1179                                self.arith.assert_le(&terms, constant, reason);
1180                            }
1181                            ArithConstraintType::Gt => {
1182                                // lhs - rhs > 0, i.e., sum of terms > constant
1183                                self.arith.assert_gt(&terms, constant, reason);
1184                            }
1185                            ArithConstraintType::Ge => {
1186                                // lhs - rhs >= 0
1187                                self.arith.assert_ge(&terms, constant, reason);
1188                            }
1189                        }
1190                    } else {
1191                        // Negative assignment: negation of constraint holds
1192                        // ~(a < b) => a >= b
1193                        // ~(a <= b) => a > b
1194                        // ~(a > b) => a <= b
1195                        // ~(a >= b) => a < b
1196                        match parsed.constraint_type {
1197                            ArithConstraintType::Lt => {
1198                                // ~(lhs < rhs) => lhs >= rhs
1199                                self.arith.assert_ge(&terms, constant, reason);
1200                            }
1201                            ArithConstraintType::Le => {
1202                                // ~(lhs <= rhs) => lhs > rhs
1203                                self.arith.assert_gt(&terms, constant, reason);
1204                            }
1205                            ArithConstraintType::Gt => {
1206                                // ~(lhs > rhs) => lhs <= rhs
1207                                self.arith.assert_le(&terms, constant, reason);
1208                            }
1209                            ArithConstraintType::Ge => {
1210                                // ~(lhs >= rhs) => lhs < rhs
1211                                self.arith.assert_lt(&terms, constant, reason);
1212                            }
1213                        }
1214                    }
1215
1216                    // Check ArithSolver for conflicts
1217                    use oxiz_theories::Theory;
1218                    use oxiz_theories::TheoryCheckResult as TheoryCheckResultEnum;
1219                    if let Ok(TheoryCheckResultEnum::Unsat(conflict_terms)) = self.arith.check() {
1220                        let conflict_lits = self.terms_to_conflict_clause(&conflict_terms);
1221                        return TheoryCheckResult::Conflict(conflict_lits);
1222                    }
1223                }
1224            }
1225        }
1226        TheoryCheckResult::Sat
1227    }
1228}
1229
1230impl TheoryCallback for TheoryManager<'_> {
1231    fn on_assignment(&mut self, lit: Lit) -> TheoryCheckResult {
1232        let var = lit.var();
1233        let is_positive = !lit.is_neg();
1234
1235        // Track propagation
1236        self.statistics.propagations += 1;
1237
1238        // In lazy mode, just collect assignments for batch processing
1239        if self.theory_mode == TheoryMode::Lazy {
1240            // Check if this variable has a theory constraint
1241            if self.var_to_constraint.contains_key(&var) {
1242                self.pending_assignments.push((lit, is_positive));
1243            }
1244            return TheoryCheckResult::Sat;
1245        }
1246
1247        // Eager mode: process immediately
1248        // Check if this variable has a theory constraint
1249        let Some(constraint) = self.var_to_constraint.get(&var).cloned() else {
1250            return TheoryCheckResult::Sat;
1251        };
1252
1253        self.processed_count += 1;
1254        self.statistics.theory_propagations += 1;
1255
1256        let result = self.process_constraint(var, constraint, is_positive);
1257
1258        // Track theory conflicts
1259        if matches!(result, TheoryCheckResult::Conflict(_)) {
1260            self.statistics.theory_conflicts += 1;
1261            self.statistics.conflicts += 1;
1262
1263            // Check conflict limit
1264            if self.max_conflicts > 0 && self.statistics.conflicts >= self.max_conflicts {
1265                return TheoryCheckResult::Sat; // Return Sat to signal resource exhaustion
1266            }
1267        }
1268
1269        result
1270    }
1271
1272    fn final_check(&mut self) -> TheoryCheckResult {
1273        // In lazy mode, process all pending assignments now
1274        if self.theory_mode == TheoryMode::Lazy {
1275            for &(lit, is_positive) in &self.pending_assignments.clone() {
1276                let var = lit.var();
1277                let Some(constraint) = self.var_to_constraint.get(&var).cloned() else {
1278                    continue;
1279                };
1280
1281                self.statistics.theory_propagations += 1;
1282
1283                // Process the constraint (same logic as eager mode)
1284                let result = self.process_constraint(var, constraint, is_positive);
1285                if let TheoryCheckResult::Conflict(conflict) = result {
1286                    self.statistics.theory_conflicts += 1;
1287                    self.statistics.conflicts += 1;
1288
1289                    // Check conflict limit
1290                    if self.max_conflicts > 0 && self.statistics.conflicts >= self.max_conflicts {
1291                        return TheoryCheckResult::Sat; // Signal resource exhaustion
1292                    }
1293
1294                    return TheoryCheckResult::Conflict(conflict);
1295                }
1296            }
1297            // Clear pending assignments after processing
1298            self.pending_assignments.clear();
1299        }
1300
1301        // Check EUF for conflicts
1302        if let Some(conflict_terms) = self.euf.check_conflicts() {
1303            // Convert TermIds to Lits for the conflict clause
1304            let conflict_lits = self.terms_to_conflict_clause(&conflict_terms);
1305            self.statistics.theory_conflicts += 1;
1306            self.statistics.conflicts += 1;
1307
1308            // Check conflict limit
1309            if self.max_conflicts > 0 && self.statistics.conflicts >= self.max_conflicts {
1310                return TheoryCheckResult::Sat; // Signal resource exhaustion
1311            }
1312
1313            return TheoryCheckResult::Conflict(conflict_lits);
1314        }
1315
1316        // Check arithmetic
1317        match self.arith.check() {
1318            Ok(result) => {
1319                match result {
1320                    oxiz_theories::TheoryCheckResult::Sat => {
1321                        // Arithmetic is consistent, now check model-based theory combination
1322                        // This ensures that different theories agree on shared terms
1323                        self.model_based_combination()
1324                    }
1325                    oxiz_theories::TheoryCheckResult::Unsat(conflict_terms) => {
1326                        // Arithmetic conflict detected - convert to SAT conflict clause
1327                        let conflict_lits = self.terms_to_conflict_clause(&conflict_terms);
1328                        self.statistics.theory_conflicts += 1;
1329                        self.statistics.conflicts += 1;
1330
1331                        // Check conflict limit
1332                        if self.max_conflicts > 0 && self.statistics.conflicts >= self.max_conflicts
1333                        {
1334                            return TheoryCheckResult::Sat; // Signal resource exhaustion
1335                        }
1336
1337                        TheoryCheckResult::Conflict(conflict_lits)
1338                    }
1339                    oxiz_theories::TheoryCheckResult::Propagate(_) => {
1340                        // Propagations should be handled in on_assignment
1341                        self.model_based_combination()
1342                    }
1343                    oxiz_theories::TheoryCheckResult::Unknown => {
1344                        // Theory is incomplete, be conservative
1345                        TheoryCheckResult::Sat
1346                    }
1347                }
1348            }
1349            Err(_error) => {
1350                // Internal error in the arithmetic solver
1351                // For now, be conservative and return Sat
1352                TheoryCheckResult::Sat
1353            }
1354        }
1355    }
1356
1357    fn on_new_level(&mut self, level: u32) {
1358        // Push theory state when a new decision level is created
1359        // Ensure we have enough levels in the stack
1360        while self.level_stack.len() < (level as usize + 1) {
1361            self.level_stack.push(self.processed_count);
1362            self.euf.push();
1363            self.arith.push();
1364            self.bv.push();
1365        }
1366    }
1367
1368    fn on_backtrack(&mut self, level: u32) {
1369        // Pop EUF, Arith, and BV states if needed
1370        while self.level_stack.len() > (level as usize + 1) {
1371            self.level_stack.pop();
1372            self.euf.pop();
1373            self.arith.pop();
1374            self.bv.pop();
1375        }
1376        self.processed_count = *self.level_stack.last().unwrap_or(&0);
1377
1378        // Clear pending assignments on backtrack (in lazy mode)
1379        if self.theory_mode == TheoryMode::Lazy {
1380            self.pending_assignments.clear();
1381        }
1382    }
1383}
1384
1385/// Trail operation for efficient undo
1386#[derive(Debug, Clone)]
1387enum TrailOp {
1388    /// An assertion was added
1389    AssertionAdded { index: usize },
1390    /// A variable was created
1391    VarCreated {
1392        #[allow(dead_code)]
1393        var: Var,
1394        term: TermId,
1395    },
1396    /// A constraint was added
1397    ConstraintAdded { var: Var },
1398    /// False assertion flag was set
1399    FalseAssertionSet,
1400    /// A named assertion was added
1401    NamedAssertionAdded { index: usize },
1402    /// A bitvector term was added
1403    BvTermAdded { term: TermId },
1404    /// An arithmetic term was added
1405    ArithTermAdded { term: TermId },
1406}
1407
1408/// State for push/pop with trail-based undo
1409#[derive(Debug, Clone)]
1410struct ContextState {
1411    num_assertions: usize,
1412    num_vars: usize,
1413    has_false_assertion: bool,
1414    /// Trail position at the time of push
1415    trail_position: usize,
1416}
1417
1418impl Default for Solver {
1419    fn default() -> Self {
1420        Self::new()
1421    }
1422}
1423
1424impl Solver {
1425    /// Create a new solver
1426    #[must_use]
1427    pub fn new() -> Self {
1428        Self::with_config(SolverConfig::default())
1429    }
1430
1431    /// Create a new solver with configuration
1432    #[must_use]
1433    pub fn with_config(config: SolverConfig) -> Self {
1434        let proof_enabled = config.proof;
1435
1436        // Build SAT solver configuration from our config
1437        let sat_config = SatConfig {
1438            restart_strategy: config.restart_strategy,
1439            enable_inprocessing: config.enable_inprocessing,
1440            inprocessing_interval: config.inprocessing_interval,
1441            ..SatConfig::default()
1442        };
1443
1444        // Note: The following features are controlled by the SAT solver's preprocessor
1445        // and clause management systems. We pass the configuration but the actual
1446        // implementation is in oxiz-sat:
1447        // - Clause minimization (via RecursiveMinimizer)
1448        // - Clause subsumption (via SubsumptionChecker)
1449        // - Variable elimination (via Preprocessor::variable_elimination)
1450        // - Blocked clause elimination (via Preprocessor::blocked_clause_elimination)
1451        // - Symmetry breaking (via SymmetryBreaker)
1452
1453        Self {
1454            config,
1455            sat: SatSolver::with_config(sat_config),
1456            euf: EufSolver::new(),
1457            arith: ArithSolver::lra(),
1458            bv: BvSolver::new(),
1459            mbqi: MBQISolver::new(),
1460            has_quantifiers: false,
1461            term_to_var: FxHashMap::default(),
1462            var_to_term: Vec::new(),
1463            var_to_constraint: FxHashMap::default(),
1464            var_to_parsed_arith: FxHashMap::default(),
1465            logic: None,
1466            assertions: Vec::new(),
1467            named_assertions: Vec::new(),
1468            assumption_vars: FxHashMap::default(),
1469            model: None,
1470            unsat_core: None,
1471            context_stack: Vec::new(),
1472            trail: Vec::new(),
1473            theory_processed_up_to: 0,
1474            produce_unsat_cores: false,
1475            has_false_assertion: false,
1476            polarities: FxHashMap::default(),
1477            polarity_aware: true, // Enable polarity-aware encoding by default
1478            theory_aware_branching: true, // Enable theory-aware branching by default
1479            proof: if proof_enabled {
1480                Some(Proof::new())
1481            } else {
1482                None
1483            },
1484            simplifier: Simplifier::new(),
1485            statistics: Statistics::new(),
1486            bv_terms: FxHashSet::default(),
1487            arith_terms: FxHashSet::default(),
1488        }
1489    }
1490
1491    /// Get the proof (if proof generation is enabled and the result is unsat)
1492    #[must_use]
1493    pub fn get_proof(&self) -> Option<&Proof> {
1494        self.proof.as_ref()
1495    }
1496
1497    /// Get the solver statistics
1498    #[must_use]
1499    pub fn get_statistics(&self) -> &Statistics {
1500        &self.statistics
1501    }
1502
1503    /// Reset the solver statistics
1504    pub fn reset_statistics(&mut self) {
1505        self.statistics.reset();
1506    }
1507
1508    /// Enable or disable theory-aware branching
1509    pub fn set_theory_aware_branching(&mut self, enabled: bool) {
1510        self.theory_aware_branching = enabled;
1511    }
1512
1513    /// Check if theory-aware branching is enabled
1514    #[must_use]
1515    pub fn theory_aware_branching(&self) -> bool {
1516        self.theory_aware_branching
1517    }
1518
1519    /// Enable or disable unsat core production
1520    pub fn set_produce_unsat_cores(&mut self, produce: bool) {
1521        self.produce_unsat_cores = produce;
1522    }
1523
1524    /// Set the logic
1525    pub fn set_logic(&mut self, logic: &str) {
1526        self.logic = Some(logic.to_string());
1527
1528        // Switch ArithSolver based on logic
1529        if logic.contains("LIA") || logic.contains("IDL") || logic.contains("NIA") {
1530            // Integer arithmetic logic (QF_LIA, LIA, QF_AUFLIA, QF_IDL, etc.)
1531            self.arith = ArithSolver::lia();
1532        } else if logic.contains("LRA") || logic.contains("RDL") || logic.contains("NRA") {
1533            // Real arithmetic logic (QF_LRA, LRA, QF_RDL, etc.)
1534            self.arith = ArithSolver::lra();
1535        } else if logic.contains("BV") {
1536            // Bitvector logic - use LIA since BV comparisons are handled
1537            // as bounded integer arithmetic
1538            self.arith = ArithSolver::lia();
1539        }
1540        // For other logics (QF_UF, etc.) keep the default LRA
1541    }
1542
1543    /// Collect polarity information for all subterms
1544    /// This is used for polarity-aware encoding optimization
1545    fn collect_polarities(&mut self, term: TermId, polarity: Polarity, manager: &TermManager) {
1546        // Update the polarity for this term
1547        let current = self.polarities.get(&term).copied();
1548        let new_polarity = match (current, polarity) {
1549            (Some(Polarity::Both), _) | (_, Polarity::Both) => Polarity::Both,
1550            (Some(Polarity::Positive), Polarity::Negative)
1551            | (Some(Polarity::Negative), Polarity::Positive) => Polarity::Both,
1552            (Some(p), _) => p,
1553            (None, p) => p,
1554        };
1555        self.polarities.insert(term, new_polarity);
1556
1557        // If we've reached Both polarity, no need to recurse further
1558        if current == Some(Polarity::Both) {
1559            return;
1560        }
1561
1562        // Recursively collect polarities for subterms
1563        let Some(t) = manager.get(term) else {
1564            return;
1565        };
1566
1567        match &t.kind {
1568            TermKind::Not(arg) => {
1569                let neg_polarity = match polarity {
1570                    Polarity::Positive => Polarity::Negative,
1571                    Polarity::Negative => Polarity::Positive,
1572                    Polarity::Both => Polarity::Both,
1573                };
1574                self.collect_polarities(*arg, neg_polarity, manager);
1575            }
1576            TermKind::And(args) | TermKind::Or(args) => {
1577                for &arg in args {
1578                    self.collect_polarities(arg, polarity, manager);
1579                }
1580            }
1581            TermKind::Implies(lhs, rhs) => {
1582                let neg_polarity = match polarity {
1583                    Polarity::Positive => Polarity::Negative,
1584                    Polarity::Negative => Polarity::Positive,
1585                    Polarity::Both => Polarity::Both,
1586                };
1587                self.collect_polarities(*lhs, neg_polarity, manager);
1588                self.collect_polarities(*rhs, polarity, manager);
1589            }
1590            TermKind::Ite(cond, then_br, else_br) => {
1591                self.collect_polarities(*cond, Polarity::Both, manager);
1592                self.collect_polarities(*then_br, polarity, manager);
1593                self.collect_polarities(*else_br, polarity, manager);
1594            }
1595            TermKind::Xor(lhs, rhs) | TermKind::Eq(lhs, rhs) => {
1596                // For XOR and Eq, both sides appear in both polarities
1597                self.collect_polarities(*lhs, Polarity::Both, manager);
1598                self.collect_polarities(*rhs, Polarity::Both, manager);
1599            }
1600            _ => {
1601                // For other terms (constants, variables, theory atoms), stop recursion
1602            }
1603        }
1604    }
1605
1606    /// Get a SAT variable for a term
1607    fn get_or_create_var(&mut self, term: TermId) -> Var {
1608        if let Some(&var) = self.term_to_var.get(&term) {
1609            return var;
1610        }
1611
1612        let var = self.sat.new_var();
1613        self.term_to_var.insert(term, var);
1614        self.trail.push(TrailOp::VarCreated { var, term });
1615
1616        while self.var_to_term.len() <= var.index() {
1617            self.var_to_term.push(TermId::new(0));
1618        }
1619        self.var_to_term[var.index()] = term;
1620        var
1621    }
1622
1623    /// Track theory variables in a term for model extraction
1624    /// Recursively scans a term to find Int/Real/BV variables and registers them
1625    fn track_theory_vars(&mut self, term_id: TermId, manager: &TermManager) {
1626        let Some(term) = manager.get(term_id) else {
1627            return;
1628        };
1629
1630        match &term.kind {
1631            TermKind::Var(_) => {
1632                // Found a variable - check its sort and track appropriately
1633                let is_int = term.sort == manager.sorts.int_sort;
1634                let is_real = term.sort == manager.sorts.real_sort;
1635
1636                if is_int || is_real {
1637                    if !self.arith_terms.contains(&term_id) {
1638                        self.arith_terms.insert(term_id);
1639                        self.trail.push(TrailOp::ArithTermAdded { term: term_id });
1640                        self.arith.intern(term_id);
1641                    }
1642                } else if let Some(sort) = manager.sorts.get(term.sort)
1643                    && sort.is_bitvec()
1644                    && !self.bv_terms.contains(&term_id)
1645                {
1646                    self.bv_terms.insert(term_id);
1647                    self.trail.push(TrailOp::BvTermAdded { term: term_id });
1648                    if let Some(width) = sort.bitvec_width() {
1649                        self.bv.new_bv(term_id, width);
1650                    }
1651                    // Also intern in ArithSolver for BV comparison constraints
1652                    // (BV comparisons are handled as bounded integer arithmetic)
1653                    self.arith.intern(term_id);
1654                }
1655            }
1656            // Recursively scan compound terms
1657            TermKind::Add(args)
1658            | TermKind::Mul(args)
1659            | TermKind::And(args)
1660            | TermKind::Or(args) => {
1661                for &arg in args {
1662                    self.track_theory_vars(arg, manager);
1663                }
1664            }
1665            TermKind::Sub(lhs, rhs)
1666            | TermKind::Eq(lhs, rhs)
1667            | TermKind::Lt(lhs, rhs)
1668            | TermKind::Le(lhs, rhs)
1669            | TermKind::Gt(lhs, rhs)
1670            | TermKind::Ge(lhs, rhs)
1671            | TermKind::BvAdd(lhs, rhs)
1672            | TermKind::BvSub(lhs, rhs)
1673            | TermKind::BvMul(lhs, rhs)
1674            | TermKind::BvUlt(lhs, rhs)
1675            | TermKind::BvUle(lhs, rhs)
1676            | TermKind::BvSlt(lhs, rhs)
1677            | TermKind::BvSle(lhs, rhs) => {
1678                self.track_theory_vars(*lhs, manager);
1679                self.track_theory_vars(*rhs, manager);
1680            }
1681            TermKind::Neg(arg) | TermKind::Not(arg) | TermKind::BvNot(arg) => {
1682                self.track_theory_vars(*arg, manager);
1683            }
1684            TermKind::Ite(cond, then_br, else_br) => {
1685                self.track_theory_vars(*cond, manager);
1686                self.track_theory_vars(*then_br, manager);
1687                self.track_theory_vars(*else_br, manager);
1688            }
1689            // Constants and other leaf terms - nothing to track
1690            _ => {}
1691        }
1692    }
1693
1694    /// Parse an arithmetic comparison and extract linear expression
1695    /// Returns: (terms with coefficients, constant, constraint_type)
1696    fn parse_arith_comparison(
1697        &self,
1698        lhs: TermId,
1699        rhs: TermId,
1700        constraint_type: ArithConstraintType,
1701        reason: TermId,
1702        manager: &TermManager,
1703    ) -> Option<ParsedArithConstraint> {
1704        let mut terms: SmallVec<[(TermId, Rational64); 4]> = SmallVec::new();
1705        let mut constant = Rational64::zero();
1706
1707        // Parse LHS (add positive coefficients)
1708        self.extract_linear_terms(lhs, Rational64::one(), &mut terms, &mut constant, manager)?;
1709
1710        // Parse RHS (subtract, so coefficients are negated)
1711        // For lhs OP rhs, we want lhs - rhs OP 0
1712        self.extract_linear_terms(rhs, -Rational64::one(), &mut terms, &mut constant, manager)?;
1713
1714        // Combine like terms
1715        let mut combined: FxHashMap<TermId, Rational64> = FxHashMap::default();
1716        for (term, coef) in terms {
1717            *combined.entry(term).or_insert(Rational64::zero()) += coef;
1718        }
1719
1720        // Remove zero coefficients
1721        let final_terms: SmallVec<[(TermId, Rational64); 4]> =
1722            combined.into_iter().filter(|(_, c)| !c.is_zero()).collect();
1723
1724        Some(ParsedArithConstraint {
1725            terms: final_terms,
1726            constant: -constant, // Move constant to RHS
1727            constraint_type,
1728            reason_term: reason,
1729        })
1730    }
1731
1732    /// Extract linear terms recursively from an arithmetic expression
1733    /// Returns None if the term is not linear
1734    #[allow(clippy::only_used_in_recursion)]
1735    fn extract_linear_terms(
1736        &self,
1737        term_id: TermId,
1738        scale: Rational64,
1739        terms: &mut SmallVec<[(TermId, Rational64); 4]>,
1740        constant: &mut Rational64,
1741        manager: &TermManager,
1742    ) -> Option<()> {
1743        let term = manager.get(term_id)?;
1744
1745        match &term.kind {
1746            // Integer constant
1747            TermKind::IntConst(n) => {
1748                if let Some(val) = n.to_i64() {
1749                    *constant += scale * Rational64::from_integer(val);
1750                    Some(())
1751                } else {
1752                    // BigInt too large, skip for now
1753                    None
1754                }
1755            }
1756
1757            // Rational constant
1758            TermKind::RealConst(r) => {
1759                *constant += scale * *r;
1760                Some(())
1761            }
1762
1763            // Bitvector constant - treat as integer
1764            TermKind::BitVecConst { value, .. } => {
1765                if let Some(val) = value.to_i64() {
1766                    *constant += scale * Rational64::from_integer(val);
1767                    Some(())
1768                } else {
1769                    // BigInt too large, skip for now
1770                    None
1771                }
1772            }
1773
1774            // Variable (or bitvector variable - treat as integer variable)
1775            TermKind::Var(_) => {
1776                terms.push((term_id, scale));
1777                Some(())
1778            }
1779
1780            // Addition
1781            TermKind::Add(args) => {
1782                for &arg in args {
1783                    self.extract_linear_terms(arg, scale, terms, constant, manager)?;
1784                }
1785                Some(())
1786            }
1787
1788            // Subtraction
1789            TermKind::Sub(lhs, rhs) => {
1790                self.extract_linear_terms(*lhs, scale, terms, constant, manager)?;
1791                self.extract_linear_terms(*rhs, -scale, terms, constant, manager)?;
1792                Some(())
1793            }
1794
1795            // Negation
1796            TermKind::Neg(arg) => self.extract_linear_terms(*arg, -scale, terms, constant, manager),
1797
1798            // Multiplication by constant
1799            TermKind::Mul(args) => {
1800                // Check if all but one are constants
1801                let mut const_product = Rational64::one();
1802                let mut var_term: Option<TermId> = None;
1803
1804                for &arg in args {
1805                    let arg_term = manager.get(arg)?;
1806                    match &arg_term.kind {
1807                        TermKind::IntConst(n) => {
1808                            if let Some(val) = n.to_i64() {
1809                                const_product *= Rational64::from_integer(val);
1810                            } else {
1811                                return None; // BigInt too large
1812                            }
1813                        }
1814                        TermKind::RealConst(r) => {
1815                            const_product *= *r;
1816                        }
1817                        _ => {
1818                            if var_term.is_some() {
1819                                // Multiple non-constant terms - not linear
1820                                return None;
1821                            }
1822                            var_term = Some(arg);
1823                        }
1824                    }
1825                }
1826
1827                let new_scale = scale * const_product;
1828                match var_term {
1829                    Some(v) => self.extract_linear_terms(v, new_scale, terms, constant, manager),
1830                    None => {
1831                        // All constants
1832                        *constant += new_scale;
1833                        Some(())
1834                    }
1835                }
1836            }
1837
1838            // Not linear
1839            _ => None,
1840        }
1841    }
1842
1843    /// Assert a term
1844    pub fn assert(&mut self, term: TermId, manager: &mut TermManager) {
1845        let index = self.assertions.len();
1846        self.assertions.push(term);
1847        self.trail.push(TrailOp::AssertionAdded { index });
1848
1849        // Check if this is a boolean constant first
1850        if let Some(t) = manager.get(term) {
1851            match t.kind {
1852                TermKind::False => {
1853                    // Mark that we have a false assertion
1854                    if !self.has_false_assertion {
1855                        self.has_false_assertion = true;
1856                        self.trail.push(TrailOp::FalseAssertionSet);
1857                    }
1858                    if self.produce_unsat_cores {
1859                        let na_index = self.named_assertions.len();
1860                        self.named_assertions.push(NamedAssertion {
1861                            term,
1862                            name: None,
1863                            index: index as u32,
1864                        });
1865                        self.trail
1866                            .push(TrailOp::NamedAssertionAdded { index: na_index });
1867                    }
1868                    return;
1869                }
1870                TermKind::True => {
1871                    // True is always satisfied, no need to encode
1872                    if self.produce_unsat_cores {
1873                        let na_index = self.named_assertions.len();
1874                        self.named_assertions.push(NamedAssertion {
1875                            term,
1876                            name: None,
1877                            index: index as u32,
1878                        });
1879                        self.trail
1880                            .push(TrailOp::NamedAssertionAdded { index: na_index });
1881                    }
1882                    return;
1883                }
1884                _ => {}
1885            }
1886        }
1887
1888        // Apply simplification if enabled
1889        let term_to_encode = if self.config.simplify {
1890            self.simplifier.simplify(term, manager)
1891        } else {
1892            term
1893        };
1894
1895        // Check again if simplification produced a constant
1896        if let Some(t) = manager.get(term_to_encode) {
1897            match t.kind {
1898                TermKind::False => {
1899                    if !self.has_false_assertion {
1900                        self.has_false_assertion = true;
1901                        self.trail.push(TrailOp::FalseAssertionSet);
1902                    }
1903                    return;
1904                }
1905                TermKind::True => {
1906                    // Simplified to true, no need to encode
1907                    return;
1908                }
1909                _ => {}
1910            }
1911        }
1912
1913        // Collect polarity information if polarity-aware encoding is enabled
1914        if self.polarity_aware {
1915            self.collect_polarities(term_to_encode, Polarity::Positive, manager);
1916        }
1917
1918        // Encode the assertion immediately
1919        let lit = self.encode(term_to_encode, manager);
1920        self.sat.add_clause([lit]);
1921
1922        if self.produce_unsat_cores {
1923            let na_index = self.named_assertions.len();
1924            self.named_assertions.push(NamedAssertion {
1925                term,
1926                name: None,
1927                index: index as u32,
1928            });
1929            self.trail
1930                .push(TrailOp::NamedAssertionAdded { index: na_index });
1931        }
1932    }
1933
1934    /// Assert a named term (for unsat core tracking)
1935    pub fn assert_named(&mut self, term: TermId, name: &str, manager: &mut TermManager) {
1936        let index = self.assertions.len();
1937        self.assertions.push(term);
1938        self.trail.push(TrailOp::AssertionAdded { index });
1939
1940        // Check if this is a boolean constant first
1941        if let Some(t) = manager.get(term) {
1942            match t.kind {
1943                TermKind::False => {
1944                    // Mark that we have a false assertion
1945                    if !self.has_false_assertion {
1946                        self.has_false_assertion = true;
1947                        self.trail.push(TrailOp::FalseAssertionSet);
1948                    }
1949                    if self.produce_unsat_cores {
1950                        let na_index = self.named_assertions.len();
1951                        self.named_assertions.push(NamedAssertion {
1952                            term,
1953                            name: Some(name.to_string()),
1954                            index: index as u32,
1955                        });
1956                        self.trail
1957                            .push(TrailOp::NamedAssertionAdded { index: na_index });
1958                    }
1959                    return;
1960                }
1961                TermKind::True => {
1962                    // True is always satisfied, no need to encode
1963                    if self.produce_unsat_cores {
1964                        let na_index = self.named_assertions.len();
1965                        self.named_assertions.push(NamedAssertion {
1966                            term,
1967                            name: Some(name.to_string()),
1968                            index: index as u32,
1969                        });
1970                        self.trail
1971                            .push(TrailOp::NamedAssertionAdded { index: na_index });
1972                    }
1973                    return;
1974                }
1975                _ => {}
1976            }
1977        }
1978
1979        // Collect polarity information if polarity-aware encoding is enabled
1980        if self.polarity_aware {
1981            self.collect_polarities(term, Polarity::Positive, manager);
1982        }
1983
1984        // Encode the assertion immediately
1985        let lit = self.encode(term, manager);
1986        self.sat.add_clause([lit]);
1987
1988        if self.produce_unsat_cores {
1989            let na_index = self.named_assertions.len();
1990            self.named_assertions.push(NamedAssertion {
1991                term,
1992                name: Some(name.to_string()),
1993                index: index as u32,
1994            });
1995            self.trail
1996                .push(TrailOp::NamedAssertionAdded { index: na_index });
1997        }
1998    }
1999
2000    /// Get the unsat core (after check() returned Unsat)
2001    #[must_use]
2002    pub fn get_unsat_core(&self) -> Option<&UnsatCore> {
2003        self.unsat_core.as_ref()
2004    }
2005
2006    /// Encode a term into SAT clauses using Tseitin transformation
2007    fn encode(&mut self, term: TermId, manager: &mut TermManager) -> Lit {
2008        // Clone the term data to avoid borrowing issues
2009        let Some(t) = manager.get(term).cloned() else {
2010            let var = self.get_or_create_var(term);
2011            return Lit::pos(var);
2012        };
2013
2014        match &t.kind {
2015            TermKind::True => {
2016                let var = self.get_or_create_var(manager.mk_true());
2017                self.sat.add_clause([Lit::pos(var)]);
2018                Lit::pos(var)
2019            }
2020            TermKind::False => {
2021                let var = self.get_or_create_var(manager.mk_false());
2022                self.sat.add_clause([Lit::neg(var)]);
2023                Lit::neg(var)
2024            }
2025            TermKind::Var(_) => {
2026                let var = self.get_or_create_var(term);
2027                // Track theory terms for model extraction
2028                let is_int = t.sort == manager.sorts.int_sort;
2029                let is_real = t.sort == manager.sorts.real_sort;
2030
2031                if is_int || is_real {
2032                    // Track arithmetic terms
2033                    if !self.arith_terms.contains(&term) {
2034                        self.arith_terms.insert(term);
2035                        self.trail.push(TrailOp::ArithTermAdded { term });
2036                        // Register with arithmetic solver
2037                        self.arith.intern(term);
2038                    }
2039                } else if let Some(sort) = manager.sorts.get(t.sort)
2040                    && sort.is_bitvec()
2041                    && !self.bv_terms.contains(&term)
2042                {
2043                    self.bv_terms.insert(term);
2044                    self.trail.push(TrailOp::BvTermAdded { term });
2045                    // Register with BV solver if not already registered
2046                    if let Some(width) = sort.bitvec_width() {
2047                        self.bv.new_bv(term, width);
2048                    }
2049                }
2050                Lit::pos(var)
2051            }
2052            TermKind::Not(arg) => {
2053                let arg_lit = self.encode(*arg, manager);
2054                arg_lit.negate()
2055            }
2056            TermKind::And(args) => {
2057                let result_var = self.get_or_create_var(term);
2058                let result = Lit::pos(result_var);
2059
2060                let mut arg_lits: Vec<Lit> = Vec::new();
2061                for &arg in args {
2062                    arg_lits.push(self.encode(arg, manager));
2063                }
2064
2065                // Get polarity for optimization
2066                let polarity = if self.polarity_aware {
2067                    self.polarities
2068                        .get(&term)
2069                        .copied()
2070                        .unwrap_or(Polarity::Both)
2071                } else {
2072                    Polarity::Both
2073                };
2074
2075                // result => all args (needed when result is positive)
2076                // ~result or arg1, ~result or arg2, ...
2077                if polarity != Polarity::Negative {
2078                    for &arg in &arg_lits {
2079                        self.sat.add_clause([result.negate(), arg]);
2080                    }
2081                }
2082
2083                // all args => result (needed when result is negative)
2084                // ~arg1 or ~arg2 or ... or result
2085                if polarity != Polarity::Positive {
2086                    let mut clause: Vec<Lit> = arg_lits.iter().map(|l| l.negate()).collect();
2087                    clause.push(result);
2088                    self.sat.add_clause(clause);
2089                }
2090
2091                result
2092            }
2093            TermKind::Or(args) => {
2094                let result_var = self.get_or_create_var(term);
2095                let result = Lit::pos(result_var);
2096
2097                let mut arg_lits: Vec<Lit> = Vec::new();
2098                for &arg in args {
2099                    arg_lits.push(self.encode(arg, manager));
2100                }
2101
2102                // Get polarity for optimization
2103                let polarity = if self.polarity_aware {
2104                    self.polarities
2105                        .get(&term)
2106                        .copied()
2107                        .unwrap_or(Polarity::Both)
2108                } else {
2109                    Polarity::Both
2110                };
2111
2112                // result => some arg (needed when result is positive)
2113                // ~result or arg1 or arg2 or ...
2114                if polarity != Polarity::Negative {
2115                    let mut clause: Vec<Lit> = vec![result.negate()];
2116                    clause.extend(arg_lits.iter().copied());
2117                    self.sat.add_clause(clause);
2118                }
2119
2120                // some arg => result (needed when result is negative)
2121                // ~arg1 or result, ~arg2 or result, ...
2122                if polarity != Polarity::Positive {
2123                    for &arg in &arg_lits {
2124                        self.sat.add_clause([arg.negate(), result]);
2125                    }
2126                }
2127
2128                result
2129            }
2130            TermKind::Xor(lhs, rhs) => {
2131                let lhs_lit = self.encode(*lhs, manager);
2132                let rhs_lit = self.encode(*rhs, manager);
2133
2134                let result_var = self.get_or_create_var(term);
2135                let result = Lit::pos(result_var);
2136
2137                // result <=> (lhs xor rhs)
2138                // result <=> (lhs and ~rhs) or (~lhs and rhs)
2139
2140                // result => (lhs or rhs)
2141                self.sat.add_clause([result.negate(), lhs_lit, rhs_lit]);
2142                // result => (~lhs or ~rhs)
2143                self.sat
2144                    .add_clause([result.negate(), lhs_lit.negate(), rhs_lit.negate()]);
2145
2146                // (lhs and ~rhs) => result
2147                self.sat.add_clause([lhs_lit.negate(), rhs_lit, result]);
2148                // (~lhs and rhs) => result
2149                self.sat.add_clause([lhs_lit, rhs_lit.negate(), result]);
2150
2151                result
2152            }
2153            TermKind::Implies(lhs, rhs) => {
2154                let lhs_lit = self.encode(*lhs, manager);
2155                let rhs_lit = self.encode(*rhs, manager);
2156
2157                let result_var = self.get_or_create_var(term);
2158                let result = Lit::pos(result_var);
2159
2160                // result <=> (~lhs or rhs)
2161                // result => ~lhs or rhs
2162                self.sat
2163                    .add_clause([result.negate(), lhs_lit.negate(), rhs_lit]);
2164
2165                // (~lhs or rhs) => result
2166                // lhs or result, ~rhs or result
2167                self.sat.add_clause([lhs_lit, result]);
2168                self.sat.add_clause([rhs_lit.negate(), result]);
2169
2170                result
2171            }
2172            TermKind::Ite(cond, then_br, else_br) => {
2173                let cond_lit = self.encode(*cond, manager);
2174                let then_lit = self.encode(*then_br, manager);
2175                let else_lit = self.encode(*else_br, manager);
2176
2177                let result_var = self.get_or_create_var(term);
2178                let result = Lit::pos(result_var);
2179
2180                // result <=> (cond ? then : else)
2181                // cond and result => then
2182                self.sat
2183                    .add_clause([cond_lit.negate(), result.negate(), then_lit]);
2184                // cond and then => result
2185                self.sat
2186                    .add_clause([cond_lit.negate(), then_lit.negate(), result]);
2187
2188                // ~cond and result => else
2189                self.sat.add_clause([cond_lit, result.negate(), else_lit]);
2190                // ~cond and else => result
2191                self.sat.add_clause([cond_lit, else_lit.negate(), result]);
2192
2193                result
2194            }
2195            TermKind::Eq(lhs, rhs) => {
2196                // Check if this is a boolean equality or theory equality
2197                let lhs_term = manager.get(*lhs);
2198                let is_bool_eq = lhs_term.is_some_and(|t| t.sort == manager.sorts.bool_sort);
2199
2200                if is_bool_eq {
2201                    // Boolean equality: encode as iff
2202                    let lhs_lit = self.encode(*lhs, manager);
2203                    let rhs_lit = self.encode(*rhs, manager);
2204
2205                    let result_var = self.get_or_create_var(term);
2206                    let result = Lit::pos(result_var);
2207
2208                    // result <=> (lhs <=> rhs)
2209                    // result => (lhs => rhs) and (rhs => lhs)
2210                    self.sat
2211                        .add_clause([result.negate(), lhs_lit.negate(), rhs_lit]);
2212                    self.sat
2213                        .add_clause([result.negate(), rhs_lit.negate(), lhs_lit]);
2214
2215                    // (lhs <=> rhs) => result
2216                    self.sat.add_clause([lhs_lit, rhs_lit, result]);
2217                    self.sat
2218                        .add_clause([lhs_lit.negate(), rhs_lit.negate(), result]);
2219
2220                    result
2221                } else {
2222                    // Theory equality: create a fresh boolean variable
2223                    // Store the constraint for theory propagation
2224                    let var = self.get_or_create_var(term);
2225                    self.var_to_constraint
2226                        .insert(var, Constraint::Eq(*lhs, *rhs));
2227                    self.trail.push(TrailOp::ConstraintAdded { var });
2228
2229                    // Track theory variables for model extraction
2230                    self.track_theory_vars(*lhs, manager);
2231                    self.track_theory_vars(*rhs, manager);
2232
2233                    // Pre-parse arithmetic equality for ArithSolver
2234                    // Only for Int/Real sorts, not BitVec
2235                    let is_arith = lhs_term.is_some_and(|t| {
2236                        t.sort == manager.sorts.int_sort || t.sort == manager.sorts.real_sort
2237                    });
2238                    if is_arith {
2239                        // We use Le type as placeholder since equality will be asserted
2240                        // as both Le and Ge
2241                        if let Some(parsed) = self.parse_arith_comparison(
2242                            *lhs,
2243                            *rhs,
2244                            ArithConstraintType::Le,
2245                            term,
2246                            manager,
2247                        ) {
2248                            self.var_to_parsed_arith.insert(var, parsed);
2249                        }
2250                    }
2251
2252                    Lit::pos(var)
2253                }
2254            }
2255            TermKind::Distinct(args) => {
2256                // Encode distinct as pairwise disequalities
2257                // distinct(a,b,c) <=> (a!=b) and (a!=c) and (b!=c)
2258                if args.len() <= 1 {
2259                    // trivially true
2260                    let var = self.get_or_create_var(manager.mk_true());
2261                    return Lit::pos(var);
2262                }
2263
2264                let result_var = self.get_or_create_var(term);
2265                let result = Lit::pos(result_var);
2266
2267                let mut diseq_lits = Vec::new();
2268                for i in 0..args.len() {
2269                    for j in (i + 1)..args.len() {
2270                        let eq = manager.mk_eq(args[i], args[j]);
2271                        let eq_lit = self.encode(eq, manager);
2272                        diseq_lits.push(eq_lit.negate());
2273                    }
2274                }
2275
2276                // result => all disequalities
2277                for &diseq in &diseq_lits {
2278                    self.sat.add_clause([result.negate(), diseq]);
2279                }
2280
2281                // all disequalities => result
2282                let mut clause: Vec<Lit> = diseq_lits.iter().map(|l| l.negate()).collect();
2283                clause.push(result);
2284                self.sat.add_clause(clause);
2285
2286                result
2287            }
2288            TermKind::Let { bindings, body } => {
2289                // For encoding, we can substitute the bindings into the body
2290                // This is a simplification - a more sophisticated approach would
2291                // memoize the bindings
2292                let substituted = *body;
2293                for (name, value) in bindings.iter().rev() {
2294                    // In a full implementation, we'd perform proper substitution
2295                    // For now, just encode the body directly
2296                    let _ = (name, value);
2297                }
2298                self.encode(substituted, manager)
2299            }
2300            // Theory atoms (arithmetic, bitvec, arrays, UF)
2301            // These get fresh boolean variables - the theory solver handles the semantics
2302            TermKind::IntConst(_) | TermKind::RealConst(_) | TermKind::BitVecConst { .. } => {
2303                // Constants are theory terms, not boolean formulas
2304                // Should not appear at top level in boolean context
2305                let var = self.get_or_create_var(term);
2306                Lit::pos(var)
2307            }
2308            TermKind::Neg(_)
2309            | TermKind::Add(_)
2310            | TermKind::Sub(_, _)
2311            | TermKind::Mul(_)
2312            | TermKind::Div(_, _)
2313            | TermKind::Mod(_, _) => {
2314                // Arithmetic terms - should not appear at boolean top level
2315                let var = self.get_or_create_var(term);
2316                Lit::pos(var)
2317            }
2318            TermKind::Lt(lhs, rhs) => {
2319                // Arithmetic predicate: lhs < rhs
2320                let var = self.get_or_create_var(term);
2321                self.var_to_constraint
2322                    .insert(var, Constraint::Lt(*lhs, *rhs));
2323                self.trail.push(TrailOp::ConstraintAdded { var });
2324                // Parse and store linear constraint for ArithSolver
2325                if let Some(parsed) =
2326                    self.parse_arith_comparison(*lhs, *rhs, ArithConstraintType::Lt, term, manager)
2327                {
2328                    self.var_to_parsed_arith.insert(var, parsed);
2329                }
2330                // Track theory variables for model extraction
2331                self.track_theory_vars(*lhs, manager);
2332                self.track_theory_vars(*rhs, manager);
2333                Lit::pos(var)
2334            }
2335            TermKind::Le(lhs, rhs) => {
2336                // Arithmetic predicate: lhs <= rhs
2337                let var = self.get_or_create_var(term);
2338                self.var_to_constraint
2339                    .insert(var, Constraint::Le(*lhs, *rhs));
2340                self.trail.push(TrailOp::ConstraintAdded { var });
2341                // Parse and store linear constraint for ArithSolver
2342                if let Some(parsed) =
2343                    self.parse_arith_comparison(*lhs, *rhs, ArithConstraintType::Le, term, manager)
2344                {
2345                    self.var_to_parsed_arith.insert(var, parsed);
2346                }
2347                // Track theory variables for model extraction
2348                self.track_theory_vars(*lhs, manager);
2349                self.track_theory_vars(*rhs, manager);
2350                Lit::pos(var)
2351            }
2352            TermKind::Gt(lhs, rhs) => {
2353                // Arithmetic predicate: lhs > rhs
2354                let var = self.get_or_create_var(term);
2355                self.var_to_constraint
2356                    .insert(var, Constraint::Gt(*lhs, *rhs));
2357                self.trail.push(TrailOp::ConstraintAdded { var });
2358                // Parse and store linear constraint for ArithSolver
2359                if let Some(parsed) =
2360                    self.parse_arith_comparison(*lhs, *rhs, ArithConstraintType::Gt, term, manager)
2361                {
2362                    self.var_to_parsed_arith.insert(var, parsed);
2363                }
2364                // Track theory variables for model extraction
2365                self.track_theory_vars(*lhs, manager);
2366                self.track_theory_vars(*rhs, manager);
2367                Lit::pos(var)
2368            }
2369            TermKind::Ge(lhs, rhs) => {
2370                // Arithmetic predicate: lhs >= rhs
2371                let var = self.get_or_create_var(term);
2372                self.var_to_constraint
2373                    .insert(var, Constraint::Ge(*lhs, *rhs));
2374                self.trail.push(TrailOp::ConstraintAdded { var });
2375                // Parse and store linear constraint for ArithSolver
2376                if let Some(parsed) =
2377                    self.parse_arith_comparison(*lhs, *rhs, ArithConstraintType::Ge, term, manager)
2378                {
2379                    self.var_to_parsed_arith.insert(var, parsed);
2380                }
2381                // Track theory variables for model extraction
2382                self.track_theory_vars(*lhs, manager);
2383                self.track_theory_vars(*rhs, manager);
2384                Lit::pos(var)
2385            }
2386            TermKind::BvConcat(_, _)
2387            | TermKind::BvExtract { .. }
2388            | TermKind::BvNot(_)
2389            | TermKind::BvAnd(_, _)
2390            | TermKind::BvOr(_, _)
2391            | TermKind::BvXor(_, _)
2392            | TermKind::BvAdd(_, _)
2393            | TermKind::BvSub(_, _)
2394            | TermKind::BvMul(_, _)
2395            | TermKind::BvUdiv(_, _)
2396            | TermKind::BvSdiv(_, _)
2397            | TermKind::BvUrem(_, _)
2398            | TermKind::BvSrem(_, _)
2399            | TermKind::BvShl(_, _)
2400            | TermKind::BvLshr(_, _)
2401            | TermKind::BvAshr(_, _) => {
2402                // Bitvector terms - should not appear at boolean top level
2403                let var = self.get_or_create_var(term);
2404                Lit::pos(var)
2405            }
2406            TermKind::BvUlt(lhs, rhs) => {
2407                // Bitvector unsigned less-than: treat as integer comparison
2408                let var = self.get_or_create_var(term);
2409                self.var_to_constraint
2410                    .insert(var, Constraint::Lt(*lhs, *rhs));
2411                self.trail.push(TrailOp::ConstraintAdded { var });
2412                // Parse as arithmetic constraint (bitvector as bounded integer)
2413                if let Some(parsed) =
2414                    self.parse_arith_comparison(*lhs, *rhs, ArithConstraintType::Lt, term, manager)
2415                {
2416                    self.var_to_parsed_arith.insert(var, parsed);
2417                }
2418                // Track theory variables for model extraction
2419                self.track_theory_vars(*lhs, manager);
2420                self.track_theory_vars(*rhs, manager);
2421                Lit::pos(var)
2422            }
2423            TermKind::BvUle(lhs, rhs) => {
2424                // Bitvector unsigned less-than-or-equal: treat as integer comparison
2425                let var = self.get_or_create_var(term);
2426                self.var_to_constraint
2427                    .insert(var, Constraint::Le(*lhs, *rhs));
2428                self.trail.push(TrailOp::ConstraintAdded { var });
2429                if let Some(parsed) =
2430                    self.parse_arith_comparison(*lhs, *rhs, ArithConstraintType::Le, term, manager)
2431                {
2432                    self.var_to_parsed_arith.insert(var, parsed);
2433                }
2434                // Track theory variables for model extraction
2435                self.track_theory_vars(*lhs, manager);
2436                self.track_theory_vars(*rhs, manager);
2437                Lit::pos(var)
2438            }
2439            TermKind::BvSlt(lhs, rhs) => {
2440                // Bitvector signed less-than: treat as integer comparison
2441                let var = self.get_or_create_var(term);
2442                self.var_to_constraint
2443                    .insert(var, Constraint::Lt(*lhs, *rhs));
2444                self.trail.push(TrailOp::ConstraintAdded { var });
2445                if let Some(parsed) =
2446                    self.parse_arith_comparison(*lhs, *rhs, ArithConstraintType::Lt, term, manager)
2447                {
2448                    self.var_to_parsed_arith.insert(var, parsed);
2449                }
2450                // Track theory variables for model extraction
2451                self.track_theory_vars(*lhs, manager);
2452                self.track_theory_vars(*rhs, manager);
2453                Lit::pos(var)
2454            }
2455            TermKind::BvSle(lhs, rhs) => {
2456                // Bitvector signed less-than-or-equal: treat as integer comparison
2457                let var = self.get_or_create_var(term);
2458                self.var_to_constraint
2459                    .insert(var, Constraint::Le(*lhs, *rhs));
2460                self.trail.push(TrailOp::ConstraintAdded { var });
2461                if let Some(parsed) =
2462                    self.parse_arith_comparison(*lhs, *rhs, ArithConstraintType::Le, term, manager)
2463                {
2464                    self.var_to_parsed_arith.insert(var, parsed);
2465                }
2466                // Track theory variables for model extraction
2467                self.track_theory_vars(*lhs, manager);
2468                self.track_theory_vars(*rhs, manager);
2469                Lit::pos(var)
2470            }
2471            TermKind::Select(_, _) | TermKind::Store(_, _, _) => {
2472                // Array operations - theory terms
2473                let var = self.get_or_create_var(term);
2474                Lit::pos(var)
2475            }
2476            TermKind::Apply { .. } => {
2477                // Uninterpreted function application - theory term
2478                let var = self.get_or_create_var(term);
2479                Lit::pos(var)
2480            }
2481            TermKind::Forall { patterns, .. } => {
2482                // Universal quantifiers: register with MBQI
2483                self.has_quantifiers = true;
2484                self.mbqi.add_quantifier(term, manager);
2485                // Collect ground terms from patterns as candidates
2486                for pattern in patterns {
2487                    for &trigger in pattern {
2488                        self.mbqi.collect_ground_terms(trigger, manager);
2489                    }
2490                }
2491                // Create a boolean variable for the quantifier
2492                let var = self.get_or_create_var(term);
2493                Lit::pos(var)
2494            }
2495            TermKind::Exists { patterns, .. } => {
2496                // Existential quantifiers: register with MBQI for tracking
2497                self.has_quantifiers = true;
2498                self.mbqi.add_quantifier(term, manager);
2499                // Collect ground terms from patterns
2500                for pattern in patterns {
2501                    for &trigger in pattern {
2502                        self.mbqi.collect_ground_terms(trigger, manager);
2503                    }
2504                }
2505                // Create a boolean variable for the quantifier
2506                let var = self.get_or_create_var(term);
2507                Lit::pos(var)
2508            }
2509            // String operations - theory terms and predicates
2510            TermKind::StringLit(_)
2511            | TermKind::StrConcat(_, _)
2512            | TermKind::StrLen(_)
2513            | TermKind::StrSubstr(_, _, _)
2514            | TermKind::StrAt(_, _)
2515            | TermKind::StrReplace(_, _, _)
2516            | TermKind::StrReplaceAll(_, _, _)
2517            | TermKind::StrToInt(_)
2518            | TermKind::IntToStr(_)
2519            | TermKind::StrInRe(_, _) => {
2520                // String terms - theory solver handles these
2521                let var = self.get_or_create_var(term);
2522                Lit::pos(var)
2523            }
2524            TermKind::StrContains(_, _)
2525            | TermKind::StrPrefixOf(_, _)
2526            | TermKind::StrSuffixOf(_, _)
2527            | TermKind::StrIndexOf(_, _, _) => {
2528                // String predicates - theory atoms
2529                let var = self.get_or_create_var(term);
2530                Lit::pos(var)
2531            }
2532            // Floating-point constants and special values
2533            TermKind::FpLit { .. }
2534            | TermKind::FpPlusInfinity { .. }
2535            | TermKind::FpMinusInfinity { .. }
2536            | TermKind::FpPlusZero { .. }
2537            | TermKind::FpMinusZero { .. }
2538            | TermKind::FpNaN { .. } => {
2539                // FP constants - theory terms
2540                let var = self.get_or_create_var(term);
2541                Lit::pos(var)
2542            }
2543            // Floating-point operations
2544            TermKind::FpAbs(_)
2545            | TermKind::FpNeg(_)
2546            | TermKind::FpSqrt(_, _)
2547            | TermKind::FpRoundToIntegral(_, _)
2548            | TermKind::FpAdd(_, _, _)
2549            | TermKind::FpSub(_, _, _)
2550            | TermKind::FpMul(_, _, _)
2551            | TermKind::FpDiv(_, _, _)
2552            | TermKind::FpRem(_, _)
2553            | TermKind::FpMin(_, _)
2554            | TermKind::FpMax(_, _)
2555            | TermKind::FpFma(_, _, _, _) => {
2556                // FP operations - theory terms
2557                let var = self.get_or_create_var(term);
2558                Lit::pos(var)
2559            }
2560            // Floating-point predicates
2561            TermKind::FpLeq(_, _)
2562            | TermKind::FpLt(_, _)
2563            | TermKind::FpGeq(_, _)
2564            | TermKind::FpGt(_, _)
2565            | TermKind::FpEq(_, _)
2566            | TermKind::FpIsNormal(_)
2567            | TermKind::FpIsSubnormal(_)
2568            | TermKind::FpIsZero(_)
2569            | TermKind::FpIsInfinite(_)
2570            | TermKind::FpIsNaN(_)
2571            | TermKind::FpIsNegative(_)
2572            | TermKind::FpIsPositive(_) => {
2573                // FP predicates - theory atoms that return bool
2574                let var = self.get_or_create_var(term);
2575                Lit::pos(var)
2576            }
2577            // Floating-point conversions
2578            TermKind::FpToFp { .. }
2579            | TermKind::FpToSBV { .. }
2580            | TermKind::FpToUBV { .. }
2581            | TermKind::FpToReal(_)
2582            | TermKind::RealToFp { .. }
2583            | TermKind::SBVToFp { .. }
2584            | TermKind::UBVToFp { .. } => {
2585                // FP conversions - theory terms
2586                let var = self.get_or_create_var(term);
2587                Lit::pos(var)
2588            }
2589            // Datatype operations
2590            TermKind::DtConstructor { .. }
2591            | TermKind::DtTester { .. }
2592            | TermKind::DtSelector { .. } => {
2593                // Datatype operations - theory terms
2594                let var = self.get_or_create_var(term);
2595                Lit::pos(var)
2596            }
2597            // Match expressions on datatypes
2598            TermKind::Match { .. } => {
2599                // Match expressions - theory terms
2600                let var = self.get_or_create_var(term);
2601                Lit::pos(var)
2602            }
2603        }
2604    }
2605
2606    /// Check satisfiability
2607    pub fn check(&mut self, manager: &mut TermManager) -> SolverResult {
2608        // Check for trivial unsat (false assertion)
2609        if self.has_false_assertion {
2610            self.build_unsat_core_trivial_false();
2611            return SolverResult::Unsat;
2612        }
2613
2614        if self.assertions.is_empty() {
2615            return SolverResult::Sat;
2616        }
2617
2618        // Check resource limits before starting
2619        if self.config.max_conflicts > 0 && self.statistics.conflicts >= self.config.max_conflicts {
2620            return SolverResult::Unknown;
2621        }
2622        if self.config.max_decisions > 0 && self.statistics.decisions >= self.config.max_decisions {
2623            return SolverResult::Unknown;
2624        }
2625
2626        // Run SAT solver with theory integration
2627        let mut theory_manager = TheoryManager::new(
2628            &mut self.euf,
2629            &mut self.arith,
2630            &mut self.bv,
2631            &self.var_to_constraint,
2632            &self.var_to_parsed_arith,
2633            &self.term_to_var,
2634            self.config.theory_mode,
2635            &mut self.statistics,
2636            self.config.max_conflicts,
2637            self.config.max_decisions,
2638        );
2639
2640        // MBQI loop for quantified formulas
2641        let max_mbqi_iterations = 100;
2642        let mut mbqi_iteration = 0;
2643
2644        loop {
2645            let sat_result = self.sat.solve_with_theory(&mut theory_manager);
2646
2647            match sat_result {
2648                SatResult::Unsat => {
2649                    self.build_unsat_core();
2650                    return SolverResult::Unsat;
2651                }
2652                SatResult::Unknown => {
2653                    return SolverResult::Unknown;
2654                }
2655                SatResult::Sat => {
2656                    // If no quantifiers, we're done
2657                    if !self.has_quantifiers {
2658                        self.build_model(manager);
2659                        self.unsat_core = None;
2660                        return SolverResult::Sat;
2661                    }
2662
2663                    // Build partial model for MBQI
2664                    self.build_model(manager);
2665
2666                    // Run MBQI to check quantified formulas
2667                    let model_assignments = self
2668                        .model
2669                        .as_ref()
2670                        .map(|m| m.assignments().clone())
2671                        .unwrap_or_default();
2672                    let mbqi_result = self.mbqi.check_with_model(&model_assignments, manager);
2673
2674                    match mbqi_result {
2675                        MBQIResult::NoQuantifiers | MBQIResult::Satisfied => {
2676                            // All quantifiers satisfied
2677                            self.unsat_core = None;
2678                            return SolverResult::Sat;
2679                        }
2680                        MBQIResult::InstantiationLimit => {
2681                            // Too many instantiations - return unknown
2682                            return SolverResult::Unknown;
2683                        }
2684                        MBQIResult::Conflict(conflict_terms) => {
2685                            // Add conflict clause
2686                            let lits: Vec<Lit> = conflict_terms
2687                                .iter()
2688                                .filter_map(|&t| self.term_to_var.get(&t).map(|&v| Lit::neg(v)))
2689                                .collect();
2690                            if !lits.is_empty() {
2691                                self.sat.add_clause(lits);
2692                            }
2693                            // Continue loop
2694                        }
2695                        MBQIResult::NewInstantiations(instantiations) => {
2696                            // Add instantiation lemmas
2697                            for inst in instantiations {
2698                                // The instantiation is: ∀x.φ(x) → φ(t)
2699                                // We assert φ(t) (the result term)
2700                                let lit = self.encode(inst.result, manager);
2701                                self.sat.add_clause([lit]);
2702                            }
2703                            // Continue loop
2704                        }
2705                    }
2706
2707                    mbqi_iteration += 1;
2708                    if mbqi_iteration >= max_mbqi_iterations {
2709                        return SolverResult::Unknown;
2710                    }
2711
2712                    // Recreate theory manager for next iteration
2713                    theory_manager = TheoryManager::new(
2714                        &mut self.euf,
2715                        &mut self.arith,
2716                        &mut self.bv,
2717                        &self.var_to_constraint,
2718                        &self.var_to_parsed_arith,
2719                        &self.term_to_var,
2720                        self.config.theory_mode,
2721                        &mut self.statistics,
2722                        self.config.max_conflicts,
2723                        self.config.max_decisions,
2724                    );
2725                }
2726            }
2727        }
2728    }
2729
2730    /// Check satisfiability under assumptions
2731    /// Assumptions are temporary constraints that don't modify the assertion stack
2732    pub fn check_with_assumptions(
2733        &mut self,
2734        assumptions: &[TermId],
2735        manager: &mut TermManager,
2736    ) -> SolverResult {
2737        // Save current state
2738        self.push();
2739
2740        // Assert all assumptions
2741        for &assumption in assumptions {
2742            self.assert(assumption, manager);
2743        }
2744
2745        // Check satisfiability
2746        let result = self.check(manager);
2747
2748        // Restore state
2749        self.pop();
2750
2751        result
2752    }
2753
2754    /// Check satisfiability (pure SAT, no theory integration)
2755    /// Useful for benchmarking or when theories are not needed
2756    pub fn check_sat_only(&mut self, manager: &mut TermManager) -> SolverResult {
2757        if self.assertions.is_empty() {
2758            return SolverResult::Sat;
2759        }
2760
2761        match self.sat.solve() {
2762            SatResult::Sat => {
2763                self.build_model(manager);
2764                SolverResult::Sat
2765            }
2766            SatResult::Unsat => SolverResult::Unsat,
2767            SatResult::Unknown => SolverResult::Unknown,
2768        }
2769    }
2770
2771    /// Build the model after SAT solving
2772    fn build_model(&mut self, manager: &mut TermManager) {
2773        let mut model = Model::new();
2774        let sat_model = self.sat.model();
2775
2776        // Get boolean values from SAT model
2777        for (&term, &var) in &self.term_to_var {
2778            let val = sat_model.get(var.index()).copied();
2779            if let Some(v) = val {
2780                let bool_val = if v.is_true() {
2781                    manager.mk_true()
2782                } else if v.is_false() {
2783                    manager.mk_false()
2784                } else {
2785                    continue;
2786                };
2787                model.set(term, bool_val);
2788            }
2789        }
2790
2791        // Extract values from equality constraints (e.g., x = 5)
2792        // This handles cases where a variable is equated to a constant
2793        for (&var, constraint) in &self.var_to_constraint {
2794            // Check if the equality is assigned true in the SAT model
2795            let is_true = sat_model
2796                .get(var.index())
2797                .copied()
2798                .is_some_and(|v| v.is_true());
2799
2800            if !is_true {
2801                continue;
2802            }
2803
2804            if let Constraint::Eq(lhs, rhs) = constraint {
2805                // Check if one side is a tracked variable and the other is a constant
2806                let (var_term, const_term) =
2807                    if self.arith_terms.contains(lhs) || self.bv_terms.contains(lhs) {
2808                        (*lhs, *rhs)
2809                    } else if self.arith_terms.contains(rhs) || self.bv_terms.contains(rhs) {
2810                        (*rhs, *lhs)
2811                    } else {
2812                        continue;
2813                    };
2814
2815                // Check if const_term is actually a constant
2816                let Some(const_term_data) = manager.get(const_term) else {
2817                    continue;
2818                };
2819
2820                match &const_term_data.kind {
2821                    TermKind::IntConst(n) => {
2822                        if let Some(val) = n.to_i64() {
2823                            let value_term = manager.mk_int(val);
2824                            model.set(var_term, value_term);
2825                        }
2826                    }
2827                    TermKind::RealConst(r) => {
2828                        let value_term = manager.mk_real(*r);
2829                        model.set(var_term, value_term);
2830                    }
2831                    TermKind::BitVecConst { value, width } => {
2832                        if let Some(val) = value.to_u64() {
2833                            let value_term = manager.mk_bitvec(val, *width);
2834                            model.set(var_term, value_term);
2835                        }
2836                    }
2837                    _ => {}
2838                }
2839            }
2840        }
2841
2842        // Get arithmetic values from theory solver
2843        // Iterate over tracked arithmetic terms
2844        for &term in &self.arith_terms {
2845            // Don't overwrite if already set (e.g., from equality extraction above)
2846            if model.get(term).is_some() {
2847                continue;
2848            }
2849
2850            if let Some(value) = self.arith.value(term) {
2851                // Create the appropriate value term based on whether it's integer or real
2852                let value_term = if *value.denom() == 1 {
2853                    // Integer value
2854                    manager.mk_int(*value.numer())
2855                } else {
2856                    // Rational value
2857                    manager.mk_real(value)
2858                };
2859                model.set(term, value_term);
2860            } else {
2861                // If no value from ArithSolver (e.g., unconstrained variable), use default
2862                // Get the sort to determine if it's Int or Real
2863                let is_int = manager
2864                    .get(term)
2865                    .map(|t| t.sort == manager.sorts.int_sort)
2866                    .unwrap_or(true);
2867
2868                let value_term = if is_int {
2869                    manager.mk_int(0i64)
2870                } else {
2871                    manager.mk_real(num_rational::Rational64::from_integer(0))
2872                };
2873                model.set(term, value_term);
2874            }
2875        }
2876
2877        // Get bitvector values - check ArithSolver first (for BV comparisons),
2878        // then BvSolver (for BV arithmetic/bit operations)
2879        for &term in &self.bv_terms {
2880            // Don't overwrite if already set (shouldn't happen, but be safe)
2881            if model.get(term).is_some() {
2882                continue;
2883            }
2884
2885            // Get the bitvector width from the term's sort
2886            let width = manager
2887                .get(term)
2888                .and_then(|t| manager.sorts.get(t.sort))
2889                .and_then(|s| s.bitvec_width())
2890                .unwrap_or(64);
2891
2892            // For BV comparisons handled as bounded integer arithmetic,
2893            // check ArithSolver FIRST (it has the actual constraint values)
2894            if let Some(arith_value) = self.arith.value(term) {
2895                let int_value = arith_value.to_integer();
2896                let value_term = manager.mk_bitvec(int_value, width);
2897                model.set(term, value_term);
2898            } else if let Some(bv_value) = self.bv.get_value(term) {
2899                // For BV bit operations, get value from BvSolver
2900                let value_term = manager.mk_bitvec(bv_value, width);
2901                model.set(term, value_term);
2902            } else {
2903                // If no value from either solver, use default value (0)
2904                // This handles unconstrained BV variables
2905                let value_term = manager.mk_bitvec(0i64, width);
2906                model.set(term, value_term);
2907            }
2908        }
2909
2910        self.model = Some(model);
2911    }
2912
2913    /// Build unsat core for trivial conflicts (assertion of false)
2914    fn build_unsat_core_trivial_false(&mut self) {
2915        if !self.produce_unsat_cores {
2916            self.unsat_core = None;
2917            return;
2918        }
2919
2920        // Find all assertions that are trivially false
2921        let mut core = UnsatCore::new();
2922
2923        for (i, &term) in self.assertions.iter().enumerate() {
2924            if term == TermId::new(1) {
2925                // This is a false assertion
2926                core.indices.push(i as u32);
2927
2928                // Find the name if there is one
2929                if let Some(named) = self.named_assertions.iter().find(|na| na.index == i as u32)
2930                    && let Some(ref name) = named.name
2931                {
2932                    core.names.push(name.clone());
2933                }
2934            }
2935        }
2936
2937        self.unsat_core = Some(core);
2938    }
2939
2940    /// Build unsat core from SAT solver conflict analysis
2941    fn build_unsat_core(&mut self) {
2942        if !self.produce_unsat_cores {
2943            self.unsat_core = None;
2944            return;
2945        }
2946
2947        // Build unsat core from the named assertions
2948        // In assumption-based mode, we would use the failed assumptions from the SAT solver
2949        // For now, we use a heuristic approach based on the conflict analysis
2950
2951        let mut core = UnsatCore::new();
2952
2953        // If assumption_vars is populated, we can use assumption-based extraction
2954        if !self.assumption_vars.is_empty() {
2955            // Assumption-based core extraction
2956            // Get the failed assumptions from the SAT solver
2957            // Note: This requires SAT solver support for assumption tracking
2958            // For now, include all named assertions as a conservative approach
2959            for na in &self.named_assertions {
2960                core.indices.push(na.index);
2961                if let Some(ref name) = na.name {
2962                    core.names.push(name.clone());
2963                }
2964            }
2965        } else {
2966            // Fallback: include all named assertions
2967            // This provides a valid unsat core, though not necessarily minimal
2968            for na in &self.named_assertions {
2969                core.indices.push(na.index);
2970                if let Some(ref name) = na.name {
2971                    core.names.push(name.clone());
2972                }
2973            }
2974        }
2975
2976        self.unsat_core = Some(core);
2977    }
2978
2979    /// Enable assumption-based unsat core tracking
2980    /// This creates assumption variables for each assertion
2981    /// which can be used to efficiently extract minimal unsat cores
2982    pub fn enable_assumption_based_cores(&mut self) {
2983        self.produce_unsat_cores = true;
2984        // Assumption variables would be created during assertion
2985        // to enable fine-grained core extraction
2986    }
2987
2988    /// Minimize an unsat core using greedy deletion
2989    /// This creates a minimal (but not necessarily minimum) unsatisfiable subset
2990    pub fn minimize_unsat_core(&mut self, manager: &mut TermManager) -> Option<UnsatCore> {
2991        if !self.produce_unsat_cores {
2992            return None;
2993        }
2994
2995        // Get the current unsat core
2996        let core = self.unsat_core.as_ref()?;
2997        if core.is_empty() {
2998            return Some(core.clone());
2999        }
3000
3001        // Extract the assertions in the core
3002        let mut core_assertions: Vec<_> = core
3003            .indices
3004            .iter()
3005            .map(|&idx| {
3006                let assertion = self.assertions[idx as usize];
3007                let name = self
3008                    .named_assertions
3009                    .iter()
3010                    .find(|na| na.index == idx)
3011                    .and_then(|na| na.name.clone());
3012                (idx, assertion, name)
3013            })
3014            .collect();
3015
3016        // Try to remove each assertion one by one
3017        let mut i = 0;
3018        while i < core_assertions.len() {
3019            // Create a temporary solver with all assertions except the i-th one
3020            let mut temp_solver = Solver::new();
3021            temp_solver.set_logic(self.logic.as_deref().unwrap_or("ALL"));
3022
3023            // Add all assertions except the i-th one
3024            for (j, &(_, assertion, _)) in core_assertions.iter().enumerate() {
3025                if i != j {
3026                    temp_solver.assert(assertion, manager);
3027                }
3028            }
3029
3030            // Check if still unsat
3031            if temp_solver.check(manager) == SolverResult::Unsat {
3032                // Still unsat without this assertion - remove it
3033                core_assertions.remove(i);
3034                // Don't increment i, check the next element which is now at position i
3035            } else {
3036                // This assertion is needed
3037                i += 1;
3038            }
3039        }
3040
3041        // Build the minimized core
3042        let mut minimized = UnsatCore::new();
3043        for (idx, _, name) in core_assertions {
3044            minimized.indices.push(idx);
3045            if let Some(n) = name {
3046                minimized.names.push(n);
3047            }
3048        }
3049
3050        Some(minimized)
3051    }
3052
3053    /// Get the model (if sat)
3054    #[must_use]
3055    pub fn model(&self) -> Option<&Model> {
3056        self.model.as_ref()
3057    }
3058
3059    /// Assert multiple terms at once
3060    /// This is more efficient than calling assert() multiple times
3061    pub fn assert_many(&mut self, terms: &[TermId], manager: &mut TermManager) {
3062        for &term in terms {
3063            self.assert(term, manager);
3064        }
3065    }
3066
3067    /// Get the number of assertions in the solver
3068    #[must_use]
3069    pub fn num_assertions(&self) -> usize {
3070        self.assertions.len()
3071    }
3072
3073    /// Get the number of variables in the SAT solver
3074    #[must_use]
3075    pub fn num_variables(&self) -> usize {
3076        self.term_to_var.len()
3077    }
3078
3079    /// Check if the solver has any assertions
3080    #[must_use]
3081    pub fn has_assertions(&self) -> bool {
3082        !self.assertions.is_empty()
3083    }
3084
3085    /// Get the current context level (push/pop depth)
3086    #[must_use]
3087    pub fn context_level(&self) -> usize {
3088        self.context_stack.len()
3089    }
3090
3091    /// Push a context level
3092    pub fn push(&mut self) {
3093        self.context_stack.push(ContextState {
3094            num_assertions: self.assertions.len(),
3095            num_vars: self.var_to_term.len(),
3096            has_false_assertion: self.has_false_assertion,
3097            trail_position: self.trail.len(),
3098        });
3099        self.sat.push();
3100        self.euf.push();
3101        self.arith.push();
3102    }
3103
3104    /// Pop a context level using trail-based undo
3105    pub fn pop(&mut self) {
3106        if let Some(state) = self.context_stack.pop() {
3107            // Undo all operations in the trail since the push
3108            while self.trail.len() > state.trail_position {
3109                if let Some(op) = self.trail.pop() {
3110                    match op {
3111                        TrailOp::AssertionAdded { index } => {
3112                            if self.assertions.len() > index {
3113                                self.assertions.truncate(index);
3114                            }
3115                        }
3116                        TrailOp::VarCreated { var: _, term } => {
3117                            // Remove the term-to-var mapping
3118                            self.term_to_var.remove(&term);
3119                        }
3120                        TrailOp::ConstraintAdded { var } => {
3121                            // Remove the constraint
3122                            self.var_to_constraint.remove(&var);
3123                        }
3124                        TrailOp::FalseAssertionSet => {
3125                            // Reset the flag
3126                            self.has_false_assertion = false;
3127                        }
3128                        TrailOp::NamedAssertionAdded { index } => {
3129                            // Remove the named assertion
3130                            if self.named_assertions.len() > index {
3131                                self.named_assertions.truncate(index);
3132                            }
3133                        }
3134                        TrailOp::BvTermAdded { term } => {
3135                            // Remove the bitvector term
3136                            self.bv_terms.remove(&term);
3137                        }
3138                        TrailOp::ArithTermAdded { term } => {
3139                            // Remove the arithmetic term
3140                            self.arith_terms.remove(&term);
3141                        }
3142                    }
3143                }
3144            }
3145
3146            // Use state to restore other fields
3147            self.assertions.truncate(state.num_assertions);
3148            self.var_to_term.truncate(state.num_vars);
3149            self.has_false_assertion = state.has_false_assertion;
3150
3151            self.sat.pop();
3152            self.euf.pop();
3153            self.arith.pop();
3154        }
3155    }
3156
3157    /// Reset the solver
3158    pub fn reset(&mut self) {
3159        self.sat.reset();
3160        self.euf.reset();
3161        self.arith.reset();
3162        self.bv.reset();
3163        self.term_to_var.clear();
3164        self.var_to_term.clear();
3165        self.var_to_constraint.clear();
3166        self.var_to_parsed_arith.clear();
3167        self.assertions.clear();
3168        self.named_assertions.clear();
3169        self.model = None;
3170        self.unsat_core = None;
3171        self.context_stack.clear();
3172        self.trail.clear();
3173        self.logic = None;
3174        self.theory_processed_up_to = 0;
3175        self.has_false_assertion = false;
3176        self.bv_terms.clear();
3177        self.arith_terms.clear();
3178    }
3179
3180    /// Get the configuration
3181    #[must_use]
3182    pub fn config(&self) -> &SolverConfig {
3183        &self.config
3184    }
3185
3186    /// Set configuration
3187    pub fn set_config(&mut self, config: SolverConfig) {
3188        self.config = config;
3189    }
3190
3191    /// Get solver statistics
3192    #[must_use]
3193    pub fn stats(&self) -> &oxiz_sat::SolverStats {
3194        self.sat.stats()
3195    }
3196}
3197
3198#[cfg(test)]
3199mod tests {
3200    use super::*;
3201
3202    #[test]
3203    fn test_solver_empty() {
3204        let mut solver = Solver::new();
3205        let mut manager = TermManager::new();
3206        assert_eq!(solver.check(&mut manager), SolverResult::Sat);
3207    }
3208
3209    #[test]
3210    fn test_solver_true() {
3211        let mut solver = Solver::new();
3212        let mut manager = TermManager::new();
3213        let t = manager.mk_true();
3214        solver.assert(t, &mut manager);
3215        assert_eq!(solver.check(&mut manager), SolverResult::Sat);
3216    }
3217
3218    #[test]
3219    fn test_solver_false() {
3220        let mut solver = Solver::new();
3221        let mut manager = TermManager::new();
3222        let f = manager.mk_false();
3223        solver.assert(f, &mut manager);
3224        assert_eq!(solver.check(&mut manager), SolverResult::Unsat);
3225    }
3226
3227    #[test]
3228    fn test_solver_push_pop() {
3229        let mut solver = Solver::new();
3230        let mut manager = TermManager::new();
3231
3232        let t = manager.mk_true();
3233        solver.assert(t, &mut manager);
3234        solver.push();
3235
3236        let f = manager.mk_false();
3237        solver.assert(f, &mut manager);
3238        assert_eq!(solver.check(&mut manager), SolverResult::Unsat);
3239
3240        solver.pop();
3241        assert_eq!(solver.check(&mut manager), SolverResult::Sat);
3242    }
3243
3244    #[test]
3245    fn test_unsat_core_trivial() {
3246        let mut solver = Solver::new();
3247        let mut manager = TermManager::new();
3248        solver.set_produce_unsat_cores(true);
3249
3250        let t = manager.mk_true();
3251        let f = manager.mk_false();
3252
3253        solver.assert_named(t, "a1", &mut manager);
3254        solver.assert_named(f, "a2", &mut manager);
3255        solver.assert_named(t, "a3", &mut manager);
3256
3257        assert_eq!(solver.check(&mut manager), SolverResult::Unsat);
3258
3259        let core = solver.get_unsat_core();
3260        assert!(core.is_some());
3261
3262        let core = core.unwrap();
3263        assert!(!core.is_empty());
3264        assert!(core.names.contains(&"a2".to_string()));
3265    }
3266
3267    #[test]
3268    fn test_unsat_core_not_produced_when_sat() {
3269        let mut solver = Solver::new();
3270        let mut manager = TermManager::new();
3271        solver.set_produce_unsat_cores(true);
3272
3273        let t = manager.mk_true();
3274        solver.assert_named(t, "a1", &mut manager);
3275        solver.assert_named(t, "a2", &mut manager);
3276
3277        assert_eq!(solver.check(&mut manager), SolverResult::Sat);
3278        assert!(solver.get_unsat_core().is_none());
3279    }
3280
3281    #[test]
3282    fn test_unsat_core_disabled() {
3283        let mut solver = Solver::new();
3284        let mut manager = TermManager::new();
3285        // Don't enable unsat cores
3286
3287        let f = manager.mk_false();
3288        solver.assert(f, &mut manager);
3289        assert_eq!(solver.check(&mut manager), SolverResult::Unsat);
3290
3291        // Core should be None when not enabled
3292        assert!(solver.get_unsat_core().is_none());
3293    }
3294
3295    #[test]
3296    fn test_boolean_encoding_and() {
3297        let mut solver = Solver::new();
3298        let mut manager = TermManager::new();
3299
3300        // Test: (p and q) should be SAT with p=true, q=true
3301        let p = manager.mk_var("p", manager.sorts.bool_sort);
3302        let q = manager.mk_var("q", manager.sorts.bool_sort);
3303        let and = manager.mk_and(vec![p, q]);
3304
3305        solver.assert(and, &mut manager);
3306        assert_eq!(solver.check(&mut manager), SolverResult::Sat);
3307
3308        // The model should have both p and q as true
3309        let model = solver.model().expect("Should have model");
3310        assert!(model.get(p).is_some());
3311        assert!(model.get(q).is_some());
3312    }
3313
3314    #[test]
3315    fn test_boolean_encoding_or() {
3316        let mut solver = Solver::new();
3317        let mut manager = TermManager::new();
3318
3319        // Test: (p or q) and (not p) should be SAT with q=true
3320        let p = manager.mk_var("p", manager.sorts.bool_sort);
3321        let q = manager.mk_var("q", manager.sorts.bool_sort);
3322        let or = manager.mk_or(vec![p, q]);
3323        let not_p = manager.mk_not(p);
3324
3325        solver.assert(or, &mut manager);
3326        solver.assert(not_p, &mut manager);
3327        assert_eq!(solver.check(&mut manager), SolverResult::Sat);
3328    }
3329
3330    #[test]
3331    fn test_boolean_encoding_implies() {
3332        let mut solver = Solver::new();
3333        let mut manager = TermManager::new();
3334
3335        // Test: (p => q) and p and (not q) should be UNSAT
3336        let p = manager.mk_var("p", manager.sorts.bool_sort);
3337        let q = manager.mk_var("q", manager.sorts.bool_sort);
3338        let implies = manager.mk_implies(p, q);
3339        let not_q = manager.mk_not(q);
3340
3341        solver.assert(implies, &mut manager);
3342        solver.assert(p, &mut manager);
3343        solver.assert(not_q, &mut manager);
3344        assert_eq!(solver.check(&mut manager), SolverResult::Unsat);
3345    }
3346
3347    #[test]
3348    fn test_boolean_encoding_distinct() {
3349        let mut solver = Solver::new();
3350        let mut manager = TermManager::new();
3351
3352        // Test: distinct(p, q, r) and p and q should be UNSAT (since p=q)
3353        let p = manager.mk_var("p", manager.sorts.bool_sort);
3354        let q = manager.mk_var("q", manager.sorts.bool_sort);
3355        let r = manager.mk_var("r", manager.sorts.bool_sort);
3356        let distinct = manager.mk_distinct(vec![p, q, r]);
3357
3358        solver.assert(distinct, &mut manager);
3359        solver.assert(p, &mut manager);
3360        solver.assert(q, &mut manager);
3361        assert_eq!(solver.check(&mut manager), SolverResult::Unsat);
3362    }
3363
3364    #[test]
3365    fn test_model_evaluation_bool() {
3366        let mut solver = Solver::new();
3367        let mut manager = TermManager::new();
3368
3369        let p = manager.mk_var("p", manager.sorts.bool_sort);
3370        let q = manager.mk_var("q", manager.sorts.bool_sort);
3371
3372        // Assert p and not q
3373        solver.assert(p, &mut manager);
3374        solver.assert(manager.mk_not(q), &mut manager);
3375        assert_eq!(solver.check(&mut manager), SolverResult::Sat);
3376
3377        let model = solver.model().expect("Should have model");
3378
3379        // Evaluate p (should be true)
3380        let p_val = model.eval(p, &mut manager);
3381        assert_eq!(p_val, manager.mk_true());
3382
3383        // Evaluate q (should be false)
3384        let q_val = model.eval(q, &mut manager);
3385        assert_eq!(q_val, manager.mk_false());
3386
3387        // Evaluate (p and q) - should be false
3388        let and_term = manager.mk_and(vec![p, q]);
3389        let and_val = model.eval(and_term, &mut manager);
3390        assert_eq!(and_val, manager.mk_false());
3391
3392        // Evaluate (p or q) - should be true
3393        let or_term = manager.mk_or(vec![p, q]);
3394        let or_val = model.eval(or_term, &mut manager);
3395        assert_eq!(or_val, manager.mk_true());
3396    }
3397
3398    #[test]
3399    fn test_model_evaluation_ite() {
3400        let mut solver = Solver::new();
3401        let mut manager = TermManager::new();
3402
3403        let p = manager.mk_var("p", manager.sorts.bool_sort);
3404        let q = manager.mk_var("q", manager.sorts.bool_sort);
3405        let r = manager.mk_var("r", manager.sorts.bool_sort);
3406
3407        // Assert p
3408        solver.assert(p, &mut manager);
3409        assert_eq!(solver.check(&mut manager), SolverResult::Sat);
3410
3411        let model = solver.model().expect("Should have model");
3412
3413        // Evaluate (ite p q r) - should evaluate to q since p is true
3414        let ite_term = manager.mk_ite(p, q, r);
3415        let ite_val = model.eval(ite_term, &mut manager);
3416        // The result should be q's value (whatever it is in the model)
3417        let q_val = model.eval(q, &mut manager);
3418        assert_eq!(ite_val, q_val);
3419    }
3420
3421    #[test]
3422    fn test_model_evaluation_implies() {
3423        let mut solver = Solver::new();
3424        let mut manager = TermManager::new();
3425
3426        let p = manager.mk_var("p", manager.sorts.bool_sort);
3427        let q = manager.mk_var("q", manager.sorts.bool_sort);
3428
3429        // Assert not p
3430        solver.assert(manager.mk_not(p), &mut manager);
3431        assert_eq!(solver.check(&mut manager), SolverResult::Sat);
3432
3433        let model = solver.model().expect("Should have model");
3434
3435        // Evaluate (p => q) - should be true since p is false
3436        let implies_term = manager.mk_implies(p, q);
3437        let implies_val = model.eval(implies_term, &mut manager);
3438        assert_eq!(implies_val, manager.mk_true());
3439    }
3440
3441    #[test]
3442    fn test_bv_comparison_model_generation() {
3443        // Test BV comparison: 5 < x < 10 should give x in range [6, 9]
3444        let mut solver = Solver::new();
3445        let mut manager = TermManager::new();
3446
3447        solver.set_logic("QF_BV");
3448
3449        // Create BitVec[8] variable
3450        let bv8_sort = manager.sorts.bitvec(8);
3451        let x = manager.mk_var("x", bv8_sort);
3452
3453        // Create constants
3454        let five = manager.mk_bitvec(5i64, 8);
3455        let ten = manager.mk_bitvec(10i64, 8);
3456
3457        // Assert: 5 < x (unsigned)
3458        let lt1 = manager.mk_bv_ult(five, x);
3459        solver.assert(lt1, &mut manager);
3460
3461        // Assert: x < 10 (unsigned)
3462        let lt2 = manager.mk_bv_ult(x, ten);
3463        solver.assert(lt2, &mut manager);
3464
3465        let result = solver.check(&mut manager);
3466        assert_eq!(result, SolverResult::Sat);
3467
3468        // Check that we get a valid model
3469        let model = solver.model().expect("Should have model");
3470
3471        // Get the value of x
3472        if let Some(x_value_id) = model.get(x) {
3473            if let Some(x_term) = manager.get(x_value_id) {
3474                if let TermKind::BitVecConst { value, .. } = &x_term.kind {
3475                    let x_val = value.to_u64().unwrap_or(0);
3476                    // x should be in range [6, 9]
3477                    assert!(
3478                        x_val >= 6 && x_val <= 9,
3479                        "Expected x in [6,9], got {}",
3480                        x_val
3481                    );
3482                }
3483            }
3484        }
3485    }
3486
3487    #[test]
3488    fn test_arithmetic_model_generation() {
3489        use num_bigint::BigInt;
3490
3491        let mut solver = Solver::new();
3492        let mut manager = TermManager::new();
3493
3494        // Create integer variables
3495        let x = manager.mk_var("x", manager.sorts.int_sort);
3496        let y = manager.mk_var("y", manager.sorts.int_sort);
3497
3498        // Create constraints: x + y = 10, x >= 0, y >= 0
3499        let ten = manager.mk_int(BigInt::from(10));
3500        let zero = manager.mk_int(BigInt::from(0));
3501        let sum = manager.mk_add(vec![x, y]);
3502
3503        let eq = manager.mk_eq(sum, ten);
3504        let x_ge_0 = manager.mk_ge(x, zero);
3505        let y_ge_0 = manager.mk_ge(y, zero);
3506
3507        solver.assert(eq, &mut manager);
3508        solver.assert(x_ge_0, &mut manager);
3509        solver.assert(y_ge_0, &mut manager);
3510
3511        assert_eq!(solver.check(&mut manager), SolverResult::Sat);
3512
3513        // Check that we can get a model (even if the arithmetic values aren't fully computed yet)
3514        let model = solver.model();
3515        assert!(model.is_some(), "Should have a model for SAT result");
3516    }
3517
3518    #[test]
3519    fn test_model_pretty_print() {
3520        let mut solver = Solver::new();
3521        let mut manager = TermManager::new();
3522
3523        let p = manager.mk_var("p", manager.sorts.bool_sort);
3524        let q = manager.mk_var("q", manager.sorts.bool_sort);
3525
3526        solver.assert(p, &mut manager);
3527        solver.assert(manager.mk_not(q), &mut manager);
3528        assert_eq!(solver.check(&mut manager), SolverResult::Sat);
3529
3530        let model = solver.model().expect("Should have model");
3531        let pretty = model.pretty_print(&manager);
3532
3533        // Should contain the model structure
3534        assert!(pretty.contains("(model"));
3535        assert!(pretty.contains("define-fun"));
3536        // Should contain variable names
3537        assert!(pretty.contains("p") || pretty.contains("q"));
3538    }
3539
3540    #[test]
3541    fn test_trail_based_undo_assertions() {
3542        let mut solver = Solver::new();
3543        let mut manager = TermManager::new();
3544
3545        let p = manager.mk_var("p", manager.sorts.bool_sort);
3546        let q = manager.mk_var("q", manager.sorts.bool_sort);
3547
3548        // Initial state
3549        assert_eq!(solver.assertions.len(), 0);
3550        assert_eq!(solver.trail.len(), 0);
3551
3552        // Assert p
3553        solver.assert(p, &mut manager);
3554        assert_eq!(solver.assertions.len(), 1);
3555        assert!(!solver.trail.is_empty());
3556
3557        // Push and assert q
3558        solver.push();
3559        let trail_len_after_push = solver.trail.len();
3560        solver.assert(q, &mut manager);
3561        assert_eq!(solver.assertions.len(), 2);
3562        assert!(solver.trail.len() > trail_len_after_push);
3563
3564        // Pop should undo the second assertion
3565        solver.pop();
3566        assert_eq!(solver.assertions.len(), 1);
3567        assert_eq!(solver.assertions[0], p);
3568    }
3569
3570    #[test]
3571    fn test_trail_based_undo_variables() {
3572        let mut solver = Solver::new();
3573        let mut manager = TermManager::new();
3574
3575        let p = manager.mk_var("p", manager.sorts.bool_sort);
3576        let q = manager.mk_var("q", manager.sorts.bool_sort);
3577
3578        // Assert p creates variables
3579        solver.assert(p, &mut manager);
3580        let initial_var_count = solver.term_to_var.len();
3581
3582        // Push and assert q
3583        solver.push();
3584        solver.assert(q, &mut manager);
3585        assert!(solver.term_to_var.len() >= initial_var_count);
3586
3587        // Pop should remove q's variable
3588        solver.pop();
3589        // Note: Some variables may remain due to encoding, but q should be removed
3590        assert_eq!(solver.assertions.len(), 1);
3591    }
3592
3593    #[test]
3594    fn test_trail_based_undo_constraints() {
3595        use num_bigint::BigInt;
3596
3597        let mut solver = Solver::new();
3598        let mut manager = TermManager::new();
3599
3600        let x = manager.mk_var("x", manager.sorts.int_sort);
3601        let zero = manager.mk_int(BigInt::from(0));
3602
3603        // Assert x >= 0 creates a constraint
3604        let c1 = manager.mk_ge(x, zero);
3605        solver.assert(c1, &mut manager);
3606        let initial_constraint_count = solver.var_to_constraint.len();
3607
3608        // Push and add another constraint
3609        solver.push();
3610        let ten = manager.mk_int(BigInt::from(10));
3611        let c2 = manager.mk_le(x, ten);
3612        solver.assert(c2, &mut manager);
3613        assert!(solver.var_to_constraint.len() >= initial_constraint_count);
3614
3615        // Pop should remove the second constraint
3616        solver.pop();
3617        assert_eq!(solver.var_to_constraint.len(), initial_constraint_count);
3618    }
3619
3620    #[test]
3621    fn test_trail_based_undo_false_assertion() {
3622        let mut solver = Solver::new();
3623        let mut manager = TermManager::new();
3624
3625        assert!(!solver.has_false_assertion);
3626
3627        solver.push();
3628        solver.assert(manager.mk_false(), &mut manager);
3629        assert!(solver.has_false_assertion);
3630
3631        solver.pop();
3632        assert!(!solver.has_false_assertion);
3633    }
3634
3635    #[test]
3636    fn test_trail_based_undo_named_assertions() {
3637        let mut solver = Solver::new();
3638        let mut manager = TermManager::new();
3639        solver.set_produce_unsat_cores(true);
3640
3641        let p = manager.mk_var("p", manager.sorts.bool_sort);
3642
3643        solver.assert_named(p, "assertion1", &mut manager);
3644        assert_eq!(solver.named_assertions.len(), 1);
3645
3646        solver.push();
3647        let q = manager.mk_var("q", manager.sorts.bool_sort);
3648        solver.assert_named(q, "assertion2", &mut manager);
3649        assert_eq!(solver.named_assertions.len(), 2);
3650
3651        solver.pop();
3652        assert_eq!(solver.named_assertions.len(), 1);
3653        assert_eq!(
3654            solver.named_assertions[0].name,
3655            Some("assertion1".to_string())
3656        );
3657    }
3658
3659    #[test]
3660    fn test_trail_based_undo_nested_push_pop() {
3661        let mut solver = Solver::new();
3662        let mut manager = TermManager::new();
3663
3664        let p = manager.mk_var("p", manager.sorts.bool_sort);
3665        solver.assert(p, &mut manager);
3666
3667        // First push
3668        solver.push();
3669        let q = manager.mk_var("q", manager.sorts.bool_sort);
3670        solver.assert(q, &mut manager);
3671        assert_eq!(solver.assertions.len(), 2);
3672
3673        // Second push
3674        solver.push();
3675        let r = manager.mk_var("r", manager.sorts.bool_sort);
3676        solver.assert(r, &mut manager);
3677        assert_eq!(solver.assertions.len(), 3);
3678
3679        // Pop once
3680        solver.pop();
3681        assert_eq!(solver.assertions.len(), 2);
3682
3683        // Pop again
3684        solver.pop();
3685        assert_eq!(solver.assertions.len(), 1);
3686        assert_eq!(solver.assertions[0], p);
3687    }
3688
3689    #[test]
3690    fn test_config_presets() {
3691        // Test that all presets can be created without panicking
3692        let _fast = SolverConfig::fast();
3693        let _balanced = SolverConfig::balanced();
3694        let _thorough = SolverConfig::thorough();
3695        let _minimal = SolverConfig::minimal();
3696    }
3697
3698    #[test]
3699    fn test_config_fast_characteristics() {
3700        let config = SolverConfig::fast();
3701
3702        // Fast config should disable expensive features
3703        assert!(!config.enable_variable_elimination);
3704        assert!(!config.enable_blocked_clause_elimination);
3705        assert!(!config.enable_symmetry_breaking);
3706        assert!(!config.enable_inprocessing);
3707        assert!(!config.enable_clause_subsumption);
3708
3709        // But keep fast optimizations
3710        assert!(config.enable_clause_minimization);
3711        assert!(config.simplify);
3712
3713        // Should use Geometric restarts (faster)
3714        assert_eq!(config.restart_strategy, RestartStrategy::Geometric);
3715    }
3716
3717    #[test]
3718    fn test_config_balanced_characteristics() {
3719        let config = SolverConfig::balanced();
3720
3721        // Balanced should enable most features with moderate settings
3722        assert!(config.enable_variable_elimination);
3723        assert!(config.enable_blocked_clause_elimination);
3724        assert!(config.enable_inprocessing);
3725        assert!(config.enable_clause_minimization);
3726        assert!(config.enable_clause_subsumption);
3727        assert!(config.simplify);
3728
3729        // But not the most expensive one
3730        assert!(!config.enable_symmetry_breaking);
3731
3732        // Should use Glucose restarts (adaptive)
3733        assert_eq!(config.restart_strategy, RestartStrategy::Glucose);
3734
3735        // Conservative limits
3736        assert_eq!(config.variable_elimination_limit, 1000);
3737        assert_eq!(config.inprocessing_interval, 10000);
3738    }
3739
3740    #[test]
3741    fn test_config_thorough_characteristics() {
3742        let config = SolverConfig::thorough();
3743
3744        // Thorough should enable all features
3745        assert!(config.enable_variable_elimination);
3746        assert!(config.enable_blocked_clause_elimination);
3747        assert!(config.enable_symmetry_breaking); // Even this expensive one
3748        assert!(config.enable_inprocessing);
3749        assert!(config.enable_clause_minimization);
3750        assert!(config.enable_clause_subsumption);
3751        assert!(config.simplify);
3752
3753        // Aggressive settings
3754        assert_eq!(config.variable_elimination_limit, 5000);
3755        assert_eq!(config.inprocessing_interval, 5000);
3756    }
3757
3758    #[test]
3759    fn test_config_minimal_characteristics() {
3760        let config = SolverConfig::minimal();
3761
3762        // Minimal should disable everything optional
3763        assert!(!config.simplify);
3764        assert!(!config.enable_variable_elimination);
3765        assert!(!config.enable_blocked_clause_elimination);
3766        assert!(!config.enable_symmetry_breaking);
3767        assert!(!config.enable_inprocessing);
3768        assert!(!config.enable_clause_minimization);
3769        assert!(!config.enable_clause_subsumption);
3770
3771        // Should use lazy theory mode for minimal overhead
3772        assert_eq!(config.theory_mode, TheoryMode::Lazy);
3773
3774        // Single threaded
3775        assert_eq!(config.num_threads, 1);
3776    }
3777
3778    #[test]
3779    fn test_config_builder_pattern() {
3780        // Test the builder-style methods
3781        let config = SolverConfig::fast()
3782            .with_proof()
3783            .with_timeout(5000)
3784            .with_max_conflicts(1000)
3785            .with_max_decisions(2000)
3786            .with_parallel(8)
3787            .with_restart_strategy(RestartStrategy::Luby)
3788            .with_theory_mode(TheoryMode::Lazy);
3789
3790        assert!(config.proof);
3791        assert_eq!(config.timeout_ms, 5000);
3792        assert_eq!(config.max_conflicts, 1000);
3793        assert_eq!(config.max_decisions, 2000);
3794        assert!(config.parallel);
3795        assert_eq!(config.num_threads, 8);
3796        assert_eq!(config.restart_strategy, RestartStrategy::Luby);
3797        assert_eq!(config.theory_mode, TheoryMode::Lazy);
3798    }
3799
3800    #[test]
3801    fn test_solver_with_different_configs() {
3802        let mut manager = TermManager::new();
3803
3804        // Create solvers with different configs
3805        let mut solver_fast = Solver::with_config(SolverConfig::fast());
3806        let mut solver_balanced = Solver::with_config(SolverConfig::balanced());
3807        let mut solver_thorough = Solver::with_config(SolverConfig::thorough());
3808        let mut solver_minimal = Solver::with_config(SolverConfig::minimal());
3809
3810        // They should all solve a simple problem correctly
3811        let t = manager.mk_true();
3812        solver_fast.assert(t, &mut manager);
3813        solver_balanced.assert(t, &mut manager);
3814        solver_thorough.assert(t, &mut manager);
3815        solver_minimal.assert(t, &mut manager);
3816
3817        assert_eq!(solver_fast.check(&mut manager), SolverResult::Sat);
3818        assert_eq!(solver_balanced.check(&mut manager), SolverResult::Sat);
3819        assert_eq!(solver_thorough.check(&mut manager), SolverResult::Sat);
3820        assert_eq!(solver_minimal.check(&mut manager), SolverResult::Sat);
3821    }
3822
3823    #[test]
3824    fn test_config_default_is_balanced() {
3825        let default = SolverConfig::default();
3826        let balanced = SolverConfig::balanced();
3827
3828        // Default should be the same as balanced
3829        assert_eq!(
3830            default.enable_variable_elimination,
3831            balanced.enable_variable_elimination
3832        );
3833        assert_eq!(
3834            default.enable_clause_minimization,
3835            balanced.enable_clause_minimization
3836        );
3837        assert_eq!(
3838            default.enable_symmetry_breaking,
3839            balanced.enable_symmetry_breaking
3840        );
3841        assert_eq!(default.restart_strategy, balanced.restart_strategy);
3842    }
3843
3844    #[test]
3845    fn test_theory_combination_arith_solver() {
3846        use oxiz_theories::arithmetic::ArithSolver;
3847        use oxiz_theories::{EqualityNotification, TheoryCombination};
3848
3849        let mut arith = ArithSolver::lra();
3850        let mut manager = TermManager::new();
3851
3852        // Create two arithmetic variables
3853        let x = manager.mk_var("x", manager.sorts.int_sort);
3854        let y = manager.mk_var("y", manager.sorts.int_sort);
3855
3856        // Intern them in the arithmetic solver
3857        let _x_var = arith.intern(x);
3858        let _y_var = arith.intern(y);
3859
3860        // Test notify_equality with relevant terms
3861        let eq_notification = EqualityNotification {
3862            lhs: x,
3863            rhs: y,
3864            reason: None,
3865        };
3866
3867        let accepted = arith.notify_equality(eq_notification);
3868        assert!(
3869            accepted,
3870            "ArithSolver should accept equality notification for known terms"
3871        );
3872
3873        // Test is_relevant
3874        assert!(
3875            arith.is_relevant(x),
3876            "x should be relevant to arithmetic solver"
3877        );
3878        assert!(
3879            arith.is_relevant(y),
3880            "y should be relevant to arithmetic solver"
3881        );
3882
3883        // Test with unknown term
3884        let z = manager.mk_var("z", manager.sorts.int_sort);
3885        assert!(
3886            !arith.is_relevant(z),
3887            "z should not be relevant (not interned)"
3888        );
3889
3890        // Test notify_equality with unknown terms
3891        let eq_unknown = EqualityNotification {
3892            lhs: x,
3893            rhs: z,
3894            reason: None,
3895        };
3896        let accepted_unknown = arith.notify_equality(eq_unknown);
3897        assert!(
3898            !accepted_unknown,
3899            "ArithSolver should reject equality with unknown term"
3900        );
3901    }
3902
3903    #[test]
3904    fn test_theory_combination_get_shared_equalities() {
3905        use oxiz_theories::TheoryCombination;
3906        use oxiz_theories::arithmetic::ArithSolver;
3907
3908        let arith = ArithSolver::lra();
3909
3910        // Test get_shared_equalities
3911        let shared = arith.get_shared_equalities();
3912        assert!(
3913            shared.is_empty(),
3914            "ArithSolver should return empty shared equalities (placeholder)"
3915        );
3916    }
3917
3918    #[test]
3919    fn test_equality_notification_fields() {
3920        use oxiz_theories::EqualityNotification;
3921
3922        let mut manager = TermManager::new();
3923        let x = manager.mk_var("x", manager.sorts.int_sort);
3924        let y = manager.mk_var("y", manager.sorts.int_sort);
3925
3926        // Test with reason
3927        let eq1 = EqualityNotification {
3928            lhs: x,
3929            rhs: y,
3930            reason: Some(x),
3931        };
3932        assert_eq!(eq1.lhs, x);
3933        assert_eq!(eq1.rhs, y);
3934        assert_eq!(eq1.reason, Some(x));
3935
3936        // Test without reason
3937        let eq2 = EqualityNotification {
3938            lhs: x,
3939            rhs: y,
3940            reason: None,
3941        };
3942        assert_eq!(eq2.reason, None);
3943
3944        // Test equality and cloning
3945        let eq3 = eq1;
3946        assert_eq!(eq3.lhs, eq1.lhs);
3947        assert_eq!(eq3.rhs, eq1.rhs);
3948    }
3949
3950    #[test]
3951    fn test_assert_many() {
3952        let mut solver = Solver::new();
3953        let mut manager = TermManager::new();
3954
3955        let p = manager.mk_var("p", manager.sorts.bool_sort);
3956        let q = manager.mk_var("q", manager.sorts.bool_sort);
3957        let r = manager.mk_var("r", manager.sorts.bool_sort);
3958
3959        // Assert multiple terms at once
3960        solver.assert_many(&[p, q, r], &mut manager);
3961
3962        assert_eq!(solver.num_assertions(), 3);
3963        assert_eq!(solver.check(&mut manager), SolverResult::Sat);
3964    }
3965
3966    #[test]
3967    fn test_num_assertions_and_variables() {
3968        let mut solver = Solver::new();
3969        let mut manager = TermManager::new();
3970
3971        assert_eq!(solver.num_assertions(), 0);
3972        assert!(!solver.has_assertions());
3973
3974        let p = manager.mk_var("p", manager.sorts.bool_sort);
3975        let q = manager.mk_var("q", manager.sorts.bool_sort);
3976
3977        solver.assert(p, &mut manager);
3978        assert_eq!(solver.num_assertions(), 1);
3979        assert!(solver.has_assertions());
3980
3981        solver.assert(q, &mut manager);
3982        assert_eq!(solver.num_assertions(), 2);
3983
3984        // Variables are created during encoding
3985        assert!(solver.num_variables() > 0);
3986    }
3987
3988    #[test]
3989    fn test_context_level() {
3990        let mut solver = Solver::new();
3991
3992        assert_eq!(solver.context_level(), 0);
3993
3994        solver.push();
3995        assert_eq!(solver.context_level(), 1);
3996
3997        solver.push();
3998        assert_eq!(solver.context_level(), 2);
3999
4000        solver.pop();
4001        assert_eq!(solver.context_level(), 1);
4002
4003        solver.pop();
4004        assert_eq!(solver.context_level(), 0);
4005    }
4006
4007    // ===== Quantifier Tests =====
4008
4009    #[test]
4010    fn test_quantifier_basic_forall() {
4011        let mut solver = Solver::new();
4012        let mut manager = TermManager::new();
4013        let bool_sort = manager.sorts.bool_sort;
4014
4015        // Create: forall x. P(x)
4016        // This asserts P holds for all x
4017        let x = manager.mk_var("x", bool_sort);
4018        let p_x = manager.mk_apply("P", [x], bool_sort);
4019        let forall = manager.mk_forall([("x", bool_sort)], p_x);
4020
4021        solver.assert(forall, &mut manager);
4022
4023        // The solver should handle the quantifier (may return sat, unknown, or use MBQI)
4024        let result = solver.check(&mut manager);
4025        // Quantifiers without ground terms typically return sat (trivially satisfied)
4026        assert!(
4027            result == SolverResult::Sat || result == SolverResult::Unknown,
4028            "Basic forall should not be unsat"
4029        );
4030    }
4031
4032    #[test]
4033    fn test_quantifier_basic_exists() {
4034        let mut solver = Solver::new();
4035        let mut manager = TermManager::new();
4036        let bool_sort = manager.sorts.bool_sort;
4037
4038        // Create: exists x. P(x)
4039        let x = manager.mk_var("x", bool_sort);
4040        let p_x = manager.mk_apply("P", [x], bool_sort);
4041        let exists = manager.mk_exists([("x", bool_sort)], p_x);
4042
4043        solver.assert(exists, &mut manager);
4044
4045        let result = solver.check(&mut manager);
4046        assert!(
4047            result == SolverResult::Sat || result == SolverResult::Unknown,
4048            "Basic exists should not be unsat"
4049        );
4050    }
4051
4052    #[test]
4053    fn test_quantifier_with_ground_terms() {
4054        let mut solver = Solver::new();
4055        let mut manager = TermManager::new();
4056        let int_sort = manager.sorts.int_sort;
4057        let bool_sort = manager.sorts.bool_sort;
4058
4059        // Create ground terms for instantiation
4060        let zero = manager.mk_int(0);
4061        let one = manager.mk_int(1);
4062
4063        // P(0) = true and P(1) = true
4064        let p_0 = manager.mk_apply("P", [zero], bool_sort);
4065        let p_1 = manager.mk_apply("P", [one], bool_sort);
4066        solver.assert(p_0, &mut manager);
4067        solver.assert(p_1, &mut manager);
4068
4069        // forall x. P(x) - should be satisfiable with the given ground terms
4070        let x = manager.mk_var("x", int_sort);
4071        let p_x = manager.mk_apply("P", [x], bool_sort);
4072        let forall = manager.mk_forall([("x", int_sort)], p_x);
4073        solver.assert(forall, &mut manager);
4074
4075        let result = solver.check(&mut manager);
4076        // MBQI should find that P(0) and P(1) satisfy the quantifier
4077        assert!(
4078            result == SolverResult::Sat || result == SolverResult::Unknown,
4079            "Quantifier with matching ground terms should be satisfiable"
4080        );
4081    }
4082
4083    #[test]
4084    fn test_quantifier_instantiation() {
4085        let mut solver = Solver::new();
4086        let mut manager = TermManager::new();
4087        let int_sort = manager.sorts.int_sort;
4088        let bool_sort = manager.sorts.bool_sort;
4089
4090        // Create a ground term
4091        let c = manager.mk_apply("c", [], int_sort);
4092
4093        // Assert: forall x. f(x) > 0
4094        let x = manager.mk_var("x", int_sort);
4095        let f_x = manager.mk_apply("f", [x], int_sort);
4096        let zero = manager.mk_int(0);
4097        let f_x_gt_0 = manager.mk_gt(f_x, zero);
4098        let forall = manager.mk_forall([("x", int_sort)], f_x_gt_0);
4099        solver.assert(forall, &mut manager);
4100
4101        // Assert: f(c) exists (provides instantiation candidate)
4102        let f_c = manager.mk_apply("f", [c], int_sort);
4103        let f_c_exists = manager.mk_apply("exists_f_c", [f_c], bool_sort);
4104        solver.assert(f_c_exists, &mut manager);
4105
4106        let result = solver.check(&mut manager);
4107        // MBQI should instantiate the quantifier with c
4108        assert!(
4109            result == SolverResult::Sat || result == SolverResult::Unknown,
4110            "Quantifier instantiation test"
4111        );
4112    }
4113
4114    #[test]
4115    fn test_quantifier_mbqi_solver_integration() {
4116        use crate::mbqi::MBQISolver;
4117
4118        let mut mbqi = MBQISolver::new();
4119        let mut manager = TermManager::new();
4120        let int_sort = manager.sorts.int_sort;
4121
4122        // Create a universal quantifier
4123        let x = manager.mk_var("x", int_sort);
4124        let zero = manager.mk_int(0);
4125        let x_gt_0 = manager.mk_gt(x, zero);
4126        let forall = manager.mk_forall([("x", int_sort)], x_gt_0);
4127
4128        // Add the quantifier to MBQI
4129        mbqi.add_quantifier(forall, &manager);
4130
4131        // Add some candidate terms
4132        let one = manager.mk_int(1);
4133        let two = manager.mk_int(2);
4134        mbqi.add_candidate(one, int_sort);
4135        mbqi.add_candidate(two, int_sort);
4136
4137        // Check that MBQI tracks the quantifier
4138        assert!(mbqi.is_enabled(), "MBQI should be enabled by default");
4139    }
4140
4141    #[test]
4142    fn test_quantifier_pattern_matching() {
4143        let mut solver = Solver::new();
4144        let mut manager = TermManager::new();
4145        let int_sort = manager.sorts.int_sort;
4146
4147        // Create: forall x. (f(x) = g(x)) with pattern f(x)
4148        let x = manager.mk_var("x", int_sort);
4149        let f_x = manager.mk_apply("f", [x], int_sort);
4150        let g_x = manager.mk_apply("g", [x], int_sort);
4151        let body = manager.mk_eq(f_x, g_x);
4152
4153        // Create pattern
4154        let pattern: smallvec::SmallVec<[_; 2]> = smallvec::smallvec![f_x];
4155        let patterns: smallvec::SmallVec<[_; 2]> = smallvec::smallvec![pattern];
4156
4157        let forall = manager.mk_forall_with_patterns([("x", int_sort)], body, patterns);
4158        solver.assert(forall, &mut manager);
4159
4160        // Add ground term f(c) to trigger pattern matching
4161        let c = manager.mk_apply("c", [], int_sort);
4162        let f_c = manager.mk_apply("f", [c], int_sort);
4163        let f_c_eq_c = manager.mk_eq(f_c, c);
4164        solver.assert(f_c_eq_c, &mut manager);
4165
4166        let result = solver.check(&mut manager);
4167        assert!(
4168            result == SolverResult::Sat || result == SolverResult::Unknown,
4169            "Pattern matching should allow instantiation"
4170        );
4171    }
4172
4173    #[test]
4174    fn test_quantifier_multiple() {
4175        let mut solver = Solver::new();
4176        let mut manager = TermManager::new();
4177        let int_sort = manager.sorts.int_sort;
4178
4179        // Create: forall x. forall y. x + y = y + x (commutativity)
4180        let x = manager.mk_var("x", int_sort);
4181        let y = manager.mk_var("y", int_sort);
4182        let x_plus_y = manager.mk_add([x, y]);
4183        let y_plus_x = manager.mk_add([y, x]);
4184        let commutative = manager.mk_eq(x_plus_y, y_plus_x);
4185
4186        let inner_forall = manager.mk_forall([("y", int_sort)], commutative);
4187        let outer_forall = manager.mk_forall([("x", int_sort)], inner_forall);
4188
4189        solver.assert(outer_forall, &mut manager);
4190
4191        let result = solver.check(&mut manager);
4192        assert!(
4193            result == SolverResult::Sat || result == SolverResult::Unknown,
4194            "Nested forall should be handled"
4195        );
4196    }
4197
4198    #[test]
4199    fn test_quantifier_with_model() {
4200        let mut solver = Solver::new();
4201        let mut manager = TermManager::new();
4202        let bool_sort = manager.sorts.bool_sort;
4203
4204        // Simple satisfiable formula with quantifier
4205        let p = manager.mk_var("p", bool_sort);
4206        solver.assert(p, &mut manager);
4207
4208        // Add a trivial quantifier (x OR NOT x is always true)
4209        let x = manager.mk_var("x", bool_sort);
4210        let not_x = manager.mk_not(x);
4211        let x_or_not_x = manager.mk_or([x, not_x]);
4212        let tautology = manager.mk_forall([("x", bool_sort)], x_or_not_x);
4213        solver.assert(tautology, &mut manager);
4214
4215        let result = solver.check(&mut manager);
4216        assert_eq!(
4217            result,
4218            SolverResult::Sat,
4219            "Tautology in quantifier should be SAT"
4220        );
4221
4222        // Check that we can get a model
4223        if let Some(model) = solver.model() {
4224            assert!(model.size() > 0, "Model should have assignments");
4225        }
4226    }
4227
4228    #[test]
4229    fn test_quantifier_push_pop() {
4230        let mut solver = Solver::new();
4231        let mut manager = TermManager::new();
4232        let int_sort = manager.sorts.int_sort;
4233
4234        // Assert base formula
4235        let x = manager.mk_var("x", int_sort);
4236        let zero = manager.mk_int(0);
4237        let x_gt_0 = manager.mk_gt(x, zero);
4238        let forall = manager.mk_forall([("x", int_sort)], x_gt_0);
4239
4240        solver.push();
4241        solver.assert(forall, &mut manager);
4242
4243        let result1 = solver.check(&mut manager);
4244        // forall x. x > 0 is invalid (counterexample: x = 0 or x = -1)
4245        // So the solver should return Unsat or Unknown
4246        assert!(
4247            result1 == SolverResult::Unsat || result1 == SolverResult::Unknown,
4248            "forall x. x > 0 should be Unsat or Unknown, got {:?}",
4249            result1
4250        );
4251
4252        solver.pop();
4253
4254        // After pop, the quantifier assertion should be gone
4255        let result2 = solver.check(&mut manager);
4256        assert_eq!(
4257            result2,
4258            SolverResult::Sat,
4259            "After pop, should be trivially SAT"
4260        );
4261    }
4262
4263    /// Test that integer contradictions are correctly detected as UNSAT.
4264    ///
4265    /// NOTE: This test currently fails because arithmetic constraints (Ge, Lt, etc.)
4266    /// are not fully integrated with the simplex solver. The `process_constraint` function
4267    /// in solver.rs has a TODO to implement this. Once arithmetic constraint encoding
4268    /// is complete, this test should pass and the ignore attribute should be removed.
4269    #[test]
4270    #[ignore = "Requires complete arithmetic constraint encoding to simplex (see process_constraint in solver.rs)"]
4271    fn test_integer_contradiction_unsat() {
4272        use num_bigint::BigInt;
4273
4274        let mut solver = Solver::new();
4275        let mut manager = TermManager::new();
4276
4277        // Create integer variable x
4278        let x = manager.mk_var("x", manager.sorts.int_sort);
4279        let zero = manager.mk_int(BigInt::from(0));
4280
4281        // Assert x >= 0
4282        let x_ge_0 = manager.mk_ge(x, zero);
4283        solver.assert(x_ge_0, &mut manager);
4284
4285        // Assert x < 0 (contradicts x >= 0)
4286        let x_lt_0 = manager.mk_lt(x, zero);
4287        solver.assert(x_lt_0, &mut manager);
4288
4289        // Should be UNSAT because x cannot be both >= 0 and < 0
4290        let result = solver.check(&mut manager);
4291        assert_eq!(
4292            result,
4293            SolverResult::Unsat,
4294            "x >= 0 AND x < 0 should be UNSAT"
4295        );
4296    }
4297}