oxiz_solver/
solver.rs

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