oxiz_solver/
mbqi.rs

1//! Model-Based Quantifier Instantiation (MBQI)
2//!
3//! This module implements MBQI for handling universally quantified formulas.
4//! The algorithm works by:
5//!
6//! 1. Getting a partial model M from the main solver
7//! 2. For each universal quantifier ∀x.φ(x): specialize φ with M
8//! 3. Search for a counterexample x' where ¬φ(x') holds under M
9//! 4. If found: add instantiation φ(x') as a lemma
10//! 5. Repeat until no counterexamples are found
11//!
12//! # References
13//!
14//! - Ge, Y., & de Moura, L. (2009). Complete instantiation for quantified formulas
15//!   in satisfiability modulo theories.
16
17use lasso::Spur;
18use oxiz_core::ast::{TermId, TermKind, TermManager};
19use oxiz_core::sort::SortId;
20use rustc_hash::{FxHashMap, FxHashSet};
21use smallvec::SmallVec;
22
23/// A quantified formula tracked by MBQI
24#[derive(Debug, Clone)]
25pub struct QuantifiedFormula {
26    /// The original quantified term
27    pub term: TermId,
28    /// Bound variables (name, sort)
29    pub bound_vars: SmallVec<[(Spur, SortId); 2]>,
30    /// The body of the quantifier
31    pub body: TermId,
32    /// Whether this is universal (true) or existential (false)
33    pub universal: bool,
34    /// Number of times this quantifier has been instantiated
35    pub instantiation_count: usize,
36    /// Maximum allowed instantiations
37    pub max_instantiations: usize,
38}
39
40impl QuantifiedFormula {
41    /// Create a new tracked quantified formula
42    pub fn new(
43        term: TermId,
44        bound_vars: SmallVec<[(Spur, SortId); 2]>,
45        body: TermId,
46        universal: bool,
47    ) -> Self {
48        Self {
49            term,
50            bound_vars,
51            body,
52            universal,
53            instantiation_count: 0,
54            max_instantiations: 100,
55        }
56    }
57
58    /// Check if we can instantiate more
59    pub fn can_instantiate(&self) -> bool {
60        self.instantiation_count < self.max_instantiations
61    }
62}
63
64/// An instantiation of a quantified formula
65#[derive(Debug, Clone)]
66pub struct Instantiation {
67    /// The quantifier that was instantiated
68    pub quantifier: TermId,
69    /// The substitution used (variable name -> term)
70    pub substitution: FxHashMap<Spur, TermId>,
71    /// The resulting ground term (body with substitution applied)
72    pub result: TermId,
73}
74
75/// Result of MBQI check
76#[derive(Debug, Clone)]
77pub enum MBQIResult {
78    /// No quantifiers to process
79    NoQuantifiers,
80    /// All quantifiers satisfied under the model
81    Satisfied,
82    /// Found new instantiations to add
83    NewInstantiations(Vec<Instantiation>),
84    /// Found a conflict (quantifier cannot be satisfied)
85    Conflict(Vec<TermId>),
86    /// Reached instantiation limit
87    InstantiationLimit,
88}
89
90/// Model-Based Quantifier Instantiation solver
91#[derive(Debug)]
92pub struct MBQISolver {
93    /// Tracked quantified formulas
94    quantifiers: Vec<QuantifiedFormula>,
95    /// Generated instantiations (for deduplication)
96    generated_instantiations: FxHashSet<(TermId, Vec<(Spur, TermId)>)>,
97    /// Candidate terms by sort (for instantiation)
98    candidates_by_sort: FxHashMap<SortId, Vec<TermId>>,
99    /// Maximum total instantiations
100    max_total_instantiations: usize,
101    /// Current total instantiation count
102    total_instantiation_count: usize,
103    /// Whether MBQI is enabled
104    enabled: bool,
105}
106
107impl Default for MBQISolver {
108    fn default() -> Self {
109        Self::new()
110    }
111}
112
113impl MBQISolver {
114    /// Create a new MBQI solver
115    pub fn new() -> Self {
116        Self {
117            quantifiers: Vec::new(),
118            generated_instantiations: FxHashSet::default(),
119            candidates_by_sort: FxHashMap::default(),
120            max_total_instantiations: 10000,
121            total_instantiation_count: 0,
122            enabled: true,
123        }
124    }
125
126    /// Create with custom instantiation limit
127    pub fn with_limit(max_total: usize) -> Self {
128        let mut solver = Self::new();
129        solver.max_total_instantiations = max_total;
130        solver
131    }
132
133    /// Enable or disable MBQI
134    pub fn set_enabled(&mut self, enabled: bool) {
135        self.enabled = enabled;
136    }
137
138    /// Check if MBQI is enabled
139    pub fn is_enabled(&self) -> bool {
140        self.enabled
141    }
142
143    /// Clear all state
144    pub fn clear(&mut self) {
145        self.quantifiers.clear();
146        self.generated_instantiations.clear();
147        self.candidates_by_sort.clear();
148        self.total_instantiation_count = 0;
149    }
150
151    /// Add a quantified formula to track
152    pub fn add_quantifier(&mut self, term: TermId, manager: &TermManager) {
153        let Some(t) = manager.get(term) else {
154            return;
155        };
156
157        match &t.kind {
158            TermKind::Forall { vars, body, .. } => {
159                self.quantifiers
160                    .push(QuantifiedFormula::new(term, vars.clone(), *body, true));
161            }
162            TermKind::Exists { vars, body, .. } => {
163                // Existentials can be handled by negating and treating as universal
164                // ∃x.φ(x) ≡ ¬∀x.¬φ(x)
165                // For now, just track them
166                self.quantifiers
167                    .push(QuantifiedFormula::new(term, vars.clone(), *body, false));
168            }
169            _ => {}
170        }
171    }
172
173    /// Register a candidate term for instantiation
174    pub fn add_candidate(&mut self, term: TermId, sort: SortId) {
175        self.candidates_by_sort.entry(sort).or_default().push(term);
176    }
177
178    /// Collect ground terms from a formula for use as instantiation candidates
179    pub fn collect_ground_terms(&mut self, term: TermId, manager: &TermManager) {
180        self.collect_ground_terms_rec(term, manager, &mut FxHashSet::default());
181    }
182
183    fn collect_ground_terms_rec(
184        &mut self,
185        term: TermId,
186        manager: &TermManager,
187        visited: &mut FxHashSet<TermId>,
188    ) {
189        if visited.contains(&term) {
190            return;
191        }
192        visited.insert(term);
193
194        let Some(t) = manager.get(term) else {
195            return;
196        };
197
198        // Check if this is a ground term (no quantified variables)
199        // For simplicity, we treat all non-quantifier terms as potential candidates
200        match &t.kind {
201            TermKind::Var(_) => {
202                // Variables are candidates
203                self.add_candidate(term, t.sort);
204            }
205            TermKind::IntConst(_) | TermKind::RealConst(_) | TermKind::BitVecConst { .. } => {
206                self.add_candidate(term, t.sort);
207            }
208            TermKind::Apply { args, .. } => {
209                // Function applications might be good candidates
210                self.add_candidate(term, t.sort);
211                for &arg in args {
212                    self.collect_ground_terms_rec(arg, manager, visited);
213                }
214            }
215            TermKind::Add(args)
216            | TermKind::Mul(args)
217            | TermKind::And(args)
218            | TermKind::Or(args) => {
219                for &arg in args {
220                    self.collect_ground_terms_rec(arg, manager, visited);
221                }
222            }
223            TermKind::Sub(lhs, rhs)
224            | TermKind::Div(lhs, rhs)
225            | TermKind::Mod(lhs, rhs)
226            | TermKind::Eq(lhs, rhs)
227            | TermKind::Lt(lhs, rhs)
228            | TermKind::Le(lhs, rhs)
229            | TermKind::Gt(lhs, rhs)
230            | TermKind::Ge(lhs, rhs) => {
231                self.collect_ground_terms_rec(*lhs, manager, visited);
232                self.collect_ground_terms_rec(*rhs, manager, visited);
233            }
234            TermKind::Not(arg) | TermKind::Neg(arg) => {
235                self.collect_ground_terms_rec(*arg, manager, visited);
236            }
237            TermKind::Ite(cond, then_br, else_br) => {
238                self.collect_ground_terms_rec(*cond, manager, visited);
239                self.collect_ground_terms_rec(*then_br, manager, visited);
240                self.collect_ground_terms_rec(*else_br, manager, visited);
241            }
242            TermKind::Forall { body, .. } | TermKind::Exists { body, .. } => {
243                // Don't descend into quantifier bodies for candidate collection
244                // (those terms are not ground)
245                let _ = body;
246            }
247            _ => {}
248        }
249    }
250
251    /// Get candidates for a given sort
252    pub fn get_candidates(&self, sort: SortId) -> &[TermId] {
253        self.candidates_by_sort
254            .get(&sort)
255            .map_or(&[], |v| v.as_slice())
256    }
257
258    /// Check if we've reached the instantiation limit
259    pub fn at_limit(&self) -> bool {
260        self.total_instantiation_count >= self.max_total_instantiations
261    }
262
263    /// Perform MBQI with the given model
264    ///
265    /// This is the main entry point for MBQI. Given a partial model from the
266    /// main solver, it tries to find counterexamples to universal quantifiers
267    /// and generates instantiation lemmas.
268    pub fn check_with_model(
269        &mut self,
270        model: &FxHashMap<TermId, TermId>,
271        manager: &mut TermManager,
272    ) -> MBQIResult {
273        if !self.enabled {
274            return MBQIResult::NoQuantifiers;
275        }
276
277        if self.quantifiers.is_empty() {
278            return MBQIResult::NoQuantifiers;
279        }
280
281        if self.at_limit() {
282            return MBQIResult::InstantiationLimit;
283        }
284
285        let mut new_instantiations = Vec::new();
286
287        // Process each universal quantifier
288        for i in 0..self.quantifiers.len() {
289            if !self.quantifiers[i].can_instantiate() {
290                continue;
291            }
292
293            if !self.quantifiers[i].universal {
294                // Skip existential quantifiers for now
295                // (they require different handling)
296                continue;
297            }
298
299            // Try to find counterexamples
300            let instantiations = self.find_counterexamples(i, model, manager);
301
302            for inst in instantiations {
303                if self.at_limit() {
304                    break;
305                }
306
307                // Check for duplicates
308                let mut key_vec: Vec<_> = inst.substitution.iter().map(|(&k, &v)| (k, v)).collect();
309                key_vec.sort_by_key(|(k, _)| *k);
310                let key = (inst.quantifier, key_vec);
311
312                if self.generated_instantiations.contains(&key) {
313                    continue;
314                }
315
316                self.generated_instantiations.insert(key);
317                self.quantifiers[i].instantiation_count += 1;
318                self.total_instantiation_count += 1;
319                new_instantiations.push(inst);
320            }
321        }
322
323        if new_instantiations.is_empty() {
324            MBQIResult::Satisfied
325        } else {
326            MBQIResult::NewInstantiations(new_instantiations)
327        }
328    }
329
330    /// Find counterexamples for a quantifier under the given model
331    fn find_counterexamples(
332        &self,
333        quantifier_idx: usize,
334        model: &FxHashMap<TermId, TermId>,
335        manager: &mut TermManager,
336    ) -> Vec<Instantiation> {
337        let quant = &self.quantifiers[quantifier_idx];
338        let mut results = Vec::new();
339
340        // Strategy 1: Try candidates from the term pool
341        let candidates = self.build_candidate_lists(&quant.bound_vars, manager);
342
343        // Generate combinations of candidates
344        let combinations = self.enumerate_combinations(&candidates, 10); // Limit combinations
345
346        for combo in combinations {
347            // Build substitution
348            let mut subst: FxHashMap<Spur, TermId> = FxHashMap::default();
349            for (i, &candidate) in combo.iter().enumerate() {
350                if i < quant.bound_vars.len() {
351                    subst.insert(quant.bound_vars[i].0, candidate);
352                }
353            }
354
355            // Apply substitution to get ground instance
356            let ground_body = self.apply_substitution(quant.body, &subst, manager);
357
358            // Evaluate under the model
359            let evaluated = self.evaluate_under_model(ground_body, model, manager);
360
361            // Check if this is a counterexample (evaluates to false for ∀x.φ(x))
362            if let Some(t) = manager.get(evaluated)
363                && matches!(t.kind, TermKind::False)
364            {
365                results.push(Instantiation {
366                    quantifier: quant.term,
367                    substitution: subst,
368                    result: ground_body,
369                });
370
371                // Limit counterexamples per quantifier per round
372                if results.len() >= 5 {
373                    break;
374                }
375            }
376        }
377
378        // Strategy 2: Use model values if no candidates found counterexamples
379        if results.is_empty() {
380            // Try to use values from the model for variables of matching sort
381            let model_instantiation = self.instantiate_from_model(quantifier_idx, model, manager);
382            if let Some(inst) = model_instantiation {
383                results.push(inst);
384            }
385        }
386
387        results
388    }
389
390    /// Build candidate lists for each bound variable
391    fn build_candidate_lists(
392        &self,
393        bound_vars: &[(Spur, SortId)],
394        manager: &mut TermManager,
395    ) -> Vec<Vec<TermId>> {
396        let mut result = Vec::new();
397
398        for &(_name, sort) in bound_vars {
399            let mut candidates = Vec::new();
400
401            // Add candidates from the pool
402            if let Some(pool) = self.candidates_by_sort.get(&sort) {
403                candidates.extend(pool.iter().copied().take(10)); // Limit per sort
404            }
405
406            // Add default values based on sort
407            if sort == manager.sorts.int_sort {
408                // Add some default integers
409                for i in 0..3 {
410                    let int_id = manager.mk_int(i);
411                    if !candidates.contains(&int_id) {
412                        candidates.push(int_id);
413                    }
414                }
415            } else if sort == manager.sorts.bool_sort {
416                let true_id = manager.mk_true();
417                let false_id = manager.mk_false();
418                if !candidates.contains(&true_id) {
419                    candidates.push(true_id);
420                }
421                if !candidates.contains(&false_id) {
422                    candidates.push(false_id);
423                }
424            }
425
426            result.push(candidates);
427        }
428
429        result
430    }
431
432    /// Enumerate combinations of candidates (limited)
433    fn enumerate_combinations(
434        &self,
435        candidates: &[Vec<TermId>],
436        max_per_dim: usize,
437    ) -> Vec<Vec<TermId>> {
438        if candidates.is_empty() {
439            return vec![vec![]];
440        }
441
442        let mut results = Vec::new();
443        let mut indices = vec![0usize; candidates.len()];
444
445        loop {
446            // Build current combination
447            let combo: Vec<TermId> = indices
448                .iter()
449                .enumerate()
450                .filter_map(|(i, &idx)| candidates.get(i).and_then(|c| c.get(idx).copied()))
451                .collect();
452
453            if combo.len() == candidates.len() {
454                results.push(combo);
455            }
456
457            // Limit total combinations
458            if results.len() >= 100 {
459                break;
460            }
461
462            // Increment indices
463            let mut carry = true;
464            for (i, idx) in indices.iter_mut().enumerate() {
465                if carry {
466                    *idx += 1;
467                    let limit = candidates.get(i).map_or(1, |c| c.len().min(max_per_dim));
468                    if *idx >= limit {
469                        *idx = 0;
470                    } else {
471                        carry = false;
472                    }
473                }
474            }
475
476            if carry {
477                // Overflow - we've tried all combinations
478                break;
479            }
480        }
481
482        results
483    }
484
485    /// Apply a substitution to a term
486    #[allow(clippy::only_used_in_recursion)]
487    fn apply_substitution(
488        &self,
489        term: TermId,
490        subst: &FxHashMap<Spur, TermId>,
491        manager: &mut TermManager,
492    ) -> TermId {
493        // Use the term manager's substitute method if available
494        // For now, implement a simple recursive substitution
495        let Some(t) = manager.get(term).cloned() else {
496            return term;
497        };
498
499        match &t.kind {
500            TermKind::Var(name) => {
501                // Check if this variable should be substituted
502                if let Some(&replacement) = subst.get(name) {
503                    replacement
504                } else {
505                    term
506                }
507            }
508            TermKind::Not(arg) => {
509                let new_arg = self.apply_substitution(*arg, subst, manager);
510                manager.mk_not(new_arg)
511            }
512            TermKind::And(args) => {
513                let new_args: Vec<_> = args
514                    .iter()
515                    .map(|&a| self.apply_substitution(a, subst, manager))
516                    .collect();
517                manager.mk_and(new_args)
518            }
519            TermKind::Or(args) => {
520                let new_args: Vec<_> = args
521                    .iter()
522                    .map(|&a| self.apply_substitution(a, subst, manager))
523                    .collect();
524                manager.mk_or(new_args)
525            }
526            TermKind::Eq(lhs, rhs) => {
527                let new_lhs = self.apply_substitution(*lhs, subst, manager);
528                let new_rhs = self.apply_substitution(*rhs, subst, manager);
529                manager.mk_eq(new_lhs, new_rhs)
530            }
531            TermKind::Lt(lhs, rhs) => {
532                let new_lhs = self.apply_substitution(*lhs, subst, manager);
533                let new_rhs = self.apply_substitution(*rhs, subst, manager);
534                manager.mk_lt(new_lhs, new_rhs)
535            }
536            TermKind::Le(lhs, rhs) => {
537                let new_lhs = self.apply_substitution(*lhs, subst, manager);
538                let new_rhs = self.apply_substitution(*rhs, subst, manager);
539                manager.mk_le(new_lhs, new_rhs)
540            }
541            TermKind::Gt(lhs, rhs) => {
542                let new_lhs = self.apply_substitution(*lhs, subst, manager);
543                let new_rhs = self.apply_substitution(*rhs, subst, manager);
544                manager.mk_gt(new_lhs, new_rhs)
545            }
546            TermKind::Ge(lhs, rhs) => {
547                let new_lhs = self.apply_substitution(*lhs, subst, manager);
548                let new_rhs = self.apply_substitution(*rhs, subst, manager);
549                manager.mk_ge(new_lhs, new_rhs)
550            }
551            TermKind::Add(args) => {
552                let new_args: SmallVec<[TermId; 4]> = args
553                    .iter()
554                    .map(|&a| self.apply_substitution(a, subst, manager))
555                    .collect();
556                manager.mk_add(new_args)
557            }
558            TermKind::Sub(lhs, rhs) => {
559                let new_lhs = self.apply_substitution(*lhs, subst, manager);
560                let new_rhs = self.apply_substitution(*rhs, subst, manager);
561                manager.mk_sub(new_lhs, new_rhs)
562            }
563            TermKind::Mul(args) => {
564                let new_args: SmallVec<[TermId; 4]> = args
565                    .iter()
566                    .map(|&a| self.apply_substitution(a, subst, manager))
567                    .collect();
568                manager.mk_mul(new_args)
569            }
570            TermKind::Neg(arg) => {
571                let new_arg = self.apply_substitution(*arg, subst, manager);
572                manager.mk_neg(new_arg)
573            }
574            TermKind::Implies(lhs, rhs) => {
575                let new_lhs = self.apply_substitution(*lhs, subst, manager);
576                let new_rhs = self.apply_substitution(*rhs, subst, manager);
577                manager.mk_implies(new_lhs, new_rhs)
578            }
579            TermKind::Ite(cond, then_br, else_br) => {
580                let new_cond = self.apply_substitution(*cond, subst, manager);
581                let new_then = self.apply_substitution(*then_br, subst, manager);
582                let new_else = self.apply_substitution(*else_br, subst, manager);
583                manager.mk_ite(new_cond, new_then, new_else)
584            }
585            TermKind::Apply { func, args } => {
586                let func_name = manager.resolve_str(*func).to_string();
587                let new_args: SmallVec<[TermId; 4]> = args
588                    .iter()
589                    .map(|&a| self.apply_substitution(a, subst, manager))
590                    .collect();
591                manager.mk_apply(&func_name, new_args, t.sort)
592            }
593            // Constants and other terms don't need substitution
594            _ => term,
595        }
596    }
597
598    /// Evaluate a term under a model
599    #[allow(clippy::only_used_in_recursion)]
600    fn evaluate_under_model(
601        &self,
602        term: TermId,
603        model: &FxHashMap<TermId, TermId>,
604        manager: &mut TermManager,
605    ) -> TermId {
606        // Check if we have a direct model value
607        if let Some(&val) = model.get(&term) {
608            return val;
609        }
610
611        let Some(t) = manager.get(term).cloned() else {
612            return term;
613        };
614
615        match &t.kind {
616            TermKind::True | TermKind::False | TermKind::IntConst(_) | TermKind::RealConst(_) => {
617                // Constants evaluate to themselves
618                term
619            }
620            TermKind::Var(_) => {
621                // Variables: look up in model
622                model.get(&term).copied().unwrap_or(term)
623            }
624            TermKind::Not(arg) => {
625                let eval_arg = self.evaluate_under_model(*arg, model, manager);
626                if let Some(arg_t) = manager.get(eval_arg) {
627                    match arg_t.kind {
628                        TermKind::True => return manager.mk_false(),
629                        TermKind::False => return manager.mk_true(),
630                        _ => {}
631                    }
632                }
633                manager.mk_not(eval_arg)
634            }
635            TermKind::And(args) => {
636                let mut all_true = true;
637                for &arg in args {
638                    let eval_arg = self.evaluate_under_model(arg, model, manager);
639                    if let Some(arg_t) = manager.get(eval_arg) {
640                        match arg_t.kind {
641                            TermKind::False => return manager.mk_false(),
642                            TermKind::True => continue,
643                            _ => all_true = false,
644                        }
645                    } else {
646                        all_true = false;
647                    }
648                }
649                if all_true { manager.mk_true() } else { term }
650            }
651            TermKind::Or(args) => {
652                let mut all_false = true;
653                for &arg in args {
654                    let eval_arg = self.evaluate_under_model(arg, model, manager);
655                    if let Some(arg_t) = manager.get(eval_arg) {
656                        match arg_t.kind {
657                            TermKind::True => return manager.mk_true(),
658                            TermKind::False => continue,
659                            _ => all_false = false,
660                        }
661                    } else {
662                        all_false = false;
663                    }
664                }
665                if all_false { manager.mk_false() } else { term }
666            }
667            TermKind::Eq(lhs, rhs) => {
668                let eval_lhs = self.evaluate_under_model(*lhs, model, manager);
669                let eval_rhs = self.evaluate_under_model(*rhs, model, manager);
670
671                // Simple equality check for constants
672                if eval_lhs == eval_rhs {
673                    return manager.mk_true();
674                }
675
676                // Check integer constants
677                let lhs_t = manager.get(eval_lhs).cloned();
678                let rhs_t = manager.get(eval_rhs).cloned();
679
680                if let (Some(l), Some(r)) = (lhs_t, rhs_t) {
681                    match (&l.kind, &r.kind) {
682                        (TermKind::IntConst(a), TermKind::IntConst(b)) => {
683                            if a == b {
684                                return manager.mk_true();
685                            } else {
686                                return manager.mk_false();
687                            }
688                        }
689                        (TermKind::True, TermKind::True) | (TermKind::False, TermKind::False) => {
690                            return manager.mk_true();
691                        }
692                        (TermKind::True, TermKind::False) | (TermKind::False, TermKind::True) => {
693                            return manager.mk_false();
694                        }
695                        _ => {}
696                    }
697                }
698
699                term
700            }
701            TermKind::Lt(lhs, rhs) => {
702                let eval_lhs = self.evaluate_under_model(*lhs, model, manager);
703                let eval_rhs = self.evaluate_under_model(*rhs, model, manager);
704
705                let lhs_t = manager.get(eval_lhs).cloned();
706                let rhs_t = manager.get(eval_rhs).cloned();
707
708                if let (Some(l), Some(r)) = (lhs_t, rhs_t)
709                    && let (TermKind::IntConst(a), TermKind::IntConst(b)) = (&l.kind, &r.kind)
710                {
711                    if a < b {
712                        return manager.mk_true();
713                    } else {
714                        return manager.mk_false();
715                    }
716                }
717
718                term
719            }
720            TermKind::Le(lhs, rhs) => {
721                let eval_lhs = self.evaluate_under_model(*lhs, model, manager);
722                let eval_rhs = self.evaluate_under_model(*rhs, model, manager);
723
724                let lhs_t = manager.get(eval_lhs).cloned();
725                let rhs_t = manager.get(eval_rhs).cloned();
726
727                if let (Some(l), Some(r)) = (lhs_t, rhs_t)
728                    && let (TermKind::IntConst(a), TermKind::IntConst(b)) = (&l.kind, &r.kind)
729                {
730                    if a <= b {
731                        return manager.mk_true();
732                    } else {
733                        return manager.mk_false();
734                    }
735                }
736
737                term
738            }
739            TermKind::Gt(lhs, rhs) => {
740                let eval_lhs = self.evaluate_under_model(*lhs, model, manager);
741                let eval_rhs = self.evaluate_under_model(*rhs, model, manager);
742
743                let lhs_t = manager.get(eval_lhs).cloned();
744                let rhs_t = manager.get(eval_rhs).cloned();
745
746                if let (Some(l), Some(r)) = (lhs_t, rhs_t)
747                    && let (TermKind::IntConst(a), TermKind::IntConst(b)) = (&l.kind, &r.kind)
748                {
749                    if a > b {
750                        return manager.mk_true();
751                    } else {
752                        return manager.mk_false();
753                    }
754                }
755
756                term
757            }
758            TermKind::Ge(lhs, rhs) => {
759                let eval_lhs = self.evaluate_under_model(*lhs, model, manager);
760                let eval_rhs = self.evaluate_under_model(*rhs, model, manager);
761
762                let lhs_t = manager.get(eval_lhs).cloned();
763                let rhs_t = manager.get(eval_rhs).cloned();
764
765                if let (Some(l), Some(r)) = (lhs_t, rhs_t)
766                    && let (TermKind::IntConst(a), TermKind::IntConst(b)) = (&l.kind, &r.kind)
767                {
768                    if a >= b {
769                        return manager.mk_true();
770                    } else {
771                        return manager.mk_false();
772                    }
773                }
774
775                term
776            }
777            // Add more cases as needed
778            _ => {
779                // For complex terms, try to simplify
780                manager.simplify(term)
781            }
782        }
783    }
784
785    /// Try to instantiate from model values
786    fn instantiate_from_model(
787        &self,
788        quantifier_idx: usize,
789        model: &FxHashMap<TermId, TermId>,
790        manager: &mut TermManager,
791    ) -> Option<Instantiation> {
792        let quant = &self.quantifiers[quantifier_idx];
793        let mut subst: FxHashMap<Spur, TermId> = FxHashMap::default();
794
795        // Try to find model values for each bound variable's sort
796        for &(name, sort) in &quant.bound_vars {
797            // Look for any model value with matching sort
798            let mut found = None;
799            for (&term, &_value) in model {
800                if let Some(t) = manager.get(term)
801                    && t.sort == sort
802                {
803                    found = Some(term);
804                    break;
805                }
806            }
807
808            // If no model value found, use a default
809            let candidate = match found {
810                Some(t) => t,
811                None => {
812                    if sort == manager.sorts.int_sort {
813                        manager.mk_int(0)
814                    } else if sort == manager.sorts.bool_sort {
815                        manager.mk_true()
816                    } else {
817                        // Return a dummy for unknown sorts
818                        manager.mk_true()
819                    }
820                }
821            };
822
823            subst.insert(name, candidate);
824        }
825
826        // Apply substitution
827        let ground_body = self.apply_substitution(quant.body, &subst, manager);
828
829        Some(Instantiation {
830            quantifier: quant.term,
831            substitution: subst,
832            result: ground_body,
833        })
834    }
835
836    /// Get statistics about MBQI
837    pub fn stats(&self) -> MBQIStats {
838        MBQIStats {
839            num_quantifiers: self.quantifiers.len(),
840            total_instantiations: self.total_instantiation_count,
841            max_instantiations: self.max_total_instantiations,
842            unique_instantiations: self.generated_instantiations.len(),
843        }
844    }
845}
846
847/// Statistics about MBQI
848#[derive(Debug, Clone)]
849pub struct MBQIStats {
850    /// Number of tracked quantifiers
851    pub num_quantifiers: usize,
852    /// Total instantiations generated
853    pub total_instantiations: usize,
854    /// Maximum allowed instantiations
855    pub max_instantiations: usize,
856    /// Unique instantiations (after deduplication)
857    pub unique_instantiations: usize,
858}
859
860#[cfg(test)]
861mod tests {
862    use super::*;
863
864    #[test]
865    fn test_mbqi_new() {
866        let mbqi = MBQISolver::new();
867        assert!(mbqi.is_enabled());
868        assert_eq!(mbqi.quantifiers.len(), 0);
869    }
870
871    #[test]
872    fn test_mbqi_disable() {
873        let mut mbqi = MBQISolver::new();
874        mbqi.set_enabled(false);
875        assert!(!mbqi.is_enabled());
876
877        let model = FxHashMap::default();
878        let mut manager = TermManager::new();
879        let result = mbqi.check_with_model(&model, &mut manager);
880        assert!(matches!(result, MBQIResult::NoQuantifiers));
881    }
882
883    #[test]
884    fn test_mbqi_no_quantifiers() {
885        let mut mbqi = MBQISolver::new();
886        let model = FxHashMap::default();
887        let mut manager = TermManager::new();
888
889        let result = mbqi.check_with_model(&model, &mut manager);
890        assert!(matches!(result, MBQIResult::NoQuantifiers));
891    }
892
893    #[test]
894    fn test_mbqi_add_candidate() {
895        let mut mbqi = MBQISolver::new();
896        let manager = TermManager::new();
897
898        let sort = manager.sorts.int_sort;
899        mbqi.add_candidate(TermId::new(1), sort);
900        mbqi.add_candidate(TermId::new(2), sort);
901
902        let candidates = mbqi.get_candidates(sort);
903        assert_eq!(candidates.len(), 2);
904    }
905
906    #[test]
907    fn test_mbqi_stats() {
908        let mbqi = MBQISolver::new();
909        let stats = mbqi.stats();
910
911        assert_eq!(stats.num_quantifiers, 0);
912        assert_eq!(stats.total_instantiations, 0);
913    }
914
915    #[test]
916    fn test_mbqi_clear() {
917        let mut mbqi = MBQISolver::new();
918        let manager = TermManager::new();
919
920        mbqi.add_candidate(TermId::new(1), manager.sorts.int_sort);
921        mbqi.total_instantiation_count = 5;
922
923        mbqi.clear();
924
925        assert_eq!(mbqi.quantifiers.len(), 0);
926        assert_eq!(mbqi.total_instantiation_count, 0);
927    }
928
929    #[test]
930    fn test_mbqi_with_limit() {
931        let mbqi = MBQISolver::with_limit(100);
932        assert_eq!(mbqi.max_total_instantiations, 100);
933    }
934
935    #[test]
936    fn test_enumerate_combinations_empty() {
937        let mbqi = MBQISolver::new();
938        let candidates: Vec<Vec<TermId>> = vec![];
939        let combos = mbqi.enumerate_combinations(&candidates, 10);
940        assert_eq!(combos.len(), 1);
941        assert!(combos[0].is_empty());
942    }
943
944    #[test]
945    fn test_enumerate_combinations_single() {
946        let mbqi = MBQISolver::new();
947        let candidates = vec![vec![TermId::new(1), TermId::new(2)]];
948        let combos = mbqi.enumerate_combinations(&candidates, 10);
949        assert_eq!(combos.len(), 2);
950    }
951
952    #[test]
953    fn test_enumerate_combinations_multiple() {
954        let mbqi = MBQISolver::new();
955        let candidates = vec![
956            vec![TermId::new(1), TermId::new(2)],
957            vec![TermId::new(3), TermId::new(4)],
958        ];
959        let combos = mbqi.enumerate_combinations(&candidates, 10);
960        // 2 * 2 = 4 combinations
961        assert_eq!(combos.len(), 4);
962    }
963}