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;
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}
852
853/// Theory decision hint
854#[derive(Debug, Clone, Copy)]
855#[allow(dead_code)]
856pub struct TheoryDecision {
857    /// The variable to branch on
858    pub var: Var,
859    /// Suggested value (true = positive, false = negative)
860    pub value: bool,
861    /// Priority (higher = more important)
862    pub priority: i32,
863}
864
865/// Theory manager that bridges the SAT solver with theory solvers
866struct TheoryManager<'a> {
867    /// Reference to the EUF solver
868    euf: &'a mut EufSolver,
869    /// Reference to the arithmetic solver
870    arith: &'a mut ArithSolver,
871    /// Reference to the bitvector solver
872    bv: &'a mut BvSolver,
873    /// Mapping from SAT variables to constraints
874    var_to_constraint: &'a FxHashMap<Var, Constraint>,
875    /// Mapping from SAT variables to parsed arithmetic constraints
876    var_to_parsed_arith: &'a FxHashMap<Var, ParsedArithConstraint>,
877    /// Mapping from terms to SAT variables (for conflict clause generation)
878    term_to_var: &'a FxHashMap<TermId, Var>,
879    /// Current decision level stack for backtracking
880    level_stack: Vec<usize>,
881    /// Number of processed assignments
882    processed_count: usize,
883    /// Theory checking mode
884    theory_mode: TheoryMode,
885    /// Pending assignments for lazy theory checking
886    pending_assignments: Vec<(Lit, bool)>,
887    /// Theory decision hints for branching
888    #[allow(dead_code)]
889    decision_hints: Vec<TheoryDecision>,
890    /// Pending equality notifications for Nelson-Oppen
891    pending_equalities: Vec<EqualityNotification>,
892    /// Processed equalities (to avoid duplicates)
893    processed_equalities: FxHashMap<(TermId, TermId), bool>,
894    /// Reference to solver statistics (for tracking)
895    statistics: &'a mut Statistics,
896    /// Maximum conflicts allowed (0 = unlimited)
897    max_conflicts: u64,
898    /// Maximum decisions allowed (0 = unlimited)
899    #[allow(dead_code)]
900    max_decisions: u64,
901}
902
903impl<'a> TheoryManager<'a> {
904    #[allow(clippy::too_many_arguments)]
905    fn new(
906        euf: &'a mut EufSolver,
907        arith: &'a mut ArithSolver,
908        bv: &'a mut BvSolver,
909        var_to_constraint: &'a FxHashMap<Var, Constraint>,
910        var_to_parsed_arith: &'a FxHashMap<Var, ParsedArithConstraint>,
911        term_to_var: &'a FxHashMap<TermId, Var>,
912        theory_mode: TheoryMode,
913        statistics: &'a mut Statistics,
914        max_conflicts: u64,
915        max_decisions: u64,
916    ) -> Self {
917        Self {
918            euf,
919            arith,
920            bv,
921            var_to_constraint,
922            var_to_parsed_arith,
923            term_to_var,
924            level_stack: vec![0],
925            processed_count: 0,
926            theory_mode,
927            pending_assignments: Vec::new(),
928            decision_hints: Vec::new(),
929            pending_equalities: Vec::new(),
930            processed_equalities: FxHashMap::default(),
931            statistics,
932            max_conflicts,
933            max_decisions,
934        }
935    }
936
937    /// Process Nelson-Oppen equality sharing
938    /// Propagates equalities between theories until a fixed point is reached
939    #[allow(dead_code)]
940    fn propagate_equalities(&mut self) -> TheoryCheckResult {
941        // Process all pending equalities
942        while let Some(eq) = self.pending_equalities.pop() {
943            // Avoid processing the same equality twice
944            let key = if eq.lhs < eq.rhs {
945                (eq.lhs, eq.rhs)
946            } else {
947                (eq.rhs, eq.lhs)
948            };
949
950            if self.processed_equalities.contains_key(&key) {
951                continue;
952            }
953            self.processed_equalities.insert(key, true);
954
955            // Notify EUF theory
956            let lhs_node = self.euf.intern(eq.lhs);
957            let rhs_node = self.euf.intern(eq.rhs);
958            if let Err(_e) = self
959                .euf
960                .merge(lhs_node, rhs_node, eq.reason.unwrap_or(eq.lhs))
961            {
962                // Merge failed - should not happen
963                continue;
964            }
965
966            // Check for conflicts after merging
967            if let Some(conflict_terms) = self.euf.check_conflicts() {
968                let conflict_lits = self.terms_to_conflict_clause(&conflict_terms);
969                return TheoryCheckResult::Conflict(conflict_lits);
970            }
971
972            // Notify arithmetic theory
973            self.arith.notify_equality(eq);
974        }
975
976        TheoryCheckResult::Sat
977    }
978
979    /// Model-based theory combination
980    /// Checks if theories agree on shared terms in their models
981    /// If they disagree, generates equality constraints to force agreement
982    #[allow(dead_code)]
983    fn model_based_combination(&mut self) -> TheoryCheckResult {
984        // Collect shared terms (terms that appear in multiple theories)
985        let mut shared_terms: Vec<TermId> = Vec::new();
986
987        // For now, we'll consider all terms in the mapping as potentially shared
988        // A full implementation would track which terms belong to which theories
989        for &term in self.term_to_var.keys() {
990            shared_terms.push(term);
991        }
992
993        if shared_terms.len() < 2 {
994            return TheoryCheckResult::Sat;
995        }
996
997        // Check if EUF and arithmetic models agree on shared terms
998        // For each pair of terms that EUF considers equal, check if arithmetic agrees
999        for i in 0..shared_terms.len() {
1000            for j in (i + 1)..shared_terms.len() {
1001                let t1 = shared_terms[i];
1002                let t2 = shared_terms[j];
1003
1004                // Check if EUF considers them equal
1005                let t1_node = self.euf.intern(t1);
1006                let t2_node = self.euf.intern(t2);
1007
1008                if self.euf.are_equal(t1_node, t2_node) {
1009                    // EUF says they're equal
1010                    // Check if arithmetic solver also considers them equal
1011                    let t1_value = self.arith.value(t1);
1012                    let t2_value = self.arith.value(t2);
1013
1014                    if let (Some(v1), Some(v2)) = (t1_value, t2_value)
1015                        && v1 != v2
1016                    {
1017                        // Disagreement! Generate conflict clause
1018                        // The conflict is that EUF says t1=t2 but arithmetic says t1≠t2
1019                        // We need to find the literals that led to this equality in EUF
1020                        let conflict_lits = self.terms_to_conflict_clause(&[t1, t2]);
1021                        return TheoryCheckResult::Conflict(conflict_lits);
1022                    }
1023                }
1024            }
1025        }
1026
1027        TheoryCheckResult::Sat
1028    }
1029
1030    /// Add an equality to be shared between theories
1031    #[allow(dead_code)]
1032    fn add_shared_equality(&mut self, lhs: TermId, rhs: TermId, reason: Option<TermId>) {
1033        self.pending_equalities
1034            .push(EqualityNotification { lhs, rhs, reason });
1035    }
1036
1037    /// Get theory decision hints for branching
1038    /// Returns suggested variables to branch on, ordered by priority
1039    #[allow(dead_code)]
1040    fn get_decision_hints(&mut self) -> &[TheoryDecision] {
1041        // Clear old hints
1042        self.decision_hints.clear();
1043
1044        // Collect hints from theory solvers
1045        // For now, we can suggest branching on variables that appear in
1046        // unsatisfied constraints or pending equalities
1047
1048        // EUF hints: suggest branching on disequalities that might conflict
1049        // Arithmetic hints: suggest branching on bounds that are close to being violated
1050
1051        // This is a placeholder - full implementation would query theory solvers
1052        // for their preferred branching decisions
1053
1054        &self.decision_hints
1055    }
1056
1057    /// Convert a list of term IDs to a conflict clause
1058    /// Each term ID should correspond to a constraint that was asserted
1059    fn terms_to_conflict_clause(&self, terms: &[TermId]) -> SmallVec<[Lit; 8]> {
1060        let mut conflict = SmallVec::new();
1061        for &term in terms {
1062            if let Some(&var) = self.term_to_var.get(&term) {
1063                // Negate the literal since these are the assertions that led to conflict
1064                conflict.push(Lit::neg(var));
1065            }
1066        }
1067        conflict
1068    }
1069
1070    /// Process a theory constraint
1071    fn process_constraint(
1072        &mut self,
1073        var: Var,
1074        constraint: Constraint,
1075        is_positive: bool,
1076    ) -> TheoryCheckResult {
1077        match constraint {
1078            Constraint::Eq(lhs, rhs) => {
1079                if is_positive {
1080                    // Positive assignment: a = b, tell EUF to merge
1081                    let lhs_node = self.euf.intern(lhs);
1082                    let rhs_node = self.euf.intern(rhs);
1083                    if let Err(_e) = self.euf.merge(lhs_node, rhs_node, lhs) {
1084                        // Merge failed - should not happen in normal operation
1085                        return TheoryCheckResult::Sat;
1086                    }
1087
1088                    // Check for immediate conflicts
1089                    if let Some(conflict_terms) = self.euf.check_conflicts() {
1090                        // Convert term IDs to literals for conflict clause
1091                        let conflict_lits = self.terms_to_conflict_clause(&conflict_terms);
1092                        return TheoryCheckResult::Conflict(conflict_lits);
1093                    }
1094                } else {
1095                    // Negative assignment: a != b, tell EUF about disequality
1096                    let lhs_node = self.euf.intern(lhs);
1097                    let rhs_node = self.euf.intern(rhs);
1098                    self.euf.assert_diseq(lhs_node, rhs_node, lhs);
1099
1100                    // Check for immediate conflicts (if a = b was already derived)
1101                    if let Some(conflict_terms) = self.euf.check_conflicts() {
1102                        let conflict_lits = self.terms_to_conflict_clause(&conflict_terms);
1103                        return TheoryCheckResult::Conflict(conflict_lits);
1104                    }
1105                }
1106            }
1107            Constraint::Diseq(lhs, rhs) => {
1108                if is_positive {
1109                    // Positive assignment: a != b
1110                    let lhs_node = self.euf.intern(lhs);
1111                    let rhs_node = self.euf.intern(rhs);
1112                    self.euf.assert_diseq(lhs_node, rhs_node, lhs);
1113
1114                    if let Some(conflict_terms) = self.euf.check_conflicts() {
1115                        let conflict_lits = self.terms_to_conflict_clause(&conflict_terms);
1116                        return TheoryCheckResult::Conflict(conflict_lits);
1117                    }
1118                } else {
1119                    // Negative assignment: ~(a != b) means a = b
1120                    let lhs_node = self.euf.intern(lhs);
1121                    let rhs_node = self.euf.intern(rhs);
1122                    if let Err(_e) = self.euf.merge(lhs_node, rhs_node, lhs) {
1123                        return TheoryCheckResult::Sat;
1124                    }
1125
1126                    if let Some(conflict_terms) = self.euf.check_conflicts() {
1127                        let conflict_lits = self.terms_to_conflict_clause(&conflict_terms);
1128                        return TheoryCheckResult::Conflict(conflict_lits);
1129                    }
1130                }
1131            }
1132            // Arithmetic constraints - use parsed linear expressions
1133            Constraint::Lt(_lhs, _rhs)
1134            | Constraint::Le(_lhs, _rhs)
1135            | Constraint::Gt(_lhs, _rhs)
1136            | Constraint::Ge(_lhs, _rhs) => {
1137                // Look up the pre-parsed linear constraint
1138                if let Some(parsed) = self.var_to_parsed_arith.get(&var) {
1139                    // Add constraint to ArithSolver
1140                    let terms: Vec<(TermId, Rational64)> = parsed.terms.iter().copied().collect();
1141                    let reason = parsed.reason_term;
1142                    let constant = parsed.constant;
1143
1144                    if is_positive {
1145                        // Positive assignment: constraint holds
1146                        match parsed.constraint_type {
1147                            ArithConstraintType::Lt => {
1148                                // lhs - rhs < 0, i.e., sum of terms < constant
1149                                self.arith.assert_lt(&terms, constant, reason);
1150                            }
1151                            ArithConstraintType::Le => {
1152                                // lhs - rhs <= 0
1153                                self.arith.assert_le(&terms, constant, reason);
1154                            }
1155                            ArithConstraintType::Gt => {
1156                                // lhs - rhs > 0, i.e., sum of terms > constant
1157                                self.arith.assert_gt(&terms, constant, reason);
1158                            }
1159                            ArithConstraintType::Ge => {
1160                                // lhs - rhs >= 0
1161                                self.arith.assert_ge(&terms, constant, reason);
1162                            }
1163                        }
1164                    } else {
1165                        // Negative assignment: negation of constraint holds
1166                        // ~(a < b) => a >= b
1167                        // ~(a <= b) => a > b
1168                        // ~(a > b) => a <= b
1169                        // ~(a >= b) => a < b
1170                        match parsed.constraint_type {
1171                            ArithConstraintType::Lt => {
1172                                // ~(lhs < rhs) => lhs >= rhs
1173                                self.arith.assert_ge(&terms, constant, reason);
1174                            }
1175                            ArithConstraintType::Le => {
1176                                // ~(lhs <= rhs) => lhs > rhs
1177                                self.arith.assert_gt(&terms, constant, reason);
1178                            }
1179                            ArithConstraintType::Gt => {
1180                                // ~(lhs > rhs) => lhs <= rhs
1181                                self.arith.assert_le(&terms, constant, reason);
1182                            }
1183                            ArithConstraintType::Ge => {
1184                                // ~(lhs >= rhs) => lhs < rhs
1185                                self.arith.assert_lt(&terms, constant, reason);
1186                            }
1187                        }
1188                    }
1189
1190                    // Check ArithSolver for conflicts
1191                    use oxiz_theories::Theory;
1192                    use oxiz_theories::TheoryCheckResult as TheoryCheckResultEnum;
1193                    if let Ok(TheoryCheckResultEnum::Unsat(conflict_terms)) = self.arith.check() {
1194                        let conflict_lits = self.terms_to_conflict_clause(&conflict_terms);
1195                        return TheoryCheckResult::Conflict(conflict_lits);
1196                    }
1197                }
1198            }
1199        }
1200        TheoryCheckResult::Sat
1201    }
1202}
1203
1204impl TheoryCallback for TheoryManager<'_> {
1205    fn on_assignment(&mut self, lit: Lit) -> TheoryCheckResult {
1206        let var = lit.var();
1207        let is_positive = !lit.is_neg();
1208
1209        // Track propagation
1210        self.statistics.propagations += 1;
1211
1212        // In lazy mode, just collect assignments for batch processing
1213        if self.theory_mode == TheoryMode::Lazy {
1214            // Check if this variable has a theory constraint
1215            if self.var_to_constraint.contains_key(&var) {
1216                self.pending_assignments.push((lit, is_positive));
1217            }
1218            return TheoryCheckResult::Sat;
1219        }
1220
1221        // Eager mode: process immediately
1222        // Check if this variable has a theory constraint
1223        let Some(constraint) = self.var_to_constraint.get(&var).cloned() else {
1224            return TheoryCheckResult::Sat;
1225        };
1226
1227        self.processed_count += 1;
1228        self.statistics.theory_propagations += 1;
1229
1230        let result = self.process_constraint(var, constraint, is_positive);
1231
1232        // Track theory conflicts
1233        if matches!(result, TheoryCheckResult::Conflict(_)) {
1234            self.statistics.theory_conflicts += 1;
1235            self.statistics.conflicts += 1;
1236
1237            // Check conflict limit
1238            if self.max_conflicts > 0 && self.statistics.conflicts >= self.max_conflicts {
1239                return TheoryCheckResult::Sat; // Return Sat to signal resource exhaustion
1240            }
1241        }
1242
1243        result
1244    }
1245
1246    fn final_check(&mut self) -> TheoryCheckResult {
1247        // In lazy mode, process all pending assignments now
1248        if self.theory_mode == TheoryMode::Lazy {
1249            for &(lit, is_positive) in &self.pending_assignments.clone() {
1250                let var = lit.var();
1251                let Some(constraint) = self.var_to_constraint.get(&var).cloned() else {
1252                    continue;
1253                };
1254
1255                self.statistics.theory_propagations += 1;
1256
1257                // Process the constraint (same logic as eager mode)
1258                let result = self.process_constraint(var, constraint, is_positive);
1259                if let TheoryCheckResult::Conflict(conflict) = result {
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; // Signal resource exhaustion
1266                    }
1267
1268                    return TheoryCheckResult::Conflict(conflict);
1269                }
1270            }
1271            // Clear pending assignments after processing
1272            self.pending_assignments.clear();
1273        }
1274
1275        // Check EUF for conflicts
1276        if let Some(conflict_terms) = self.euf.check_conflicts() {
1277            // Convert TermIds to Lits for the conflict clause
1278            let conflict_lits = self.terms_to_conflict_clause(&conflict_terms);
1279            self.statistics.theory_conflicts += 1;
1280            self.statistics.conflicts += 1;
1281
1282            // Check conflict limit
1283            if self.max_conflicts > 0 && self.statistics.conflicts >= self.max_conflicts {
1284                return TheoryCheckResult::Sat; // Signal resource exhaustion
1285            }
1286
1287            return TheoryCheckResult::Conflict(conflict_lits);
1288        }
1289
1290        // Check arithmetic
1291        match self.arith.check() {
1292            Ok(result) => {
1293                match result {
1294                    oxiz_theories::TheoryCheckResult::Sat => {
1295                        // Arithmetic is consistent, now check model-based theory combination
1296                        // This ensures that different theories agree on shared terms
1297                        self.model_based_combination()
1298                    }
1299                    oxiz_theories::TheoryCheckResult::Unsat(conflict_terms) => {
1300                        // Arithmetic conflict detected - convert to SAT conflict clause
1301                        let conflict_lits = self.terms_to_conflict_clause(&conflict_terms);
1302                        self.statistics.theory_conflicts += 1;
1303                        self.statistics.conflicts += 1;
1304
1305                        // Check conflict limit
1306                        if self.max_conflicts > 0 && self.statistics.conflicts >= self.max_conflicts
1307                        {
1308                            return TheoryCheckResult::Sat; // Signal resource exhaustion
1309                        }
1310
1311                        TheoryCheckResult::Conflict(conflict_lits)
1312                    }
1313                    oxiz_theories::TheoryCheckResult::Propagate(_) => {
1314                        // Propagations should be handled in on_assignment
1315                        self.model_based_combination()
1316                    }
1317                    oxiz_theories::TheoryCheckResult::Unknown => {
1318                        // Theory is incomplete, be conservative
1319                        TheoryCheckResult::Sat
1320                    }
1321                }
1322            }
1323            Err(_error) => {
1324                // Internal error in the arithmetic solver
1325                // For now, be conservative and return Sat
1326                TheoryCheckResult::Sat
1327            }
1328        }
1329    }
1330
1331    fn on_new_level(&mut self, level: u32) {
1332        // Push theory state when a new decision level is created
1333        // Ensure we have enough levels in the stack
1334        while self.level_stack.len() < (level as usize + 1) {
1335            self.level_stack.push(self.processed_count);
1336            self.euf.push();
1337            self.arith.push();
1338            self.bv.push();
1339        }
1340    }
1341
1342    fn on_backtrack(&mut self, level: u32) {
1343        // Pop EUF, Arith, and BV states if needed
1344        while self.level_stack.len() > (level as usize + 1) {
1345            self.level_stack.pop();
1346            self.euf.pop();
1347            self.arith.pop();
1348            self.bv.pop();
1349        }
1350        self.processed_count = *self.level_stack.last().unwrap_or(&0);
1351
1352        // Clear pending assignments on backtrack (in lazy mode)
1353        if self.theory_mode == TheoryMode::Lazy {
1354            self.pending_assignments.clear();
1355        }
1356    }
1357}
1358
1359/// Trail operation for efficient undo
1360#[derive(Debug, Clone)]
1361enum TrailOp {
1362    /// An assertion was added
1363    AssertionAdded { index: usize },
1364    /// A variable was created
1365    VarCreated {
1366        #[allow(dead_code)]
1367        var: Var,
1368        term: TermId,
1369    },
1370    /// A constraint was added
1371    ConstraintAdded { var: Var },
1372    /// False assertion flag was set
1373    FalseAssertionSet,
1374    /// A named assertion was added
1375    NamedAssertionAdded { index: usize },
1376}
1377
1378/// State for push/pop with trail-based undo
1379#[derive(Debug, Clone)]
1380struct ContextState {
1381    num_assertions: usize,
1382    num_vars: usize,
1383    has_false_assertion: bool,
1384    /// Trail position at the time of push
1385    trail_position: usize,
1386}
1387
1388impl Default for Solver {
1389    fn default() -> Self {
1390        Self::new()
1391    }
1392}
1393
1394impl Solver {
1395    /// Create a new solver
1396    #[must_use]
1397    pub fn new() -> Self {
1398        Self::with_config(SolverConfig::default())
1399    }
1400
1401    /// Create a new solver with configuration
1402    #[must_use]
1403    pub fn with_config(config: SolverConfig) -> Self {
1404        let proof_enabled = config.proof;
1405
1406        // Build SAT solver configuration from our config
1407        let sat_config = SatConfig {
1408            restart_strategy: config.restart_strategy,
1409            enable_inprocessing: config.enable_inprocessing,
1410            inprocessing_interval: config.inprocessing_interval,
1411            ..SatConfig::default()
1412        };
1413
1414        // Note: The following features are controlled by the SAT solver's preprocessor
1415        // and clause management systems. We pass the configuration but the actual
1416        // implementation is in oxiz-sat:
1417        // - Clause minimization (via RecursiveMinimizer)
1418        // - Clause subsumption (via SubsumptionChecker)
1419        // - Variable elimination (via Preprocessor::variable_elimination)
1420        // - Blocked clause elimination (via Preprocessor::blocked_clause_elimination)
1421        // - Symmetry breaking (via SymmetryBreaker)
1422
1423        Self {
1424            config,
1425            sat: SatSolver::with_config(sat_config),
1426            euf: EufSolver::new(),
1427            arith: ArithSolver::lra(),
1428            bv: BvSolver::new(),
1429            mbqi: MBQISolver::new(),
1430            has_quantifiers: false,
1431            term_to_var: FxHashMap::default(),
1432            var_to_term: Vec::new(),
1433            var_to_constraint: FxHashMap::default(),
1434            var_to_parsed_arith: FxHashMap::default(),
1435            logic: None,
1436            assertions: Vec::new(),
1437            named_assertions: Vec::new(),
1438            assumption_vars: FxHashMap::default(),
1439            model: None,
1440            unsat_core: None,
1441            context_stack: Vec::new(),
1442            trail: Vec::new(),
1443            theory_processed_up_to: 0,
1444            produce_unsat_cores: false,
1445            has_false_assertion: false,
1446            polarities: FxHashMap::default(),
1447            polarity_aware: true, // Enable polarity-aware encoding by default
1448            theory_aware_branching: true, // Enable theory-aware branching by default
1449            proof: if proof_enabled {
1450                Some(Proof::new())
1451            } else {
1452                None
1453            },
1454            simplifier: Simplifier::new(),
1455            statistics: Statistics::new(),
1456        }
1457    }
1458
1459    /// Get the proof (if proof generation is enabled and the result is unsat)
1460    #[must_use]
1461    pub fn get_proof(&self) -> Option<&Proof> {
1462        self.proof.as_ref()
1463    }
1464
1465    /// Get the solver statistics
1466    #[must_use]
1467    pub fn get_statistics(&self) -> &Statistics {
1468        &self.statistics
1469    }
1470
1471    /// Reset the solver statistics
1472    pub fn reset_statistics(&mut self) {
1473        self.statistics.reset();
1474    }
1475
1476    /// Enable or disable theory-aware branching
1477    pub fn set_theory_aware_branching(&mut self, enabled: bool) {
1478        self.theory_aware_branching = enabled;
1479    }
1480
1481    /// Check if theory-aware branching is enabled
1482    #[must_use]
1483    pub fn theory_aware_branching(&self) -> bool {
1484        self.theory_aware_branching
1485    }
1486
1487    /// Enable or disable unsat core production
1488    pub fn set_produce_unsat_cores(&mut self, produce: bool) {
1489        self.produce_unsat_cores = produce;
1490    }
1491
1492    /// Set the logic
1493    pub fn set_logic(&mut self, logic: &str) {
1494        self.logic = Some(logic.to_string());
1495    }
1496
1497    /// Collect polarity information for all subterms
1498    /// This is used for polarity-aware encoding optimization
1499    fn collect_polarities(&mut self, term: TermId, polarity: Polarity, manager: &TermManager) {
1500        // Update the polarity for this term
1501        let current = self.polarities.get(&term).copied();
1502        let new_polarity = match (current, polarity) {
1503            (Some(Polarity::Both), _) | (_, Polarity::Both) => Polarity::Both,
1504            (Some(Polarity::Positive), Polarity::Negative)
1505            | (Some(Polarity::Negative), Polarity::Positive) => Polarity::Both,
1506            (Some(p), _) => p,
1507            (None, p) => p,
1508        };
1509        self.polarities.insert(term, new_polarity);
1510
1511        // If we've reached Both polarity, no need to recurse further
1512        if current == Some(Polarity::Both) {
1513            return;
1514        }
1515
1516        // Recursively collect polarities for subterms
1517        let Some(t) = manager.get(term) else {
1518            return;
1519        };
1520
1521        match &t.kind {
1522            TermKind::Not(arg) => {
1523                let neg_polarity = match polarity {
1524                    Polarity::Positive => Polarity::Negative,
1525                    Polarity::Negative => Polarity::Positive,
1526                    Polarity::Both => Polarity::Both,
1527                };
1528                self.collect_polarities(*arg, neg_polarity, manager);
1529            }
1530            TermKind::And(args) | TermKind::Or(args) => {
1531                for &arg in args {
1532                    self.collect_polarities(arg, polarity, manager);
1533                }
1534            }
1535            TermKind::Implies(lhs, rhs) => {
1536                let neg_polarity = match polarity {
1537                    Polarity::Positive => Polarity::Negative,
1538                    Polarity::Negative => Polarity::Positive,
1539                    Polarity::Both => Polarity::Both,
1540                };
1541                self.collect_polarities(*lhs, neg_polarity, manager);
1542                self.collect_polarities(*rhs, polarity, manager);
1543            }
1544            TermKind::Ite(cond, then_br, else_br) => {
1545                self.collect_polarities(*cond, Polarity::Both, manager);
1546                self.collect_polarities(*then_br, polarity, manager);
1547                self.collect_polarities(*else_br, polarity, manager);
1548            }
1549            TermKind::Xor(lhs, rhs) | TermKind::Eq(lhs, rhs) => {
1550                // For XOR and Eq, both sides appear in both polarities
1551                self.collect_polarities(*lhs, Polarity::Both, manager);
1552                self.collect_polarities(*rhs, Polarity::Both, manager);
1553            }
1554            _ => {
1555                // For other terms (constants, variables, theory atoms), stop recursion
1556            }
1557        }
1558    }
1559
1560    /// Get a SAT variable for a term
1561    fn get_or_create_var(&mut self, term: TermId) -> Var {
1562        if let Some(&var) = self.term_to_var.get(&term) {
1563            return var;
1564        }
1565
1566        let var = self.sat.new_var();
1567        self.term_to_var.insert(term, var);
1568        self.trail.push(TrailOp::VarCreated { var, term });
1569
1570        while self.var_to_term.len() <= var.index() {
1571            self.var_to_term.push(TermId::new(0));
1572        }
1573        self.var_to_term[var.index()] = term;
1574        var
1575    }
1576
1577    /// Parse an arithmetic comparison and extract linear expression
1578    /// Returns: (terms with coefficients, constant, constraint_type)
1579    fn parse_arith_comparison(
1580        &self,
1581        lhs: TermId,
1582        rhs: TermId,
1583        constraint_type: ArithConstraintType,
1584        reason: TermId,
1585        manager: &TermManager,
1586    ) -> Option<ParsedArithConstraint> {
1587        let mut terms: SmallVec<[(TermId, Rational64); 4]> = SmallVec::new();
1588        let mut constant = Rational64::zero();
1589
1590        // Parse LHS (add positive coefficients)
1591        self.extract_linear_terms(lhs, Rational64::one(), &mut terms, &mut constant, manager)?;
1592
1593        // Parse RHS (subtract, so coefficients are negated)
1594        // For lhs OP rhs, we want lhs - rhs OP 0
1595        self.extract_linear_terms(rhs, -Rational64::one(), &mut terms, &mut constant, manager)?;
1596
1597        // Combine like terms
1598        let mut combined: FxHashMap<TermId, Rational64> = FxHashMap::default();
1599        for (term, coef) in terms {
1600            *combined.entry(term).or_insert(Rational64::zero()) += coef;
1601        }
1602
1603        // Remove zero coefficients
1604        let final_terms: SmallVec<[(TermId, Rational64); 4]> =
1605            combined.into_iter().filter(|(_, c)| !c.is_zero()).collect();
1606
1607        Some(ParsedArithConstraint {
1608            terms: final_terms,
1609            constant: -constant, // Move constant to RHS
1610            constraint_type,
1611            reason_term: reason,
1612        })
1613    }
1614
1615    /// Extract linear terms recursively from an arithmetic expression
1616    /// Returns None if the term is not linear
1617    fn extract_linear_terms(
1618        &self,
1619        term_id: TermId,
1620        scale: Rational64,
1621        terms: &mut SmallVec<[(TermId, Rational64); 4]>,
1622        constant: &mut Rational64,
1623        manager: &TermManager,
1624    ) -> Option<()> {
1625        let term = manager.get(term_id)?;
1626
1627        match &term.kind {
1628            // Integer constant
1629            TermKind::IntConst(n) => {
1630                if let Some(val) = n.to_i64() {
1631                    *constant += scale * Rational64::from_integer(val);
1632                    Some(())
1633                } else {
1634                    // BigInt too large, skip for now
1635                    None
1636                }
1637            }
1638
1639            // Rational constant
1640            TermKind::RealConst(r) => {
1641                *constant += scale * *r;
1642                Some(())
1643            }
1644
1645            // Bitvector constant - treat as integer
1646            TermKind::BitVecConst { value, .. } => {
1647                if let Some(val) = value.to_i64() {
1648                    *constant += scale * Rational64::from_integer(val);
1649                    Some(())
1650                } else {
1651                    // BigInt too large, skip for now
1652                    None
1653                }
1654            }
1655
1656            // Variable (or bitvector variable - treat as integer variable)
1657            TermKind::Var(_) => {
1658                terms.push((term_id, scale));
1659                Some(())
1660            }
1661
1662            // Addition
1663            TermKind::Add(args) => {
1664                for &arg in args {
1665                    self.extract_linear_terms(arg, scale, terms, constant, manager)?;
1666                }
1667                Some(())
1668            }
1669
1670            // Subtraction
1671            TermKind::Sub(lhs, rhs) => {
1672                self.extract_linear_terms(*lhs, scale, terms, constant, manager)?;
1673                self.extract_linear_terms(*rhs, -scale, terms, constant, manager)?;
1674                Some(())
1675            }
1676
1677            // Negation
1678            TermKind::Neg(arg) => self.extract_linear_terms(*arg, -scale, terms, constant, manager),
1679
1680            // Multiplication by constant
1681            TermKind::Mul(args) => {
1682                // Check if all but one are constants
1683                let mut const_product = Rational64::one();
1684                let mut var_term: Option<TermId> = None;
1685
1686                for &arg in args {
1687                    let arg_term = manager.get(arg)?;
1688                    match &arg_term.kind {
1689                        TermKind::IntConst(n) => {
1690                            if let Some(val) = n.to_i64() {
1691                                const_product *= Rational64::from_integer(val);
1692                            } else {
1693                                return None; // BigInt too large
1694                            }
1695                        }
1696                        TermKind::RealConst(r) => {
1697                            const_product *= *r;
1698                        }
1699                        _ => {
1700                            if var_term.is_some() {
1701                                // Multiple non-constant terms - not linear
1702                                return None;
1703                            }
1704                            var_term = Some(arg);
1705                        }
1706                    }
1707                }
1708
1709                let new_scale = scale * const_product;
1710                match var_term {
1711                    Some(v) => self.extract_linear_terms(v, new_scale, terms, constant, manager),
1712                    None => {
1713                        // All constants
1714                        *constant += new_scale;
1715                        Some(())
1716                    }
1717                }
1718            }
1719
1720            // Not linear
1721            _ => None,
1722        }
1723    }
1724
1725    /// Assert a term
1726    pub fn assert(&mut self, term: TermId, manager: &mut TermManager) {
1727        let index = self.assertions.len();
1728        self.assertions.push(term);
1729        self.trail.push(TrailOp::AssertionAdded { index });
1730
1731        // Check if this is a boolean constant first
1732        if let Some(t) = manager.get(term) {
1733            match t.kind {
1734                TermKind::False => {
1735                    // Mark that we have a false assertion
1736                    if !self.has_false_assertion {
1737                        self.has_false_assertion = true;
1738                        self.trail.push(TrailOp::FalseAssertionSet);
1739                    }
1740                    if self.produce_unsat_cores {
1741                        let na_index = self.named_assertions.len();
1742                        self.named_assertions.push(NamedAssertion {
1743                            term,
1744                            name: None,
1745                            index: index as u32,
1746                        });
1747                        self.trail
1748                            .push(TrailOp::NamedAssertionAdded { index: na_index });
1749                    }
1750                    return;
1751                }
1752                TermKind::True => {
1753                    // True is always satisfied, no need to encode
1754                    if self.produce_unsat_cores {
1755                        let na_index = self.named_assertions.len();
1756                        self.named_assertions.push(NamedAssertion {
1757                            term,
1758                            name: None,
1759                            index: index as u32,
1760                        });
1761                        self.trail
1762                            .push(TrailOp::NamedAssertionAdded { index: na_index });
1763                    }
1764                    return;
1765                }
1766                _ => {}
1767            }
1768        }
1769
1770        // Apply simplification if enabled
1771        let term_to_encode = if self.config.simplify {
1772            self.simplifier.simplify(term, manager)
1773        } else {
1774            term
1775        };
1776
1777        // Check again if simplification produced a constant
1778        if let Some(t) = manager.get(term_to_encode) {
1779            match t.kind {
1780                TermKind::False => {
1781                    if !self.has_false_assertion {
1782                        self.has_false_assertion = true;
1783                        self.trail.push(TrailOp::FalseAssertionSet);
1784                    }
1785                    return;
1786                }
1787                TermKind::True => {
1788                    // Simplified to true, no need to encode
1789                    return;
1790                }
1791                _ => {}
1792            }
1793        }
1794
1795        // Collect polarity information if polarity-aware encoding is enabled
1796        if self.polarity_aware {
1797            self.collect_polarities(term_to_encode, Polarity::Positive, manager);
1798        }
1799
1800        // Encode the assertion immediately
1801        let lit = self.encode(term_to_encode, manager);
1802        self.sat.add_clause([lit]);
1803
1804        if self.produce_unsat_cores {
1805            let na_index = self.named_assertions.len();
1806            self.named_assertions.push(NamedAssertion {
1807                term,
1808                name: None,
1809                index: index as u32,
1810            });
1811            self.trail
1812                .push(TrailOp::NamedAssertionAdded { index: na_index });
1813        }
1814    }
1815
1816    /// Assert a named term (for unsat core tracking)
1817    pub fn assert_named(&mut self, term: TermId, name: &str, manager: &mut TermManager) {
1818        let index = self.assertions.len();
1819        self.assertions.push(term);
1820        self.trail.push(TrailOp::AssertionAdded { index });
1821
1822        // Check if this is a boolean constant first
1823        if let Some(t) = manager.get(term) {
1824            match t.kind {
1825                TermKind::False => {
1826                    // Mark that we have a false assertion
1827                    if !self.has_false_assertion {
1828                        self.has_false_assertion = true;
1829                        self.trail.push(TrailOp::FalseAssertionSet);
1830                    }
1831                    if self.produce_unsat_cores {
1832                        let na_index = self.named_assertions.len();
1833                        self.named_assertions.push(NamedAssertion {
1834                            term,
1835                            name: Some(name.to_string()),
1836                            index: index as u32,
1837                        });
1838                        self.trail
1839                            .push(TrailOp::NamedAssertionAdded { index: na_index });
1840                    }
1841                    return;
1842                }
1843                TermKind::True => {
1844                    // True is always satisfied, no need to encode
1845                    if self.produce_unsat_cores {
1846                        let na_index = self.named_assertions.len();
1847                        self.named_assertions.push(NamedAssertion {
1848                            term,
1849                            name: Some(name.to_string()),
1850                            index: index as u32,
1851                        });
1852                        self.trail
1853                            .push(TrailOp::NamedAssertionAdded { index: na_index });
1854                    }
1855                    return;
1856                }
1857                _ => {}
1858            }
1859        }
1860
1861        // Collect polarity information if polarity-aware encoding is enabled
1862        if self.polarity_aware {
1863            self.collect_polarities(term, Polarity::Positive, manager);
1864        }
1865
1866        // Encode the assertion immediately
1867        let lit = self.encode(term, manager);
1868        self.sat.add_clause([lit]);
1869
1870        if self.produce_unsat_cores {
1871            let na_index = self.named_assertions.len();
1872            self.named_assertions.push(NamedAssertion {
1873                term,
1874                name: Some(name.to_string()),
1875                index: index as u32,
1876            });
1877            self.trail
1878                .push(TrailOp::NamedAssertionAdded { index: na_index });
1879        }
1880    }
1881
1882    /// Get the unsat core (after check() returned Unsat)
1883    #[must_use]
1884    pub fn get_unsat_core(&self) -> Option<&UnsatCore> {
1885        self.unsat_core.as_ref()
1886    }
1887
1888    /// Encode a term into SAT clauses using Tseitin transformation
1889    fn encode(&mut self, term: TermId, manager: &mut TermManager) -> Lit {
1890        // Clone the term data to avoid borrowing issues
1891        let Some(t) = manager.get(term).cloned() else {
1892            let var = self.get_or_create_var(term);
1893            return Lit::pos(var);
1894        };
1895
1896        match &t.kind {
1897            TermKind::True => {
1898                let var = self.get_or_create_var(manager.mk_true());
1899                self.sat.add_clause([Lit::pos(var)]);
1900                Lit::pos(var)
1901            }
1902            TermKind::False => {
1903                let var = self.get_or_create_var(manager.mk_false());
1904                self.sat.add_clause([Lit::neg(var)]);
1905                Lit::neg(var)
1906            }
1907            TermKind::Var(_) => {
1908                let var = self.get_or_create_var(term);
1909                Lit::pos(var)
1910            }
1911            TermKind::Not(arg) => {
1912                let arg_lit = self.encode(*arg, manager);
1913                arg_lit.negate()
1914            }
1915            TermKind::And(args) => {
1916                let result_var = self.get_or_create_var(term);
1917                let result = Lit::pos(result_var);
1918
1919                let mut arg_lits: Vec<Lit> = Vec::new();
1920                for &arg in args {
1921                    arg_lits.push(self.encode(arg, manager));
1922                }
1923
1924                // Get polarity for optimization
1925                let polarity = if self.polarity_aware {
1926                    self.polarities
1927                        .get(&term)
1928                        .copied()
1929                        .unwrap_or(Polarity::Both)
1930                } else {
1931                    Polarity::Both
1932                };
1933
1934                // result => all args (needed when result is positive)
1935                // ~result or arg1, ~result or arg2, ...
1936                if polarity != Polarity::Negative {
1937                    for &arg in &arg_lits {
1938                        self.sat.add_clause([result.negate(), arg]);
1939                    }
1940                }
1941
1942                // all args => result (needed when result is negative)
1943                // ~arg1 or ~arg2 or ... or result
1944                if polarity != Polarity::Positive {
1945                    let mut clause: Vec<Lit> = arg_lits.iter().map(|l| l.negate()).collect();
1946                    clause.push(result);
1947                    self.sat.add_clause(clause);
1948                }
1949
1950                result
1951            }
1952            TermKind::Or(args) => {
1953                let result_var = self.get_or_create_var(term);
1954                let result = Lit::pos(result_var);
1955
1956                let mut arg_lits: Vec<Lit> = Vec::new();
1957                for &arg in args {
1958                    arg_lits.push(self.encode(arg, manager));
1959                }
1960
1961                // Get polarity for optimization
1962                let polarity = if self.polarity_aware {
1963                    self.polarities
1964                        .get(&term)
1965                        .copied()
1966                        .unwrap_or(Polarity::Both)
1967                } else {
1968                    Polarity::Both
1969                };
1970
1971                // result => some arg (needed when result is positive)
1972                // ~result or arg1 or arg2 or ...
1973                if polarity != Polarity::Negative {
1974                    let mut clause: Vec<Lit> = vec![result.negate()];
1975                    clause.extend(arg_lits.iter().copied());
1976                    self.sat.add_clause(clause);
1977                }
1978
1979                // some arg => result (needed when result is negative)
1980                // ~arg1 or result, ~arg2 or result, ...
1981                if polarity != Polarity::Positive {
1982                    for &arg in &arg_lits {
1983                        self.sat.add_clause([arg.negate(), result]);
1984                    }
1985                }
1986
1987                result
1988            }
1989            TermKind::Xor(lhs, rhs) => {
1990                let lhs_lit = self.encode(*lhs, manager);
1991                let rhs_lit = self.encode(*rhs, manager);
1992
1993                let result_var = self.get_or_create_var(term);
1994                let result = Lit::pos(result_var);
1995
1996                // result <=> (lhs xor rhs)
1997                // result <=> (lhs and ~rhs) or (~lhs and rhs)
1998
1999                // result => (lhs or rhs)
2000                self.sat.add_clause([result.negate(), lhs_lit, rhs_lit]);
2001                // result => (~lhs or ~rhs)
2002                self.sat
2003                    .add_clause([result.negate(), lhs_lit.negate(), rhs_lit.negate()]);
2004
2005                // (lhs and ~rhs) => result
2006                self.sat.add_clause([lhs_lit.negate(), rhs_lit, result]);
2007                // (~lhs and rhs) => result
2008                self.sat.add_clause([lhs_lit, rhs_lit.negate(), result]);
2009
2010                result
2011            }
2012            TermKind::Implies(lhs, rhs) => {
2013                let lhs_lit = self.encode(*lhs, manager);
2014                let rhs_lit = self.encode(*rhs, manager);
2015
2016                let result_var = self.get_or_create_var(term);
2017                let result = Lit::pos(result_var);
2018
2019                // result <=> (~lhs or rhs)
2020                // result => ~lhs or rhs
2021                self.sat
2022                    .add_clause([result.negate(), lhs_lit.negate(), rhs_lit]);
2023
2024                // (~lhs or rhs) => result
2025                // lhs or result, ~rhs or result
2026                self.sat.add_clause([lhs_lit, result]);
2027                self.sat.add_clause([rhs_lit.negate(), result]);
2028
2029                result
2030            }
2031            TermKind::Ite(cond, then_br, else_br) => {
2032                let cond_lit = self.encode(*cond, manager);
2033                let then_lit = self.encode(*then_br, manager);
2034                let else_lit = self.encode(*else_br, manager);
2035
2036                let result_var = self.get_or_create_var(term);
2037                let result = Lit::pos(result_var);
2038
2039                // result <=> (cond ? then : else)
2040                // cond and result => then
2041                self.sat
2042                    .add_clause([cond_lit.negate(), result.negate(), then_lit]);
2043                // cond and then => result
2044                self.sat
2045                    .add_clause([cond_lit.negate(), then_lit.negate(), result]);
2046
2047                // ~cond and result => else
2048                self.sat.add_clause([cond_lit, result.negate(), else_lit]);
2049                // ~cond and else => result
2050                self.sat.add_clause([cond_lit, else_lit.negate(), result]);
2051
2052                result
2053            }
2054            TermKind::Eq(lhs, rhs) => {
2055                // Check if this is a boolean equality or theory equality
2056                let lhs_term = manager.get(*lhs);
2057                let is_bool_eq = lhs_term.is_some_and(|t| t.sort == manager.sorts.bool_sort);
2058
2059                if is_bool_eq {
2060                    // Boolean equality: encode as iff
2061                    let lhs_lit = self.encode(*lhs, manager);
2062                    let rhs_lit = self.encode(*rhs, manager);
2063
2064                    let result_var = self.get_or_create_var(term);
2065                    let result = Lit::pos(result_var);
2066
2067                    // result <=> (lhs <=> rhs)
2068                    // result => (lhs => rhs) and (rhs => lhs)
2069                    self.sat
2070                        .add_clause([result.negate(), lhs_lit.negate(), rhs_lit]);
2071                    self.sat
2072                        .add_clause([result.negate(), rhs_lit.negate(), lhs_lit]);
2073
2074                    // (lhs <=> rhs) => result
2075                    self.sat.add_clause([lhs_lit, rhs_lit, result]);
2076                    self.sat
2077                        .add_clause([lhs_lit.negate(), rhs_lit.negate(), result]);
2078
2079                    result
2080                } else {
2081                    // Theory equality: create a fresh boolean variable
2082                    // Store the constraint for theory propagation
2083                    let var = self.get_or_create_var(term);
2084                    self.var_to_constraint
2085                        .insert(var, Constraint::Eq(*lhs, *rhs));
2086                    self.trail.push(TrailOp::ConstraintAdded { var });
2087                    Lit::pos(var)
2088                }
2089            }
2090            TermKind::Distinct(args) => {
2091                // Encode distinct as pairwise disequalities
2092                // distinct(a,b,c) <=> (a!=b) and (a!=c) and (b!=c)
2093                if args.len() <= 1 {
2094                    // trivially true
2095                    let var = self.get_or_create_var(manager.mk_true());
2096                    return Lit::pos(var);
2097                }
2098
2099                let result_var = self.get_or_create_var(term);
2100                let result = Lit::pos(result_var);
2101
2102                let mut diseq_lits = Vec::new();
2103                for i in 0..args.len() {
2104                    for j in (i + 1)..args.len() {
2105                        let eq = manager.mk_eq(args[i], args[j]);
2106                        let eq_lit = self.encode(eq, manager);
2107                        diseq_lits.push(eq_lit.negate());
2108                    }
2109                }
2110
2111                // result => all disequalities
2112                for &diseq in &diseq_lits {
2113                    self.sat.add_clause([result.negate(), diseq]);
2114                }
2115
2116                // all disequalities => result
2117                let mut clause: Vec<Lit> = diseq_lits.iter().map(|l| l.negate()).collect();
2118                clause.push(result);
2119                self.sat.add_clause(clause);
2120
2121                result
2122            }
2123            TermKind::Let { bindings, body } => {
2124                // For encoding, we can substitute the bindings into the body
2125                // This is a simplification - a more sophisticated approach would
2126                // memoize the bindings
2127                let substituted = *body;
2128                for (name, value) in bindings.iter().rev() {
2129                    // In a full implementation, we'd perform proper substitution
2130                    // For now, just encode the body directly
2131                    let _ = (name, value);
2132                }
2133                self.encode(substituted, manager)
2134            }
2135            // Theory atoms (arithmetic, bitvec, arrays, UF)
2136            // These get fresh boolean variables - the theory solver handles the semantics
2137            TermKind::IntConst(_) | TermKind::RealConst(_) | TermKind::BitVecConst { .. } => {
2138                // Constants are theory terms, not boolean formulas
2139                // Should not appear at top level in boolean context
2140                let var = self.get_or_create_var(term);
2141                Lit::pos(var)
2142            }
2143            TermKind::Neg(_)
2144            | TermKind::Add(_)
2145            | TermKind::Sub(_, _)
2146            | TermKind::Mul(_)
2147            | TermKind::Div(_, _)
2148            | TermKind::Mod(_, _) => {
2149                // Arithmetic terms - should not appear at boolean top level
2150                let var = self.get_or_create_var(term);
2151                Lit::pos(var)
2152            }
2153            TermKind::Lt(lhs, rhs) => {
2154                // Arithmetic predicate: lhs < rhs
2155                let var = self.get_or_create_var(term);
2156                self.var_to_constraint
2157                    .insert(var, Constraint::Lt(*lhs, *rhs));
2158                self.trail.push(TrailOp::ConstraintAdded { var });
2159                // Parse and store linear constraint for ArithSolver
2160                if let Some(parsed) =
2161                    self.parse_arith_comparison(*lhs, *rhs, ArithConstraintType::Lt, term, manager)
2162                {
2163                    self.var_to_parsed_arith.insert(var, parsed);
2164                }
2165                Lit::pos(var)
2166            }
2167            TermKind::Le(lhs, rhs) => {
2168                // Arithmetic predicate: lhs <= rhs
2169                let var = self.get_or_create_var(term);
2170                self.var_to_constraint
2171                    .insert(var, Constraint::Le(*lhs, *rhs));
2172                self.trail.push(TrailOp::ConstraintAdded { var });
2173                // Parse and store linear constraint for ArithSolver
2174                if let Some(parsed) =
2175                    self.parse_arith_comparison(*lhs, *rhs, ArithConstraintType::Le, term, manager)
2176                {
2177                    self.var_to_parsed_arith.insert(var, parsed);
2178                }
2179                Lit::pos(var)
2180            }
2181            TermKind::Gt(lhs, rhs) => {
2182                // Arithmetic predicate: lhs > rhs
2183                let var = self.get_or_create_var(term);
2184                self.var_to_constraint
2185                    .insert(var, Constraint::Gt(*lhs, *rhs));
2186                self.trail.push(TrailOp::ConstraintAdded { var });
2187                // Parse and store linear constraint for ArithSolver
2188                if let Some(parsed) =
2189                    self.parse_arith_comparison(*lhs, *rhs, ArithConstraintType::Gt, term, manager)
2190                {
2191                    self.var_to_parsed_arith.insert(var, parsed);
2192                }
2193                Lit::pos(var)
2194            }
2195            TermKind::Ge(lhs, rhs) => {
2196                // Arithmetic predicate: lhs >= rhs
2197                let var = self.get_or_create_var(term);
2198                self.var_to_constraint
2199                    .insert(var, Constraint::Ge(*lhs, *rhs));
2200                self.trail.push(TrailOp::ConstraintAdded { var });
2201                // Parse and store linear constraint for ArithSolver
2202                if let Some(parsed) =
2203                    self.parse_arith_comparison(*lhs, *rhs, ArithConstraintType::Ge, term, manager)
2204                {
2205                    self.var_to_parsed_arith.insert(var, parsed);
2206                }
2207                Lit::pos(var)
2208            }
2209            TermKind::BvConcat(_, _)
2210            | TermKind::BvExtract { .. }
2211            | TermKind::BvNot(_)
2212            | TermKind::BvAnd(_, _)
2213            | TermKind::BvOr(_, _)
2214            | TermKind::BvXor(_, _)
2215            | TermKind::BvAdd(_, _)
2216            | TermKind::BvSub(_, _)
2217            | TermKind::BvMul(_, _)
2218            | TermKind::BvUdiv(_, _)
2219            | TermKind::BvSdiv(_, _)
2220            | TermKind::BvUrem(_, _)
2221            | TermKind::BvSrem(_, _)
2222            | TermKind::BvShl(_, _)
2223            | TermKind::BvLshr(_, _)
2224            | TermKind::BvAshr(_, _) => {
2225                // Bitvector terms - should not appear at boolean top level
2226                let var = self.get_or_create_var(term);
2227                Lit::pos(var)
2228            }
2229            TermKind::BvUlt(lhs, rhs) => {
2230                // Bitvector unsigned less-than: treat as integer comparison
2231                let var = self.get_or_create_var(term);
2232                self.var_to_constraint
2233                    .insert(var, Constraint::Lt(*lhs, *rhs));
2234                self.trail.push(TrailOp::ConstraintAdded { var });
2235                // Parse as arithmetic constraint (bitvector as bounded integer)
2236                if let Some(parsed) =
2237                    self.parse_arith_comparison(*lhs, *rhs, ArithConstraintType::Lt, term, manager)
2238                {
2239                    self.var_to_parsed_arith.insert(var, parsed);
2240                }
2241                Lit::pos(var)
2242            }
2243            TermKind::BvUle(lhs, rhs) => {
2244                // Bitvector unsigned less-than-or-equal: treat as integer comparison
2245                let var = self.get_or_create_var(term);
2246                self.var_to_constraint
2247                    .insert(var, Constraint::Le(*lhs, *rhs));
2248                self.trail.push(TrailOp::ConstraintAdded { var });
2249                if let Some(parsed) =
2250                    self.parse_arith_comparison(*lhs, *rhs, ArithConstraintType::Le, term, manager)
2251                {
2252                    self.var_to_parsed_arith.insert(var, parsed);
2253                }
2254                Lit::pos(var)
2255            }
2256            TermKind::BvSlt(lhs, rhs) => {
2257                // Bitvector signed less-than: treat as integer comparison
2258                let var = self.get_or_create_var(term);
2259                self.var_to_constraint
2260                    .insert(var, Constraint::Lt(*lhs, *rhs));
2261                self.trail.push(TrailOp::ConstraintAdded { var });
2262                if let Some(parsed) =
2263                    self.parse_arith_comparison(*lhs, *rhs, ArithConstraintType::Lt, term, manager)
2264                {
2265                    self.var_to_parsed_arith.insert(var, parsed);
2266                }
2267                Lit::pos(var)
2268            }
2269            TermKind::BvSle(lhs, rhs) => {
2270                // Bitvector signed less-than-or-equal: treat as integer comparison
2271                let var = self.get_or_create_var(term);
2272                self.var_to_constraint
2273                    .insert(var, Constraint::Le(*lhs, *rhs));
2274                self.trail.push(TrailOp::ConstraintAdded { var });
2275                if let Some(parsed) =
2276                    self.parse_arith_comparison(*lhs, *rhs, ArithConstraintType::Le, term, manager)
2277                {
2278                    self.var_to_parsed_arith.insert(var, parsed);
2279                }
2280                Lit::pos(var)
2281            }
2282            TermKind::Select(_, _) | TermKind::Store(_, _, _) => {
2283                // Array operations - theory terms
2284                let var = self.get_or_create_var(term);
2285                Lit::pos(var)
2286            }
2287            TermKind::Apply { .. } => {
2288                // Uninterpreted function application - theory term
2289                let var = self.get_or_create_var(term);
2290                Lit::pos(var)
2291            }
2292            TermKind::Forall { patterns, .. } => {
2293                // Universal quantifiers: register with MBQI
2294                self.has_quantifiers = true;
2295                self.mbqi.add_quantifier(term, manager);
2296                // Collect ground terms from patterns as candidates
2297                for pattern in patterns {
2298                    for &trigger in pattern {
2299                        self.mbqi.collect_ground_terms(trigger, manager);
2300                    }
2301                }
2302                // Create a boolean variable for the quantifier
2303                let var = self.get_or_create_var(term);
2304                Lit::pos(var)
2305            }
2306            TermKind::Exists { patterns, .. } => {
2307                // Existential quantifiers: register with MBQI for tracking
2308                self.has_quantifiers = true;
2309                self.mbqi.add_quantifier(term, manager);
2310                // Collect ground terms from patterns
2311                for pattern in patterns {
2312                    for &trigger in pattern {
2313                        self.mbqi.collect_ground_terms(trigger, manager);
2314                    }
2315                }
2316                // Create a boolean variable for the quantifier
2317                let var = self.get_or_create_var(term);
2318                Lit::pos(var)
2319            }
2320            // String operations - theory terms and predicates
2321            TermKind::StringLit(_)
2322            | TermKind::StrConcat(_, _)
2323            | TermKind::StrLen(_)
2324            | TermKind::StrSubstr(_, _, _)
2325            | TermKind::StrAt(_, _)
2326            | TermKind::StrReplace(_, _, _)
2327            | TermKind::StrReplaceAll(_, _, _)
2328            | TermKind::StrToInt(_)
2329            | TermKind::IntToStr(_)
2330            | TermKind::StrInRe(_, _) => {
2331                // String terms - theory solver handles these
2332                let var = self.get_or_create_var(term);
2333                Lit::pos(var)
2334            }
2335            TermKind::StrContains(_, _)
2336            | TermKind::StrPrefixOf(_, _)
2337            | TermKind::StrSuffixOf(_, _)
2338            | TermKind::StrIndexOf(_, _, _) => {
2339                // String predicates - theory atoms
2340                let var = self.get_or_create_var(term);
2341                Lit::pos(var)
2342            }
2343            // Floating-point constants and special values
2344            TermKind::FpLit { .. }
2345            | TermKind::FpPlusInfinity { .. }
2346            | TermKind::FpMinusInfinity { .. }
2347            | TermKind::FpPlusZero { .. }
2348            | TermKind::FpMinusZero { .. }
2349            | TermKind::FpNaN { .. } => {
2350                // FP constants - theory terms
2351                let var = self.get_or_create_var(term);
2352                Lit::pos(var)
2353            }
2354            // Floating-point operations
2355            TermKind::FpAbs(_)
2356            | TermKind::FpNeg(_)
2357            | TermKind::FpSqrt(_, _)
2358            | TermKind::FpRoundToIntegral(_, _)
2359            | TermKind::FpAdd(_, _, _)
2360            | TermKind::FpSub(_, _, _)
2361            | TermKind::FpMul(_, _, _)
2362            | TermKind::FpDiv(_, _, _)
2363            | TermKind::FpRem(_, _)
2364            | TermKind::FpMin(_, _)
2365            | TermKind::FpMax(_, _)
2366            | TermKind::FpFma(_, _, _, _) => {
2367                // FP operations - theory terms
2368                let var = self.get_or_create_var(term);
2369                Lit::pos(var)
2370            }
2371            // Floating-point predicates
2372            TermKind::FpLeq(_, _)
2373            | TermKind::FpLt(_, _)
2374            | TermKind::FpGeq(_, _)
2375            | TermKind::FpGt(_, _)
2376            | TermKind::FpEq(_, _)
2377            | TermKind::FpIsNormal(_)
2378            | TermKind::FpIsSubnormal(_)
2379            | TermKind::FpIsZero(_)
2380            | TermKind::FpIsInfinite(_)
2381            | TermKind::FpIsNaN(_)
2382            | TermKind::FpIsNegative(_)
2383            | TermKind::FpIsPositive(_) => {
2384                // FP predicates - theory atoms that return bool
2385                let var = self.get_or_create_var(term);
2386                Lit::pos(var)
2387            }
2388            // Floating-point conversions
2389            TermKind::FpToFp { .. }
2390            | TermKind::FpToSBV { .. }
2391            | TermKind::FpToUBV { .. }
2392            | TermKind::FpToReal(_)
2393            | TermKind::RealToFp { .. }
2394            | TermKind::SBVToFp { .. }
2395            | TermKind::UBVToFp { .. } => {
2396                // FP conversions - theory terms
2397                let var = self.get_or_create_var(term);
2398                Lit::pos(var)
2399            }
2400            // Datatype operations
2401            TermKind::DtConstructor { .. }
2402            | TermKind::DtTester { .. }
2403            | TermKind::DtSelector { .. } => {
2404                // Datatype operations - theory terms
2405                let var = self.get_or_create_var(term);
2406                Lit::pos(var)
2407            }
2408        }
2409    }
2410
2411    /// Check satisfiability
2412    pub fn check(&mut self, manager: &mut TermManager) -> SolverResult {
2413        // Check for trivial unsat (false assertion)
2414        if self.has_false_assertion {
2415            self.build_unsat_core_trivial_false();
2416            return SolverResult::Unsat;
2417        }
2418
2419        if self.assertions.is_empty() {
2420            return SolverResult::Sat;
2421        }
2422
2423        // Check resource limits before starting
2424        if self.config.max_conflicts > 0 && self.statistics.conflicts >= self.config.max_conflicts {
2425            return SolverResult::Unknown;
2426        }
2427        if self.config.max_decisions > 0 && self.statistics.decisions >= self.config.max_decisions {
2428            return SolverResult::Unknown;
2429        }
2430
2431        // Run SAT solver with theory integration
2432        let mut theory_manager = TheoryManager::new(
2433            &mut self.euf,
2434            &mut self.arith,
2435            &mut self.bv,
2436            &self.var_to_constraint,
2437            &self.var_to_parsed_arith,
2438            &self.term_to_var,
2439            self.config.theory_mode,
2440            &mut self.statistics,
2441            self.config.max_conflicts,
2442            self.config.max_decisions,
2443        );
2444
2445        // MBQI loop for quantified formulas
2446        let max_mbqi_iterations = 100;
2447        let mut mbqi_iteration = 0;
2448
2449        loop {
2450            let sat_result = self.sat.solve_with_theory(&mut theory_manager);
2451
2452            match sat_result {
2453                SatResult::Unsat => {
2454                    self.build_unsat_core();
2455                    return SolverResult::Unsat;
2456                }
2457                SatResult::Unknown => {
2458                    return SolverResult::Unknown;
2459                }
2460                SatResult::Sat => {
2461                    // If no quantifiers, we're done
2462                    if !self.has_quantifiers {
2463                        self.build_model(manager);
2464                        self.unsat_core = None;
2465                        return SolverResult::Sat;
2466                    }
2467
2468                    // Build partial model for MBQI
2469                    self.build_model(manager);
2470
2471                    // Run MBQI to check quantified formulas
2472                    let model_assignments = self
2473                        .model
2474                        .as_ref()
2475                        .map(|m| m.assignments().clone())
2476                        .unwrap_or_default();
2477                    let mbqi_result = self.mbqi.check_with_model(&model_assignments, manager);
2478
2479                    match mbqi_result {
2480                        MBQIResult::NoQuantifiers | MBQIResult::Satisfied => {
2481                            // All quantifiers satisfied
2482                            self.unsat_core = None;
2483                            return SolverResult::Sat;
2484                        }
2485                        MBQIResult::InstantiationLimit => {
2486                            // Too many instantiations - return unknown
2487                            return SolverResult::Unknown;
2488                        }
2489                        MBQIResult::Conflict(conflict_terms) => {
2490                            // Add conflict clause
2491                            let lits: Vec<Lit> = conflict_terms
2492                                .iter()
2493                                .filter_map(|&t| self.term_to_var.get(&t).map(|&v| Lit::neg(v)))
2494                                .collect();
2495                            if !lits.is_empty() {
2496                                self.sat.add_clause(lits);
2497                            }
2498                            // Continue loop
2499                        }
2500                        MBQIResult::NewInstantiations(instantiations) => {
2501                            // Add instantiation lemmas
2502                            for inst in instantiations {
2503                                // The instantiation is: ∀x.φ(x) → φ(t)
2504                                // We assert φ(t) (the result term)
2505                                let lit = self.encode(inst.result, manager);
2506                                self.sat.add_clause([lit]);
2507                            }
2508                            // Continue loop
2509                        }
2510                    }
2511
2512                    mbqi_iteration += 1;
2513                    if mbqi_iteration >= max_mbqi_iterations {
2514                        return SolverResult::Unknown;
2515                    }
2516
2517                    // Recreate theory manager for next iteration
2518                    theory_manager = TheoryManager::new(
2519                        &mut self.euf,
2520                        &mut self.arith,
2521                        &mut self.bv,
2522                        &self.var_to_constraint,
2523                        &self.var_to_parsed_arith,
2524                        &self.term_to_var,
2525                        self.config.theory_mode,
2526                        &mut self.statistics,
2527                        self.config.max_conflicts,
2528                        self.config.max_decisions,
2529                    );
2530                }
2531            }
2532        }
2533    }
2534
2535    /// Check satisfiability under assumptions
2536    /// Assumptions are temporary constraints that don't modify the assertion stack
2537    pub fn check_with_assumptions(
2538        &mut self,
2539        assumptions: &[TermId],
2540        manager: &mut TermManager,
2541    ) -> SolverResult {
2542        // Save current state
2543        self.push();
2544
2545        // Assert all assumptions
2546        for &assumption in assumptions {
2547            self.assert(assumption, manager);
2548        }
2549
2550        // Check satisfiability
2551        let result = self.check(manager);
2552
2553        // Restore state
2554        self.pop();
2555
2556        result
2557    }
2558
2559    /// Check satisfiability (pure SAT, no theory integration)
2560    /// Useful for benchmarking or when theories are not needed
2561    pub fn check_sat_only(&mut self, manager: &mut TermManager) -> SolverResult {
2562        if self.assertions.is_empty() {
2563            return SolverResult::Sat;
2564        }
2565
2566        match self.sat.solve() {
2567            SatResult::Sat => {
2568                self.build_model(manager);
2569                SolverResult::Sat
2570            }
2571            SatResult::Unsat => SolverResult::Unsat,
2572            SatResult::Unknown => SolverResult::Unknown,
2573        }
2574    }
2575
2576    /// Build the model after SAT solving
2577    fn build_model(&mut self, manager: &mut TermManager) {
2578        let mut model = Model::new();
2579        let sat_model = self.sat.model();
2580
2581        // Get boolean values from SAT model
2582        for (&term, &var) in &self.term_to_var {
2583            let val = sat_model.get(var.index()).copied();
2584            if let Some(v) = val {
2585                let bool_val = if v.is_true() {
2586                    manager.mk_true()
2587                } else if v.is_false() {
2588                    manager.mk_false()
2589                } else {
2590                    continue;
2591                };
2592                model.set(term, bool_val);
2593            }
2594        }
2595
2596        // Get arithmetic values from theory solver
2597        // Iterate over all terms and check if they have arithmetic values
2598        for &term in self.term_to_var.keys() {
2599            if let Some(value) = self.arith.value(term) {
2600                // Create the appropriate value term based on whether it's integer or real
2601                let value_term = if *value.denom() == 1 {
2602                    // Integer value
2603                    manager.mk_int(*value.numer())
2604                } else {
2605                    // Rational value
2606                    manager.mk_real(value)
2607                };
2608                model.set(term, value_term);
2609            }
2610        }
2611
2612        self.model = Some(model);
2613    }
2614
2615    /// Build unsat core for trivial conflicts (assertion of false)
2616    fn build_unsat_core_trivial_false(&mut self) {
2617        if !self.produce_unsat_cores {
2618            self.unsat_core = None;
2619            return;
2620        }
2621
2622        // Find all assertions that are trivially false
2623        let mut core = UnsatCore::new();
2624
2625        for (i, &term) in self.assertions.iter().enumerate() {
2626            if term == TermId::new(1) {
2627                // This is a false assertion
2628                core.indices.push(i as u32);
2629
2630                // Find the name if there is one
2631                if let Some(named) = self.named_assertions.iter().find(|na| na.index == i as u32)
2632                    && let Some(ref name) = named.name
2633                {
2634                    core.names.push(name.clone());
2635                }
2636            }
2637        }
2638
2639        self.unsat_core = Some(core);
2640    }
2641
2642    /// Build unsat core from SAT solver conflict analysis
2643    fn build_unsat_core(&mut self) {
2644        if !self.produce_unsat_cores {
2645            self.unsat_core = None;
2646            return;
2647        }
2648
2649        // Build unsat core from the named assertions
2650        // In assumption-based mode, we would use the failed assumptions from the SAT solver
2651        // For now, we use a heuristic approach based on the conflict analysis
2652
2653        let mut core = UnsatCore::new();
2654
2655        // If assumption_vars is populated, we can use assumption-based extraction
2656        if !self.assumption_vars.is_empty() {
2657            // Assumption-based core extraction
2658            // Get the failed assumptions from the SAT solver
2659            // Note: This requires SAT solver support for assumption tracking
2660            // For now, include all named assertions as a conservative approach
2661            for na in &self.named_assertions {
2662                core.indices.push(na.index);
2663                if let Some(ref name) = na.name {
2664                    core.names.push(name.clone());
2665                }
2666            }
2667        } else {
2668            // Fallback: include all named assertions
2669            // This provides a valid unsat core, though not necessarily minimal
2670            for na in &self.named_assertions {
2671                core.indices.push(na.index);
2672                if let Some(ref name) = na.name {
2673                    core.names.push(name.clone());
2674                }
2675            }
2676        }
2677
2678        self.unsat_core = Some(core);
2679    }
2680
2681    /// Enable assumption-based unsat core tracking
2682    /// This creates assumption variables for each assertion
2683    /// which can be used to efficiently extract minimal unsat cores
2684    pub fn enable_assumption_based_cores(&mut self) {
2685        self.produce_unsat_cores = true;
2686        // Assumption variables would be created during assertion
2687        // to enable fine-grained core extraction
2688    }
2689
2690    /// Minimize an unsat core using greedy deletion
2691    /// This creates a minimal (but not necessarily minimum) unsatisfiable subset
2692    pub fn minimize_unsat_core(&mut self, manager: &mut TermManager) -> Option<UnsatCore> {
2693        if !self.produce_unsat_cores {
2694            return None;
2695        }
2696
2697        // Get the current unsat core
2698        let core = self.unsat_core.as_ref()?;
2699        if core.is_empty() {
2700            return Some(core.clone());
2701        }
2702
2703        // Extract the assertions in the core
2704        let mut core_assertions: Vec<_> = core
2705            .indices
2706            .iter()
2707            .map(|&idx| {
2708                let assertion = self.assertions[idx as usize];
2709                let name = self
2710                    .named_assertions
2711                    .iter()
2712                    .find(|na| na.index == idx)
2713                    .and_then(|na| na.name.clone());
2714                (idx, assertion, name)
2715            })
2716            .collect();
2717
2718        // Try to remove each assertion one by one
2719        let mut i = 0;
2720        while i < core_assertions.len() {
2721            // Create a temporary solver with all assertions except the i-th one
2722            let mut temp_solver = Solver::new();
2723            temp_solver.set_logic(self.logic.as_deref().unwrap_or("ALL"));
2724
2725            // Add all assertions except the i-th one
2726            for (j, &(_, assertion, _)) in core_assertions.iter().enumerate() {
2727                if i != j {
2728                    temp_solver.assert(assertion, manager);
2729                }
2730            }
2731
2732            // Check if still unsat
2733            if temp_solver.check(manager) == SolverResult::Unsat {
2734                // Still unsat without this assertion - remove it
2735                core_assertions.remove(i);
2736                // Don't increment i, check the next element which is now at position i
2737            } else {
2738                // This assertion is needed
2739                i += 1;
2740            }
2741        }
2742
2743        // Build the minimized core
2744        let mut minimized = UnsatCore::new();
2745        for (idx, _, name) in core_assertions {
2746            minimized.indices.push(idx);
2747            if let Some(n) = name {
2748                minimized.names.push(n);
2749            }
2750        }
2751
2752        Some(minimized)
2753    }
2754
2755    /// Get the model (if sat)
2756    #[must_use]
2757    pub fn model(&self) -> Option<&Model> {
2758        self.model.as_ref()
2759    }
2760
2761    /// Assert multiple terms at once
2762    /// This is more efficient than calling assert() multiple times
2763    pub fn assert_many(&mut self, terms: &[TermId], manager: &mut TermManager) {
2764        for &term in terms {
2765            self.assert(term, manager);
2766        }
2767    }
2768
2769    /// Get the number of assertions in the solver
2770    #[must_use]
2771    pub fn num_assertions(&self) -> usize {
2772        self.assertions.len()
2773    }
2774
2775    /// Get the number of variables in the SAT solver
2776    #[must_use]
2777    pub fn num_variables(&self) -> usize {
2778        self.term_to_var.len()
2779    }
2780
2781    /// Check if the solver has any assertions
2782    #[must_use]
2783    pub fn has_assertions(&self) -> bool {
2784        !self.assertions.is_empty()
2785    }
2786
2787    /// Get the current context level (push/pop depth)
2788    #[must_use]
2789    pub fn context_level(&self) -> usize {
2790        self.context_stack.len()
2791    }
2792
2793    /// Push a context level
2794    pub fn push(&mut self) {
2795        self.context_stack.push(ContextState {
2796            num_assertions: self.assertions.len(),
2797            num_vars: self.var_to_term.len(),
2798            has_false_assertion: self.has_false_assertion,
2799            trail_position: self.trail.len(),
2800        });
2801        self.sat.push();
2802        self.euf.push();
2803        self.arith.push();
2804    }
2805
2806    /// Pop a context level using trail-based undo
2807    pub fn pop(&mut self) {
2808        if let Some(state) = self.context_stack.pop() {
2809            // Undo all operations in the trail since the push
2810            while self.trail.len() > state.trail_position {
2811                if let Some(op) = self.trail.pop() {
2812                    match op {
2813                        TrailOp::AssertionAdded { index } => {
2814                            if self.assertions.len() > index {
2815                                self.assertions.truncate(index);
2816                            }
2817                        }
2818                        TrailOp::VarCreated { var: _, term } => {
2819                            // Remove the term-to-var mapping
2820                            self.term_to_var.remove(&term);
2821                        }
2822                        TrailOp::ConstraintAdded { var } => {
2823                            // Remove the constraint
2824                            self.var_to_constraint.remove(&var);
2825                        }
2826                        TrailOp::FalseAssertionSet => {
2827                            // Reset the flag
2828                            self.has_false_assertion = false;
2829                        }
2830                        TrailOp::NamedAssertionAdded { index } => {
2831                            // Remove the named assertion
2832                            if self.named_assertions.len() > index {
2833                                self.named_assertions.truncate(index);
2834                            }
2835                        }
2836                    }
2837                }
2838            }
2839
2840            // Use state to restore other fields
2841            self.assertions.truncate(state.num_assertions);
2842            self.var_to_term.truncate(state.num_vars);
2843            self.has_false_assertion = state.has_false_assertion;
2844
2845            self.sat.pop();
2846            self.euf.pop();
2847            self.arith.pop();
2848        }
2849    }
2850
2851    /// Reset the solver
2852    pub fn reset(&mut self) {
2853        self.sat.reset();
2854        self.euf.reset();
2855        self.arith.reset();
2856        self.term_to_var.clear();
2857        self.var_to_term.clear();
2858        self.var_to_constraint.clear();
2859        self.var_to_parsed_arith.clear();
2860        self.assertions.clear();
2861        self.named_assertions.clear();
2862        self.model = None;
2863        self.unsat_core = None;
2864        self.context_stack.clear();
2865        self.trail.clear();
2866        self.logic = None;
2867        self.theory_processed_up_to = 0;
2868        self.has_false_assertion = false;
2869    }
2870
2871    /// Get the configuration
2872    #[must_use]
2873    pub fn config(&self) -> &SolverConfig {
2874        &self.config
2875    }
2876
2877    /// Set configuration
2878    pub fn set_config(&mut self, config: SolverConfig) {
2879        self.config = config;
2880    }
2881
2882    /// Get solver statistics
2883    #[must_use]
2884    pub fn stats(&self) -> &oxiz_sat::SolverStats {
2885        self.sat.stats()
2886    }
2887}
2888
2889#[cfg(test)]
2890mod tests {
2891    use super::*;
2892
2893    #[test]
2894    fn test_solver_empty() {
2895        let mut solver = Solver::new();
2896        let mut manager = TermManager::new();
2897        assert_eq!(solver.check(&mut manager), SolverResult::Sat);
2898    }
2899
2900    #[test]
2901    fn test_solver_true() {
2902        let mut solver = Solver::new();
2903        let mut manager = TermManager::new();
2904        let t = manager.mk_true();
2905        solver.assert(t, &mut manager);
2906        assert_eq!(solver.check(&mut manager), SolverResult::Sat);
2907    }
2908
2909    #[test]
2910    fn test_solver_false() {
2911        let mut solver = Solver::new();
2912        let mut manager = TermManager::new();
2913        let f = manager.mk_false();
2914        solver.assert(f, &mut manager);
2915        assert_eq!(solver.check(&mut manager), SolverResult::Unsat);
2916    }
2917
2918    #[test]
2919    fn test_solver_push_pop() {
2920        let mut solver = Solver::new();
2921        let mut manager = TermManager::new();
2922
2923        let t = manager.mk_true();
2924        solver.assert(t, &mut manager);
2925        solver.push();
2926
2927        let f = manager.mk_false();
2928        solver.assert(f, &mut manager);
2929        assert_eq!(solver.check(&mut manager), SolverResult::Unsat);
2930
2931        solver.pop();
2932        assert_eq!(solver.check(&mut manager), SolverResult::Sat);
2933    }
2934
2935    #[test]
2936    fn test_unsat_core_trivial() {
2937        let mut solver = Solver::new();
2938        let mut manager = TermManager::new();
2939        solver.set_produce_unsat_cores(true);
2940
2941        let t = manager.mk_true();
2942        let f = manager.mk_false();
2943
2944        solver.assert_named(t, "a1", &mut manager);
2945        solver.assert_named(f, "a2", &mut manager);
2946        solver.assert_named(t, "a3", &mut manager);
2947
2948        assert_eq!(solver.check(&mut manager), SolverResult::Unsat);
2949
2950        let core = solver.get_unsat_core();
2951        assert!(core.is_some());
2952
2953        let core = core.unwrap();
2954        assert!(!core.is_empty());
2955        assert!(core.names.contains(&"a2".to_string()));
2956    }
2957
2958    #[test]
2959    fn test_unsat_core_not_produced_when_sat() {
2960        let mut solver = Solver::new();
2961        let mut manager = TermManager::new();
2962        solver.set_produce_unsat_cores(true);
2963
2964        let t = manager.mk_true();
2965        solver.assert_named(t, "a1", &mut manager);
2966        solver.assert_named(t, "a2", &mut manager);
2967
2968        assert_eq!(solver.check(&mut manager), SolverResult::Sat);
2969        assert!(solver.get_unsat_core().is_none());
2970    }
2971
2972    #[test]
2973    fn test_unsat_core_disabled() {
2974        let mut solver = Solver::new();
2975        let mut manager = TermManager::new();
2976        // Don't enable unsat cores
2977
2978        let f = manager.mk_false();
2979        solver.assert(f, &mut manager);
2980        assert_eq!(solver.check(&mut manager), SolverResult::Unsat);
2981
2982        // Core should be None when not enabled
2983        assert!(solver.get_unsat_core().is_none());
2984    }
2985
2986    #[test]
2987    fn test_boolean_encoding_and() {
2988        let mut solver = Solver::new();
2989        let mut manager = TermManager::new();
2990
2991        // Test: (p and q) should be SAT with p=true, q=true
2992        let p = manager.mk_var("p", manager.sorts.bool_sort);
2993        let q = manager.mk_var("q", manager.sorts.bool_sort);
2994        let and = manager.mk_and(vec![p, q]);
2995
2996        solver.assert(and, &mut manager);
2997        assert_eq!(solver.check(&mut manager), SolverResult::Sat);
2998
2999        // The model should have both p and q as true
3000        let model = solver.model().expect("Should have model");
3001        assert!(model.get(p).is_some());
3002        assert!(model.get(q).is_some());
3003    }
3004
3005    #[test]
3006    fn test_boolean_encoding_or() {
3007        let mut solver = Solver::new();
3008        let mut manager = TermManager::new();
3009
3010        // Test: (p or q) and (not p) should be SAT with q=true
3011        let p = manager.mk_var("p", manager.sorts.bool_sort);
3012        let q = manager.mk_var("q", manager.sorts.bool_sort);
3013        let or = manager.mk_or(vec![p, q]);
3014        let not_p = manager.mk_not(p);
3015
3016        solver.assert(or, &mut manager);
3017        solver.assert(not_p, &mut manager);
3018        assert_eq!(solver.check(&mut manager), SolverResult::Sat);
3019    }
3020
3021    #[test]
3022    fn test_boolean_encoding_implies() {
3023        let mut solver = Solver::new();
3024        let mut manager = TermManager::new();
3025
3026        // Test: (p => q) and p and (not q) should be UNSAT
3027        let p = manager.mk_var("p", manager.sorts.bool_sort);
3028        let q = manager.mk_var("q", manager.sorts.bool_sort);
3029        let implies = manager.mk_implies(p, q);
3030        let not_q = manager.mk_not(q);
3031
3032        solver.assert(implies, &mut manager);
3033        solver.assert(p, &mut manager);
3034        solver.assert(not_q, &mut manager);
3035        assert_eq!(solver.check(&mut manager), SolverResult::Unsat);
3036    }
3037
3038    #[test]
3039    fn test_boolean_encoding_distinct() {
3040        let mut solver = Solver::new();
3041        let mut manager = TermManager::new();
3042
3043        // Test: distinct(p, q, r) and p and q should be UNSAT (since p=q)
3044        let p = manager.mk_var("p", manager.sorts.bool_sort);
3045        let q = manager.mk_var("q", manager.sorts.bool_sort);
3046        let r = manager.mk_var("r", manager.sorts.bool_sort);
3047        let distinct = manager.mk_distinct(vec![p, q, r]);
3048
3049        solver.assert(distinct, &mut manager);
3050        solver.assert(p, &mut manager);
3051        solver.assert(q, &mut manager);
3052        assert_eq!(solver.check(&mut manager), SolverResult::Unsat);
3053    }
3054
3055    #[test]
3056    fn test_model_evaluation_bool() {
3057        let mut solver = Solver::new();
3058        let mut manager = TermManager::new();
3059
3060        let p = manager.mk_var("p", manager.sorts.bool_sort);
3061        let q = manager.mk_var("q", manager.sorts.bool_sort);
3062
3063        // Assert p and not q
3064        solver.assert(p, &mut manager);
3065        solver.assert(manager.mk_not(q), &mut manager);
3066        assert_eq!(solver.check(&mut manager), SolverResult::Sat);
3067
3068        let model = solver.model().expect("Should have model");
3069
3070        // Evaluate p (should be true)
3071        let p_val = model.eval(p, &mut manager);
3072        assert_eq!(p_val, manager.mk_true());
3073
3074        // Evaluate q (should be false)
3075        let q_val = model.eval(q, &mut manager);
3076        assert_eq!(q_val, manager.mk_false());
3077
3078        // Evaluate (p and q) - should be false
3079        let and_term = manager.mk_and(vec![p, q]);
3080        let and_val = model.eval(and_term, &mut manager);
3081        assert_eq!(and_val, manager.mk_false());
3082
3083        // Evaluate (p or q) - should be true
3084        let or_term = manager.mk_or(vec![p, q]);
3085        let or_val = model.eval(or_term, &mut manager);
3086        assert_eq!(or_val, manager.mk_true());
3087    }
3088
3089    #[test]
3090    fn test_model_evaluation_ite() {
3091        let mut solver = Solver::new();
3092        let mut manager = TermManager::new();
3093
3094        let p = manager.mk_var("p", manager.sorts.bool_sort);
3095        let q = manager.mk_var("q", manager.sorts.bool_sort);
3096        let r = manager.mk_var("r", manager.sorts.bool_sort);
3097
3098        // Assert p
3099        solver.assert(p, &mut manager);
3100        assert_eq!(solver.check(&mut manager), SolverResult::Sat);
3101
3102        let model = solver.model().expect("Should have model");
3103
3104        // Evaluate (ite p q r) - should evaluate to q since p is true
3105        let ite_term = manager.mk_ite(p, q, r);
3106        let ite_val = model.eval(ite_term, &mut manager);
3107        // The result should be q's value (whatever it is in the model)
3108        let q_val = model.eval(q, &mut manager);
3109        assert_eq!(ite_val, q_val);
3110    }
3111
3112    #[test]
3113    fn test_model_evaluation_implies() {
3114        let mut solver = Solver::new();
3115        let mut manager = TermManager::new();
3116
3117        let p = manager.mk_var("p", manager.sorts.bool_sort);
3118        let q = manager.mk_var("q", manager.sorts.bool_sort);
3119
3120        // Assert not p
3121        solver.assert(manager.mk_not(p), &mut manager);
3122        assert_eq!(solver.check(&mut manager), SolverResult::Sat);
3123
3124        let model = solver.model().expect("Should have model");
3125
3126        // Evaluate (p => q) - should be true since p is false
3127        let implies_term = manager.mk_implies(p, q);
3128        let implies_val = model.eval(implies_term, &mut manager);
3129        assert_eq!(implies_val, manager.mk_true());
3130    }
3131
3132    #[test]
3133    fn test_arithmetic_model_generation() {
3134        use num_bigint::BigInt;
3135
3136        let mut solver = Solver::new();
3137        let mut manager = TermManager::new();
3138
3139        // Create integer variables
3140        let x = manager.mk_var("x", manager.sorts.int_sort);
3141        let y = manager.mk_var("y", manager.sorts.int_sort);
3142
3143        // Create constraints: x + y = 10, x >= 0, y >= 0
3144        let ten = manager.mk_int(BigInt::from(10));
3145        let zero = manager.mk_int(BigInt::from(0));
3146        let sum = manager.mk_add(vec![x, y]);
3147
3148        let eq = manager.mk_eq(sum, ten);
3149        let x_ge_0 = manager.mk_ge(x, zero);
3150        let y_ge_0 = manager.mk_ge(y, zero);
3151
3152        solver.assert(eq, &mut manager);
3153        solver.assert(x_ge_0, &mut manager);
3154        solver.assert(y_ge_0, &mut manager);
3155
3156        assert_eq!(solver.check(&mut manager), SolverResult::Sat);
3157
3158        // Check that we can get a model (even if the arithmetic values aren't fully computed yet)
3159        let model = solver.model();
3160        assert!(model.is_some(), "Should have a model for SAT result");
3161    }
3162
3163    #[test]
3164    fn test_model_pretty_print() {
3165        let mut solver = Solver::new();
3166        let mut manager = TermManager::new();
3167
3168        let p = manager.mk_var("p", manager.sorts.bool_sort);
3169        let q = manager.mk_var("q", manager.sorts.bool_sort);
3170
3171        solver.assert(p, &mut manager);
3172        solver.assert(manager.mk_not(q), &mut manager);
3173        assert_eq!(solver.check(&mut manager), SolverResult::Sat);
3174
3175        let model = solver.model().expect("Should have model");
3176        let pretty = model.pretty_print(&manager);
3177
3178        // Should contain the model structure
3179        assert!(pretty.contains("(model"));
3180        assert!(pretty.contains("define-fun"));
3181        // Should contain variable names
3182        assert!(pretty.contains("p") || pretty.contains("q"));
3183    }
3184
3185    #[test]
3186    fn test_trail_based_undo_assertions() {
3187        let mut solver = Solver::new();
3188        let mut manager = TermManager::new();
3189
3190        let p = manager.mk_var("p", manager.sorts.bool_sort);
3191        let q = manager.mk_var("q", manager.sorts.bool_sort);
3192
3193        // Initial state
3194        assert_eq!(solver.assertions.len(), 0);
3195        assert_eq!(solver.trail.len(), 0);
3196
3197        // Assert p
3198        solver.assert(p, &mut manager);
3199        assert_eq!(solver.assertions.len(), 1);
3200        assert!(!solver.trail.is_empty());
3201
3202        // Push and assert q
3203        solver.push();
3204        let trail_len_after_push = solver.trail.len();
3205        solver.assert(q, &mut manager);
3206        assert_eq!(solver.assertions.len(), 2);
3207        assert!(solver.trail.len() > trail_len_after_push);
3208
3209        // Pop should undo the second assertion
3210        solver.pop();
3211        assert_eq!(solver.assertions.len(), 1);
3212        assert_eq!(solver.assertions[0], p);
3213    }
3214
3215    #[test]
3216    fn test_trail_based_undo_variables() {
3217        let mut solver = Solver::new();
3218        let mut manager = TermManager::new();
3219
3220        let p = manager.mk_var("p", manager.sorts.bool_sort);
3221        let q = manager.mk_var("q", manager.sorts.bool_sort);
3222
3223        // Assert p creates variables
3224        solver.assert(p, &mut manager);
3225        let initial_var_count = solver.term_to_var.len();
3226
3227        // Push and assert q
3228        solver.push();
3229        solver.assert(q, &mut manager);
3230        assert!(solver.term_to_var.len() >= initial_var_count);
3231
3232        // Pop should remove q's variable
3233        solver.pop();
3234        // Note: Some variables may remain due to encoding, but q should be removed
3235        assert_eq!(solver.assertions.len(), 1);
3236    }
3237
3238    #[test]
3239    fn test_trail_based_undo_constraints() {
3240        use num_bigint::BigInt;
3241
3242        let mut solver = Solver::new();
3243        let mut manager = TermManager::new();
3244
3245        let x = manager.mk_var("x", manager.sorts.int_sort);
3246        let zero = manager.mk_int(BigInt::from(0));
3247
3248        // Assert x >= 0 creates a constraint
3249        let c1 = manager.mk_ge(x, zero);
3250        solver.assert(c1, &mut manager);
3251        let initial_constraint_count = solver.var_to_constraint.len();
3252
3253        // Push and add another constraint
3254        solver.push();
3255        let ten = manager.mk_int(BigInt::from(10));
3256        let c2 = manager.mk_le(x, ten);
3257        solver.assert(c2, &mut manager);
3258        assert!(solver.var_to_constraint.len() >= initial_constraint_count);
3259
3260        // Pop should remove the second constraint
3261        solver.pop();
3262        assert_eq!(solver.var_to_constraint.len(), initial_constraint_count);
3263    }
3264
3265    #[test]
3266    fn test_trail_based_undo_false_assertion() {
3267        let mut solver = Solver::new();
3268        let mut manager = TermManager::new();
3269
3270        assert!(!solver.has_false_assertion);
3271
3272        solver.push();
3273        solver.assert(manager.mk_false(), &mut manager);
3274        assert!(solver.has_false_assertion);
3275
3276        solver.pop();
3277        assert!(!solver.has_false_assertion);
3278    }
3279
3280    #[test]
3281    fn test_trail_based_undo_named_assertions() {
3282        let mut solver = Solver::new();
3283        let mut manager = TermManager::new();
3284        solver.set_produce_unsat_cores(true);
3285
3286        let p = manager.mk_var("p", manager.sorts.bool_sort);
3287
3288        solver.assert_named(p, "assertion1", &mut manager);
3289        assert_eq!(solver.named_assertions.len(), 1);
3290
3291        solver.push();
3292        let q = manager.mk_var("q", manager.sorts.bool_sort);
3293        solver.assert_named(q, "assertion2", &mut manager);
3294        assert_eq!(solver.named_assertions.len(), 2);
3295
3296        solver.pop();
3297        assert_eq!(solver.named_assertions.len(), 1);
3298        assert_eq!(
3299            solver.named_assertions[0].name,
3300            Some("assertion1".to_string())
3301        );
3302    }
3303
3304    #[test]
3305    fn test_trail_based_undo_nested_push_pop() {
3306        let mut solver = Solver::new();
3307        let mut manager = TermManager::new();
3308
3309        let p = manager.mk_var("p", manager.sorts.bool_sort);
3310        solver.assert(p, &mut manager);
3311
3312        // First push
3313        solver.push();
3314        let q = manager.mk_var("q", manager.sorts.bool_sort);
3315        solver.assert(q, &mut manager);
3316        assert_eq!(solver.assertions.len(), 2);
3317
3318        // Second push
3319        solver.push();
3320        let r = manager.mk_var("r", manager.sorts.bool_sort);
3321        solver.assert(r, &mut manager);
3322        assert_eq!(solver.assertions.len(), 3);
3323
3324        // Pop once
3325        solver.pop();
3326        assert_eq!(solver.assertions.len(), 2);
3327
3328        // Pop again
3329        solver.pop();
3330        assert_eq!(solver.assertions.len(), 1);
3331        assert_eq!(solver.assertions[0], p);
3332    }
3333
3334    #[test]
3335    fn test_config_presets() {
3336        // Test that all presets can be created without panicking
3337        let _fast = SolverConfig::fast();
3338        let _balanced = SolverConfig::balanced();
3339        let _thorough = SolverConfig::thorough();
3340        let _minimal = SolverConfig::minimal();
3341    }
3342
3343    #[test]
3344    fn test_config_fast_characteristics() {
3345        let config = SolverConfig::fast();
3346
3347        // Fast config should disable expensive features
3348        assert!(!config.enable_variable_elimination);
3349        assert!(!config.enable_blocked_clause_elimination);
3350        assert!(!config.enable_symmetry_breaking);
3351        assert!(!config.enable_inprocessing);
3352        assert!(!config.enable_clause_subsumption);
3353
3354        // But keep fast optimizations
3355        assert!(config.enable_clause_minimization);
3356        assert!(config.simplify);
3357
3358        // Should use Geometric restarts (faster)
3359        assert_eq!(config.restart_strategy, RestartStrategy::Geometric);
3360    }
3361
3362    #[test]
3363    fn test_config_balanced_characteristics() {
3364        let config = SolverConfig::balanced();
3365
3366        // Balanced should enable most features with moderate settings
3367        assert!(config.enable_variable_elimination);
3368        assert!(config.enable_blocked_clause_elimination);
3369        assert!(config.enable_inprocessing);
3370        assert!(config.enable_clause_minimization);
3371        assert!(config.enable_clause_subsumption);
3372        assert!(config.simplify);
3373
3374        // But not the most expensive one
3375        assert!(!config.enable_symmetry_breaking);
3376
3377        // Should use Glucose restarts (adaptive)
3378        assert_eq!(config.restart_strategy, RestartStrategy::Glucose);
3379
3380        // Conservative limits
3381        assert_eq!(config.variable_elimination_limit, 1000);
3382        assert_eq!(config.inprocessing_interval, 10000);
3383    }
3384
3385    #[test]
3386    fn test_config_thorough_characteristics() {
3387        let config = SolverConfig::thorough();
3388
3389        // Thorough should enable all features
3390        assert!(config.enable_variable_elimination);
3391        assert!(config.enable_blocked_clause_elimination);
3392        assert!(config.enable_symmetry_breaking); // Even this expensive one
3393        assert!(config.enable_inprocessing);
3394        assert!(config.enable_clause_minimization);
3395        assert!(config.enable_clause_subsumption);
3396        assert!(config.simplify);
3397
3398        // Aggressive settings
3399        assert_eq!(config.variable_elimination_limit, 5000);
3400        assert_eq!(config.inprocessing_interval, 5000);
3401    }
3402
3403    #[test]
3404    fn test_config_minimal_characteristics() {
3405        let config = SolverConfig::minimal();
3406
3407        // Minimal should disable everything optional
3408        assert!(!config.simplify);
3409        assert!(!config.enable_variable_elimination);
3410        assert!(!config.enable_blocked_clause_elimination);
3411        assert!(!config.enable_symmetry_breaking);
3412        assert!(!config.enable_inprocessing);
3413        assert!(!config.enable_clause_minimization);
3414        assert!(!config.enable_clause_subsumption);
3415
3416        // Should use lazy theory mode for minimal overhead
3417        assert_eq!(config.theory_mode, TheoryMode::Lazy);
3418
3419        // Single threaded
3420        assert_eq!(config.num_threads, 1);
3421    }
3422
3423    #[test]
3424    fn test_config_builder_pattern() {
3425        // Test the builder-style methods
3426        let config = SolverConfig::fast()
3427            .with_proof()
3428            .with_timeout(5000)
3429            .with_max_conflicts(1000)
3430            .with_max_decisions(2000)
3431            .with_parallel(8)
3432            .with_restart_strategy(RestartStrategy::Luby)
3433            .with_theory_mode(TheoryMode::Lazy);
3434
3435        assert!(config.proof);
3436        assert_eq!(config.timeout_ms, 5000);
3437        assert_eq!(config.max_conflicts, 1000);
3438        assert_eq!(config.max_decisions, 2000);
3439        assert!(config.parallel);
3440        assert_eq!(config.num_threads, 8);
3441        assert_eq!(config.restart_strategy, RestartStrategy::Luby);
3442        assert_eq!(config.theory_mode, TheoryMode::Lazy);
3443    }
3444
3445    #[test]
3446    fn test_solver_with_different_configs() {
3447        let mut manager = TermManager::new();
3448
3449        // Create solvers with different configs
3450        let mut solver_fast = Solver::with_config(SolverConfig::fast());
3451        let mut solver_balanced = Solver::with_config(SolverConfig::balanced());
3452        let mut solver_thorough = Solver::with_config(SolverConfig::thorough());
3453        let mut solver_minimal = Solver::with_config(SolverConfig::minimal());
3454
3455        // They should all solve a simple problem correctly
3456        let t = manager.mk_true();
3457        solver_fast.assert(t, &mut manager);
3458        solver_balanced.assert(t, &mut manager);
3459        solver_thorough.assert(t, &mut manager);
3460        solver_minimal.assert(t, &mut manager);
3461
3462        assert_eq!(solver_fast.check(&mut manager), SolverResult::Sat);
3463        assert_eq!(solver_balanced.check(&mut manager), SolverResult::Sat);
3464        assert_eq!(solver_thorough.check(&mut manager), SolverResult::Sat);
3465        assert_eq!(solver_minimal.check(&mut manager), SolverResult::Sat);
3466    }
3467
3468    #[test]
3469    fn test_config_default_is_balanced() {
3470        let default = SolverConfig::default();
3471        let balanced = SolverConfig::balanced();
3472
3473        // Default should be the same as balanced
3474        assert_eq!(
3475            default.enable_variable_elimination,
3476            balanced.enable_variable_elimination
3477        );
3478        assert_eq!(
3479            default.enable_clause_minimization,
3480            balanced.enable_clause_minimization
3481        );
3482        assert_eq!(
3483            default.enable_symmetry_breaking,
3484            balanced.enable_symmetry_breaking
3485        );
3486        assert_eq!(default.restart_strategy, balanced.restart_strategy);
3487    }
3488
3489    #[test]
3490    fn test_theory_combination_arith_solver() {
3491        use oxiz_theories::arithmetic::ArithSolver;
3492        use oxiz_theories::{EqualityNotification, TheoryCombination};
3493
3494        let mut arith = ArithSolver::lra();
3495        let mut manager = TermManager::new();
3496
3497        // Create two arithmetic variables
3498        let x = manager.mk_var("x", manager.sorts.int_sort);
3499        let y = manager.mk_var("y", manager.sorts.int_sort);
3500
3501        // Intern them in the arithmetic solver
3502        let _x_var = arith.intern(x);
3503        let _y_var = arith.intern(y);
3504
3505        // Test notify_equality with relevant terms
3506        let eq_notification = EqualityNotification {
3507            lhs: x,
3508            rhs: y,
3509            reason: None,
3510        };
3511
3512        let accepted = arith.notify_equality(eq_notification);
3513        assert!(
3514            accepted,
3515            "ArithSolver should accept equality notification for known terms"
3516        );
3517
3518        // Test is_relevant
3519        assert!(
3520            arith.is_relevant(x),
3521            "x should be relevant to arithmetic solver"
3522        );
3523        assert!(
3524            arith.is_relevant(y),
3525            "y should be relevant to arithmetic solver"
3526        );
3527
3528        // Test with unknown term
3529        let z = manager.mk_var("z", manager.sorts.int_sort);
3530        assert!(
3531            !arith.is_relevant(z),
3532            "z should not be relevant (not interned)"
3533        );
3534
3535        // Test notify_equality with unknown terms
3536        let eq_unknown = EqualityNotification {
3537            lhs: x,
3538            rhs: z,
3539            reason: None,
3540        };
3541        let accepted_unknown = arith.notify_equality(eq_unknown);
3542        assert!(
3543            !accepted_unknown,
3544            "ArithSolver should reject equality with unknown term"
3545        );
3546    }
3547
3548    #[test]
3549    fn test_theory_combination_get_shared_equalities() {
3550        use oxiz_theories::TheoryCombination;
3551        use oxiz_theories::arithmetic::ArithSolver;
3552
3553        let arith = ArithSolver::lra();
3554
3555        // Test get_shared_equalities
3556        let shared = arith.get_shared_equalities();
3557        assert!(
3558            shared.is_empty(),
3559            "ArithSolver should return empty shared equalities (placeholder)"
3560        );
3561    }
3562
3563    #[test]
3564    fn test_equality_notification_fields() {
3565        use oxiz_theories::EqualityNotification;
3566
3567        let mut manager = TermManager::new();
3568        let x = manager.mk_var("x", manager.sorts.int_sort);
3569        let y = manager.mk_var("y", manager.sorts.int_sort);
3570
3571        // Test with reason
3572        let eq1 = EqualityNotification {
3573            lhs: x,
3574            rhs: y,
3575            reason: Some(x),
3576        };
3577        assert_eq!(eq1.lhs, x);
3578        assert_eq!(eq1.rhs, y);
3579        assert_eq!(eq1.reason, Some(x));
3580
3581        // Test without reason
3582        let eq2 = EqualityNotification {
3583            lhs: x,
3584            rhs: y,
3585            reason: None,
3586        };
3587        assert_eq!(eq2.reason, None);
3588
3589        // Test equality and cloning
3590        let eq3 = eq1;
3591        assert_eq!(eq3.lhs, eq1.lhs);
3592        assert_eq!(eq3.rhs, eq1.rhs);
3593    }
3594
3595    #[test]
3596    fn test_assert_many() {
3597        let mut solver = Solver::new();
3598        let mut manager = TermManager::new();
3599
3600        let p = manager.mk_var("p", manager.sorts.bool_sort);
3601        let q = manager.mk_var("q", manager.sorts.bool_sort);
3602        let r = manager.mk_var("r", manager.sorts.bool_sort);
3603
3604        // Assert multiple terms at once
3605        solver.assert_many(&[p, q, r], &mut manager);
3606
3607        assert_eq!(solver.num_assertions(), 3);
3608        assert_eq!(solver.check(&mut manager), SolverResult::Sat);
3609    }
3610
3611    #[test]
3612    fn test_num_assertions_and_variables() {
3613        let mut solver = Solver::new();
3614        let mut manager = TermManager::new();
3615
3616        assert_eq!(solver.num_assertions(), 0);
3617        assert!(!solver.has_assertions());
3618
3619        let p = manager.mk_var("p", manager.sorts.bool_sort);
3620        let q = manager.mk_var("q", manager.sorts.bool_sort);
3621
3622        solver.assert(p, &mut manager);
3623        assert_eq!(solver.num_assertions(), 1);
3624        assert!(solver.has_assertions());
3625
3626        solver.assert(q, &mut manager);
3627        assert_eq!(solver.num_assertions(), 2);
3628
3629        // Variables are created during encoding
3630        assert!(solver.num_variables() > 0);
3631    }
3632
3633    #[test]
3634    fn test_context_level() {
3635        let mut solver = Solver::new();
3636
3637        assert_eq!(solver.context_level(), 0);
3638
3639        solver.push();
3640        assert_eq!(solver.context_level(), 1);
3641
3642        solver.push();
3643        assert_eq!(solver.context_level(), 2);
3644
3645        solver.pop();
3646        assert_eq!(solver.context_level(), 1);
3647
3648        solver.pop();
3649        assert_eq!(solver.context_level(), 0);
3650    }
3651
3652    // ===== Quantifier Tests =====
3653
3654    #[test]
3655    fn test_quantifier_basic_forall() {
3656        let mut solver = Solver::new();
3657        let mut manager = TermManager::new();
3658        let bool_sort = manager.sorts.bool_sort;
3659
3660        // Create: forall x. P(x)
3661        // This asserts P holds for all x
3662        let x = manager.mk_var("x", bool_sort);
3663        let p_x = manager.mk_apply("P", [x], bool_sort);
3664        let forall = manager.mk_forall([("x", bool_sort)], p_x);
3665
3666        solver.assert(forall, &mut manager);
3667
3668        // The solver should handle the quantifier (may return sat, unknown, or use MBQI)
3669        let result = solver.check(&mut manager);
3670        // Quantifiers without ground terms typically return sat (trivially satisfied)
3671        assert!(
3672            result == SolverResult::Sat || result == SolverResult::Unknown,
3673            "Basic forall should not be unsat"
3674        );
3675    }
3676
3677    #[test]
3678    fn test_quantifier_basic_exists() {
3679        let mut solver = Solver::new();
3680        let mut manager = TermManager::new();
3681        let bool_sort = manager.sorts.bool_sort;
3682
3683        // Create: exists x. P(x)
3684        let x = manager.mk_var("x", bool_sort);
3685        let p_x = manager.mk_apply("P", [x], bool_sort);
3686        let exists = manager.mk_exists([("x", bool_sort)], p_x);
3687
3688        solver.assert(exists, &mut manager);
3689
3690        let result = solver.check(&mut manager);
3691        assert!(
3692            result == SolverResult::Sat || result == SolverResult::Unknown,
3693            "Basic exists should not be unsat"
3694        );
3695    }
3696
3697    #[test]
3698    fn test_quantifier_with_ground_terms() {
3699        let mut solver = Solver::new();
3700        let mut manager = TermManager::new();
3701        let int_sort = manager.sorts.int_sort;
3702        let bool_sort = manager.sorts.bool_sort;
3703
3704        // Create ground terms for instantiation
3705        let zero = manager.mk_int(0);
3706        let one = manager.mk_int(1);
3707
3708        // P(0) = true and P(1) = true
3709        let p_0 = manager.mk_apply("P", [zero], bool_sort);
3710        let p_1 = manager.mk_apply("P", [one], bool_sort);
3711        solver.assert(p_0, &mut manager);
3712        solver.assert(p_1, &mut manager);
3713
3714        // forall x. P(x) - should be satisfiable with the given ground terms
3715        let x = manager.mk_var("x", int_sort);
3716        let p_x = manager.mk_apply("P", [x], bool_sort);
3717        let forall = manager.mk_forall([("x", int_sort)], p_x);
3718        solver.assert(forall, &mut manager);
3719
3720        let result = solver.check(&mut manager);
3721        // MBQI should find that P(0) and P(1) satisfy the quantifier
3722        assert!(
3723            result == SolverResult::Sat || result == SolverResult::Unknown,
3724            "Quantifier with matching ground terms should be satisfiable"
3725        );
3726    }
3727
3728    #[test]
3729    fn test_quantifier_instantiation() {
3730        let mut solver = Solver::new();
3731        let mut manager = TermManager::new();
3732        let int_sort = manager.sorts.int_sort;
3733        let bool_sort = manager.sorts.bool_sort;
3734
3735        // Create a ground term
3736        let c = manager.mk_apply("c", [], int_sort);
3737
3738        // Assert: forall x. f(x) > 0
3739        let x = manager.mk_var("x", int_sort);
3740        let f_x = manager.mk_apply("f", [x], int_sort);
3741        let zero = manager.mk_int(0);
3742        let f_x_gt_0 = manager.mk_gt(f_x, zero);
3743        let forall = manager.mk_forall([("x", int_sort)], f_x_gt_0);
3744        solver.assert(forall, &mut manager);
3745
3746        // Assert: f(c) exists (provides instantiation candidate)
3747        let f_c = manager.mk_apply("f", [c], int_sort);
3748        let f_c_exists = manager.mk_apply("exists_f_c", [f_c], bool_sort);
3749        solver.assert(f_c_exists, &mut manager);
3750
3751        let result = solver.check(&mut manager);
3752        // MBQI should instantiate the quantifier with c
3753        assert!(
3754            result == SolverResult::Sat || result == SolverResult::Unknown,
3755            "Quantifier instantiation test"
3756        );
3757    }
3758
3759    #[test]
3760    fn test_quantifier_mbqi_solver_integration() {
3761        use crate::mbqi::MBQISolver;
3762
3763        let mut mbqi = MBQISolver::new();
3764        let mut manager = TermManager::new();
3765        let int_sort = manager.sorts.int_sort;
3766
3767        // Create a universal quantifier
3768        let x = manager.mk_var("x", int_sort);
3769        let zero = manager.mk_int(0);
3770        let x_gt_0 = manager.mk_gt(x, zero);
3771        let forall = manager.mk_forall([("x", int_sort)], x_gt_0);
3772
3773        // Add the quantifier to MBQI
3774        mbqi.add_quantifier(forall, &manager);
3775
3776        // Add some candidate terms
3777        let one = manager.mk_int(1);
3778        let two = manager.mk_int(2);
3779        mbqi.add_candidate(one, int_sort);
3780        mbqi.add_candidate(two, int_sort);
3781
3782        // Check that MBQI tracks the quantifier
3783        assert!(mbqi.is_enabled(), "MBQI should be enabled by default");
3784    }
3785
3786    #[test]
3787    fn test_quantifier_pattern_matching() {
3788        let mut solver = Solver::new();
3789        let mut manager = TermManager::new();
3790        let int_sort = manager.sorts.int_sort;
3791
3792        // Create: forall x. (f(x) = g(x)) with pattern f(x)
3793        let x = manager.mk_var("x", int_sort);
3794        let f_x = manager.mk_apply("f", [x], int_sort);
3795        let g_x = manager.mk_apply("g", [x], int_sort);
3796        let body = manager.mk_eq(f_x, g_x);
3797
3798        // Create pattern
3799        let pattern: smallvec::SmallVec<[_; 2]> = smallvec::smallvec![f_x];
3800        let patterns: smallvec::SmallVec<[_; 2]> = smallvec::smallvec![pattern];
3801
3802        let forall = manager.mk_forall_with_patterns([("x", int_sort)], body, patterns);
3803        solver.assert(forall, &mut manager);
3804
3805        // Add ground term f(c) to trigger pattern matching
3806        let c = manager.mk_apply("c", [], int_sort);
3807        let f_c = manager.mk_apply("f", [c], int_sort);
3808        let f_c_eq_c = manager.mk_eq(f_c, c);
3809        solver.assert(f_c_eq_c, &mut manager);
3810
3811        let result = solver.check(&mut manager);
3812        assert!(
3813            result == SolverResult::Sat || result == SolverResult::Unknown,
3814            "Pattern matching should allow instantiation"
3815        );
3816    }
3817
3818    #[test]
3819    fn test_quantifier_multiple() {
3820        let mut solver = Solver::new();
3821        let mut manager = TermManager::new();
3822        let int_sort = manager.sorts.int_sort;
3823
3824        // Create: forall x. forall y. x + y = y + x (commutativity)
3825        let x = manager.mk_var("x", int_sort);
3826        let y = manager.mk_var("y", int_sort);
3827        let x_plus_y = manager.mk_add([x, y]);
3828        let y_plus_x = manager.mk_add([y, x]);
3829        let commutative = manager.mk_eq(x_plus_y, y_plus_x);
3830
3831        let inner_forall = manager.mk_forall([("y", int_sort)], commutative);
3832        let outer_forall = manager.mk_forall([("x", int_sort)], inner_forall);
3833
3834        solver.assert(outer_forall, &mut manager);
3835
3836        let result = solver.check(&mut manager);
3837        assert!(
3838            result == SolverResult::Sat || result == SolverResult::Unknown,
3839            "Nested forall should be handled"
3840        );
3841    }
3842
3843    #[test]
3844    fn test_quantifier_with_model() {
3845        let mut solver = Solver::new();
3846        let mut manager = TermManager::new();
3847        let bool_sort = manager.sorts.bool_sort;
3848
3849        // Simple satisfiable formula with quantifier
3850        let p = manager.mk_var("p", bool_sort);
3851        solver.assert(p, &mut manager);
3852
3853        // Add a trivial quantifier (x OR NOT x is always true)
3854        let x = manager.mk_var("x", bool_sort);
3855        let not_x = manager.mk_not(x);
3856        let x_or_not_x = manager.mk_or([x, not_x]);
3857        let tautology = manager.mk_forall([("x", bool_sort)], x_or_not_x);
3858        solver.assert(tautology, &mut manager);
3859
3860        let result = solver.check(&mut manager);
3861        assert_eq!(
3862            result,
3863            SolverResult::Sat,
3864            "Tautology in quantifier should be SAT"
3865        );
3866
3867        // Check that we can get a model
3868        if let Some(model) = solver.model() {
3869            assert!(model.size() > 0, "Model should have assignments");
3870        }
3871    }
3872
3873    #[test]
3874    fn test_quantifier_push_pop() {
3875        let mut solver = Solver::new();
3876        let mut manager = TermManager::new();
3877        let int_sort = manager.sorts.int_sort;
3878
3879        // Assert base formula
3880        let x = manager.mk_var("x", int_sort);
3881        let zero = manager.mk_int(0);
3882        let x_gt_0 = manager.mk_gt(x, zero);
3883        let forall = manager.mk_forall([("x", int_sort)], x_gt_0);
3884
3885        solver.push();
3886        solver.assert(forall, &mut manager);
3887
3888        let result1 = solver.check(&mut manager);
3889        // forall x. x > 0 is invalid (counterexample: x = 0 or x = -1)
3890        // So the solver should return Unsat or Unknown
3891        assert!(
3892            result1 == SolverResult::Unsat || result1 == SolverResult::Unknown,
3893            "forall x. x > 0 should be Unsat or Unknown, got {:?}",
3894            result1
3895        );
3896
3897        solver.pop();
3898
3899        // After pop, the quantifier assertion should be gone
3900        let result2 = solver.check(&mut manager);
3901        assert_eq!(
3902            result2,
3903            SolverResult::Sat,
3904            "After pop, should be trivially SAT"
3905        );
3906    }
3907
3908    /// Test that integer contradictions are correctly detected as UNSAT.
3909    ///
3910    /// NOTE: This test currently fails because arithmetic constraints (Ge, Lt, etc.)
3911    /// are not fully integrated with the simplex solver. The `process_constraint` function
3912    /// in solver.rs has a TODO to implement this. Once arithmetic constraint encoding
3913    /// is complete, this test should pass and the ignore attribute should be removed.
3914    #[test]
3915    #[ignore = "Requires complete arithmetic constraint encoding to simplex (see process_constraint in solver.rs)"]
3916    fn test_integer_contradiction_unsat() {
3917        use num_bigint::BigInt;
3918
3919        let mut solver = Solver::new();
3920        let mut manager = TermManager::new();
3921
3922        // Create integer variable x
3923        let x = manager.mk_var("x", manager.sorts.int_sort);
3924        let zero = manager.mk_int(BigInt::from(0));
3925
3926        // Assert x >= 0
3927        let x_ge_0 = manager.mk_ge(x, zero);
3928        solver.assert(x_ge_0, &mut manager);
3929
3930        // Assert x < 0 (contradicts x >= 0)
3931        let x_lt_0 = manager.mk_lt(x, zero);
3932        solver.assert(x_lt_0, &mut manager);
3933
3934        // Should be UNSAT because x cannot be both >= 0 and < 0
3935        let result = solver.check(&mut manager);
3936        assert_eq!(
3937            result,
3938            SolverResult::Unsat,
3939            "x >= 0 AND x < 0 should be UNSAT"
3940        );
3941    }
3942}