Skip to main content

oxiz_solver/mbqi/
instantiation.rs

1//! Model-Based Instantiation Engine
2//!
3//! This module implements the core instantiation logic for MBQI. It handles:
4//! - Extracting instantiations from models and counterexamples
5//! - Conflict-driven instantiation
6//! - Pattern matching and trigger selection
7//! - Instantiation deduplication and filtering
8
9#![allow(missing_docs)]
10#![allow(dead_code)]
11
12use lasso::Spur;
13use num_bigint::BigInt;
14use oxiz_core::ast::{TermId, TermKind, TermManager};
15use oxiz_core::sort::SortId;
16use rustc_hash::{FxHashMap, FxHashSet};
17use smallvec::SmallVec;
18
19use super::counterexample::CounterExampleGenerator;
20use super::model_completion::CompletedModel;
21use super::{Instantiation, InstantiationReason, QuantifiedFormula};
22
23/// Context for instantiation
24#[derive(Debug)]
25pub struct InstantiationContext {
26    /// Term manager
27    pub manager: TermManager,
28    /// Current model
29    pub model: CompletedModel,
30    /// Generation counter
31    pub generation: u32,
32    /// E-graph for equality reasoning (simplified)
33    pub equalities: FxHashMap<TermId, TermId>,
34}
35
36impl InstantiationContext {
37    /// Create a new instantiation context
38    pub fn new(manager: TermManager) -> Self {
39        Self {
40            manager,
41            model: CompletedModel::new(),
42            generation: 0,
43            equalities: FxHashMap::default(),
44        }
45    }
46
47    /// Set the current model
48    pub fn set_model(&mut self, model: CompletedModel) {
49        self.model = model;
50    }
51
52    /// Increment generation
53    pub fn next_generation(&mut self) {
54        self.generation += 1;
55    }
56
57    /// Add an equality
58    pub fn add_equality(&mut self, lhs: TermId, rhs: TermId) {
59        self.equalities.insert(lhs, rhs);
60        self.equalities.insert(rhs, lhs);
61    }
62
63    /// Find representative in equality graph
64    pub fn find_representative(&self, term: TermId) -> TermId {
65        let mut current = term;
66        let mut visited = FxHashSet::default();
67
68        while let Some(&next) = self.equalities.get(&current) {
69            if visited.contains(&next) {
70                break; // Cycle detected
71            }
72            visited.insert(current);
73            current = next;
74        }
75
76        current
77    }
78}
79
80/// Pattern for instantiation (E-matching style)
81#[derive(Debug, Clone)]
82pub struct InstantiationPattern {
83    /// Terms that form the pattern
84    pub terms: Vec<TermId>,
85    /// Variables that must be matched
86    pub vars: FxHashSet<Spur>,
87    /// Number of variables
88    pub num_vars: usize,
89    /// Pattern quality (higher = better)
90    pub quality: f64,
91}
92
93impl InstantiationPattern {
94    /// Create a new pattern
95    pub fn new(terms: Vec<TermId>) -> Self {
96        Self {
97            terms,
98            vars: FxHashSet::default(),
99            num_vars: 0,
100            quality: 1.0,
101        }
102    }
103
104    /// Extract patterns from a quantified formula
105    pub fn extract_patterns(quantifier: &QuantifiedFormula, manager: &TermManager) -> Vec<Self> {
106        let mut patterns = Vec::new();
107
108        // Use explicit patterns if available
109        if !quantifier.patterns.is_empty() {
110            for pattern_terms in &quantifier.patterns {
111                let mut pattern = Self::new(pattern_terms.clone());
112                pattern.collect_vars(manager);
113                pattern.calculate_quality(manager);
114                patterns.push(pattern);
115            }
116        } else {
117            // Auto-generate patterns from the body
118            let generated = Self::generate_patterns(quantifier.body, manager);
119            patterns.extend(generated);
120        }
121
122        patterns
123    }
124
125    /// Generate patterns from a term
126    fn generate_patterns(term: TermId, manager: &TermManager) -> Vec<Self> {
127        let mut patterns = Vec::new();
128        let candidates = Self::collect_pattern_candidates(term, manager);
129
130        for candidate in candidates {
131            let mut pattern = Self::new(vec![candidate]);
132            pattern.collect_vars(manager);
133            if pattern.num_vars > 0 {
134                pattern.calculate_quality(manager);
135                patterns.push(pattern);
136            }
137        }
138
139        patterns
140    }
141
142    /// Collect pattern candidates from a term
143    fn collect_pattern_candidates(term: TermId, manager: &TermManager) -> Vec<TermId> {
144        let mut candidates = Vec::new();
145        let mut visited = FxHashSet::default();
146        Self::collect_candidates_rec(term, &mut candidates, &mut visited, manager);
147        candidates
148    }
149
150    fn collect_candidates_rec(
151        term: TermId,
152        candidates: &mut Vec<TermId>,
153        visited: &mut FxHashSet<TermId>,
154        manager: &TermManager,
155    ) {
156        if visited.contains(&term) {
157            return;
158        }
159        visited.insert(term);
160
161        let Some(t) = manager.get(term) else {
162            return;
163        };
164
165        // Function applications are good pattern candidates
166        if matches!(t.kind, TermKind::Apply { .. }) {
167            candidates.push(term);
168        }
169
170        // Recurse into children
171        match &t.kind {
172            TermKind::Apply { args, .. } => {
173                for &arg in args.iter() {
174                    Self::collect_candidates_rec(arg, candidates, visited, manager);
175                }
176            }
177            TermKind::And(args) | TermKind::Or(args) => {
178                for &arg in args {
179                    Self::collect_candidates_rec(arg, candidates, visited, manager);
180                }
181            }
182            TermKind::Eq(lhs, rhs) | TermKind::Lt(lhs, rhs) | TermKind::Le(lhs, rhs) => {
183                Self::collect_candidates_rec(*lhs, candidates, visited, manager);
184                Self::collect_candidates_rec(*rhs, candidates, visited, manager);
185            }
186            _ => {}
187        }
188    }
189
190    /// Collect variables in the pattern
191    fn collect_vars(&mut self, manager: &TermManager) {
192        self.vars.clear();
193        let mut visited = FxHashSet::default();
194
195        // Collect terms first to avoid borrow checker issues
196        let terms: Vec<_> = self.terms.to_vec();
197        for term in terms {
198            self.collect_vars_rec(term, &mut visited, manager);
199        }
200
201        self.num_vars = self.vars.len();
202    }
203
204    fn collect_vars_rec(
205        &mut self,
206        term: TermId,
207        visited: &mut FxHashSet<TermId>,
208        manager: &TermManager,
209    ) {
210        if visited.contains(&term) {
211            return;
212        }
213        visited.insert(term);
214
215        let Some(t) = manager.get(term) else {
216            return;
217        };
218
219        if let TermKind::Var(name) = t.kind {
220            self.vars.insert(name);
221        }
222
223        // Recurse into children
224        match &t.kind {
225            TermKind::Apply { args, .. } => {
226                for &arg in args.iter() {
227                    self.collect_vars_rec(arg, visited, manager);
228                }
229            }
230            TermKind::Not(arg) | TermKind::Neg(arg) => {
231                self.collect_vars_rec(*arg, visited, manager);
232            }
233            TermKind::And(args) | TermKind::Or(args) => {
234                for &arg in args {
235                    self.collect_vars_rec(arg, visited, manager);
236                }
237            }
238            _ => {}
239        }
240    }
241
242    /// Calculate pattern quality
243    fn calculate_quality(&mut self, manager: &TermManager) {
244        // Quality factors:
245        // - More variables = better (more specific)
246        // - Fewer terms = better (simpler)
247        // - Contains function applications = better
248
249        let var_factor = 1.0 + (self.num_vars as f64);
250        let term_factor = 1.0 / (1.0 + self.terms.len() as f64);
251        let func_factor = if self.has_function_applications(manager) {
252            2.0
253        } else {
254            1.0
255        };
256
257        self.quality = var_factor * term_factor * func_factor;
258    }
259
260    fn has_function_applications(&self, manager: &TermManager) -> bool {
261        for &term in &self.terms {
262            if let Some(t) = manager.get(term)
263                && matches!(t.kind, TermKind::Apply { .. })
264            {
265                return true;
266            }
267        }
268        false
269    }
270}
271
272/// Quantifier instantiator
273#[derive(Debug)]
274pub struct QuantifierInstantiator {
275    /// Counterexample generator
276    cex_generator: CounterExampleGenerator,
277    /// Deduplication cache
278    dedup_cache: FxHashSet<InstantiationKey>,
279    /// Statistics
280    stats: InstantiatorStats,
281}
282
283impl QuantifierInstantiator {
284    /// Create a new instantiator
285    pub fn new() -> Self {
286        Self {
287            cex_generator: CounterExampleGenerator::new(),
288            dedup_cache: FxHashSet::default(),
289            stats: InstantiatorStats::default(),
290        }
291    }
292
293    /// Generate instantiations for a quantifier using model-based approach
294    pub fn instantiate_from_model(
295        &mut self,
296        quantifier: &QuantifiedFormula,
297        model: &CompletedModel,
298        manager: &mut TermManager,
299    ) -> Vec<Instantiation> {
300        self.stats.num_instantiation_attempts += 1;
301
302        let mut instantiations = Vec::new();
303
304        // Generate counterexamples
305        let counterexamples = self.cex_generator.generate(quantifier, model, manager);
306
307        // Convert counterexamples to instantiations
308        for cex in counterexamples {
309            // Apply substitution to get ground instance
310            let ground_body = self.apply_substitution(quantifier.body, &cex.assignment, manager);
311
312            let inst = cex.to_instantiation(ground_body);
313
314            // Check for duplicates
315            if self.is_duplicate(&inst) {
316                self.stats.num_duplicates_filtered += 1;
317                continue;
318            }
319
320            self.record_instantiation(&inst);
321            instantiations.push(inst);
322        }
323
324        self.stats.num_instantiations_generated += instantiations.len();
325        instantiations
326    }
327
328    /// Generate instantiations using conflict-driven approach
329    pub fn instantiate_from_conflict(
330        &mut self,
331        quantifier: &QuantifiedFormula,
332        conflict: &[TermId],
333        model: &CompletedModel,
334        manager: &mut TermManager,
335    ) -> Vec<Instantiation> {
336        let mut instantiations = Vec::new();
337
338        // Analyze the conflict to extract relevant terms
339        let conflict_terms = self.extract_relevant_terms(conflict, manager);
340
341        // Try to build instantiations from conflict terms
342        for assignment in
343            self.build_assignments_from_terms(&quantifier.bound_vars, &conflict_terms, manager)
344        {
345            let ground_body = self.apply_substitution(quantifier.body, &assignment, manager);
346
347            let inst = Instantiation::with_reason(
348                quantifier.term,
349                assignment,
350                ground_body,
351                model.generation,
352                InstantiationReason::Conflict,
353            );
354
355            if !self.is_duplicate(&inst) {
356                self.record_instantiation(&inst);
357                instantiations.push(inst);
358            }
359        }
360
361        instantiations
362    }
363
364    /// Apply substitution to a term
365    fn apply_substitution(
366        &self,
367        term: TermId,
368        subst: &FxHashMap<Spur, TermId>,
369        manager: &mut TermManager,
370    ) -> TermId {
371        let mut cache = FxHashMap::default();
372        self.apply_substitution_cached(term, subst, manager, &mut cache)
373    }
374
375    fn apply_substitution_cached(
376        &self,
377        term: TermId,
378        subst: &FxHashMap<Spur, TermId>,
379        manager: &mut TermManager,
380        cache: &mut FxHashMap<TermId, TermId>,
381    ) -> TermId {
382        if let Some(&cached) = cache.get(&term) {
383            return cached;
384        }
385
386        let Some(t) = manager.get(term).cloned() else {
387            return term;
388        };
389
390        let result = match &t.kind {
391            TermKind::Var(name) => subst.get(name).copied().unwrap_or(term),
392            TermKind::Not(arg) => {
393                let new_arg = self.apply_substitution_cached(*arg, subst, manager, cache);
394                manager.mk_not(new_arg)
395            }
396            TermKind::And(args) => {
397                let new_args: Vec<_> = args
398                    .iter()
399                    .map(|&a| self.apply_substitution_cached(a, subst, manager, cache))
400                    .collect();
401                manager.mk_and(new_args)
402            }
403            TermKind::Or(args) => {
404                let new_args: Vec<_> = args
405                    .iter()
406                    .map(|&a| self.apply_substitution_cached(a, subst, manager, cache))
407                    .collect();
408                manager.mk_or(new_args)
409            }
410            TermKind::Eq(lhs, rhs) => {
411                let new_lhs = self.apply_substitution_cached(*lhs, subst, manager, cache);
412                let new_rhs = self.apply_substitution_cached(*rhs, subst, manager, cache);
413                manager.mk_eq(new_lhs, new_rhs)
414            }
415            TermKind::Lt(lhs, rhs) => {
416                let new_lhs = self.apply_substitution_cached(*lhs, subst, manager, cache);
417                let new_rhs = self.apply_substitution_cached(*rhs, subst, manager, cache);
418                manager.mk_lt(new_lhs, new_rhs)
419            }
420            TermKind::Le(lhs, rhs) => {
421                let new_lhs = self.apply_substitution_cached(*lhs, subst, manager, cache);
422                let new_rhs = self.apply_substitution_cached(*rhs, subst, manager, cache);
423                manager.mk_le(new_lhs, new_rhs)
424            }
425            TermKind::Add(args) => {
426                let new_args: SmallVec<[TermId; 4]> = args
427                    .iter()
428                    .map(|&a| self.apply_substitution_cached(a, subst, manager, cache))
429                    .collect();
430                manager.mk_add(new_args)
431            }
432            TermKind::Mul(args) => {
433                let new_args: SmallVec<[TermId; 4]> = args
434                    .iter()
435                    .map(|&a| self.apply_substitution_cached(a, subst, manager, cache))
436                    .collect();
437                manager.mk_mul(new_args)
438            }
439            TermKind::Apply { func, args } => {
440                let func_name = manager.resolve_str(*func).to_string();
441                let new_args: SmallVec<[TermId; 4]> = args
442                    .iter()
443                    .map(|&a| self.apply_substitution_cached(a, subst, manager, cache))
444                    .collect();
445                manager.mk_apply(&func_name, new_args, t.sort)
446            }
447            _ => term,
448        };
449
450        cache.insert(term, result);
451        result
452    }
453
454    /// Extract relevant terms from conflict clause
455    fn extract_relevant_terms(&self, conflict: &[TermId], manager: &TermManager) -> Vec<TermId> {
456        let mut terms = Vec::new();
457        let mut visited = FxHashSet::default();
458
459        for &term in conflict {
460            self.extract_relevant_terms_rec(term, &mut terms, &mut visited, manager);
461        }
462
463        terms
464    }
465
466    fn extract_relevant_terms_rec(
467        &self,
468        term: TermId,
469        terms: &mut Vec<TermId>,
470        visited: &mut FxHashSet<TermId>,
471        manager: &TermManager,
472    ) {
473        if visited.contains(&term) {
474            return;
475        }
476        visited.insert(term);
477
478        let Some(t) = manager.get(term) else {
479            return;
480        };
481
482        // Collect ground terms
483        if self.is_ground_value(term, manager) {
484            terms.push(term);
485        }
486
487        // Recurse into children
488        match &t.kind {
489            TermKind::Not(arg) | TermKind::Neg(arg) => {
490                self.extract_relevant_terms_rec(*arg, terms, visited, manager);
491            }
492            TermKind::And(args) | TermKind::Or(args) => {
493                for &arg in args {
494                    self.extract_relevant_terms_rec(arg, terms, visited, manager);
495                }
496            }
497            TermKind::Eq(lhs, rhs) | TermKind::Lt(lhs, rhs) => {
498                self.extract_relevant_terms_rec(*lhs, terms, visited, manager);
499                self.extract_relevant_terms_rec(*rhs, terms, visited, manager);
500            }
501            TermKind::Apply { args, .. } => {
502                for &arg in args.iter() {
503                    self.extract_relevant_terms_rec(arg, terms, visited, manager);
504                }
505            }
506            _ => {}
507        }
508    }
509
510    /// Check if a term is a ground value
511    fn is_ground_value(&self, term: TermId, manager: &TermManager) -> bool {
512        let Some(t) = manager.get(term) else {
513            return false;
514        };
515
516        matches!(
517            t.kind,
518            TermKind::True
519                | TermKind::False
520                | TermKind::IntConst(_)
521                | TermKind::RealConst(_)
522                | TermKind::BitVecConst { .. }
523        )
524    }
525
526    /// Build assignments from terms
527    fn build_assignments_from_terms(
528        &self,
529        bound_vars: &[(Spur, SortId)],
530        terms: &[TermId],
531        manager: &TermManager,
532    ) -> Vec<FxHashMap<Spur, TermId>> {
533        let mut assignments = Vec::new();
534
535        // Group terms by sort
536        let mut terms_by_sort: FxHashMap<SortId, Vec<TermId>> = FxHashMap::default();
537        for &term in terms {
538            if let Some(t) = manager.get(term) {
539                terms_by_sort.entry(t.sort).or_default().push(term);
540            }
541        }
542
543        // Build candidate lists for each variable
544        let mut candidates = Vec::new();
545        for &(_name, sort) in bound_vars {
546            let sort_terms = terms_by_sort
547                .get(&sort)
548                .map(|v| v.as_slice())
549                .unwrap_or(&[]);
550            candidates.push(sort_terms.to_vec());
551        }
552
553        // Enumerate combinations (limited)
554        let max_combinations = 10;
555        let mut indices = vec![0usize; bound_vars.len()];
556
557        for _ in 0..max_combinations {
558            let mut assignment = FxHashMap::default();
559            let mut valid = true;
560
561            for (i, &idx) in indices.iter().enumerate() {
562                if let Some(cands) = candidates.get(i) {
563                    if let Some(&term) = cands.get(idx) {
564                        if let Some((name, _)) = bound_vars.get(i) {
565                            assignment.insert(*name, term);
566                        }
567                    } else {
568                        valid = false;
569                        break;
570                    }
571                }
572            }
573
574            if valid && assignment.len() == bound_vars.len() {
575                assignments.push(assignment);
576            }
577
578            // Increment indices
579            let mut carry = true;
580            for (i, idx) in indices.iter_mut().enumerate() {
581                if carry {
582                    *idx += 1;
583                    let limit = candidates.get(i).map_or(1, |c| c.len());
584                    if *idx >= limit {
585                        *idx = 0;
586                    } else {
587                        carry = false;
588                    }
589                }
590            }
591
592            if carry {
593                break;
594            }
595        }
596
597        assignments
598    }
599
600    /// Check if an instantiation is a duplicate
601    fn is_duplicate(&self, inst: &Instantiation) -> bool {
602        let key = InstantiationKey::from_instantiation(inst);
603        self.dedup_cache.contains(&key)
604    }
605
606    /// Record an instantiation for deduplication
607    fn record_instantiation(&mut self, inst: &Instantiation) {
608        let key = InstantiationKey::from_instantiation(inst);
609        self.dedup_cache.insert(key);
610    }
611
612    /// Clear deduplication cache
613    pub fn clear_cache(&mut self) {
614        self.dedup_cache.clear();
615        self.cex_generator.clear_cache();
616    }
617
618    /// Get statistics
619    pub fn stats(&self) -> &InstantiatorStats {
620        &self.stats
621    }
622}
623
624impl Default for QuantifierInstantiator {
625    fn default() -> Self {
626        Self::new()
627    }
628}
629
630/// Key for instantiation deduplication
631#[derive(Debug, Clone, PartialEq, Eq, Hash)]
632struct InstantiationKey {
633    quantifier: TermId,
634    binding: Vec<(Spur, TermId)>,
635}
636
637impl InstantiationKey {
638    fn from_instantiation(inst: &Instantiation) -> Self {
639        let mut binding: Vec<_> = inst.substitution.iter().map(|(&k, &v)| (k, v)).collect();
640        binding.sort_by_key(|(k, _)| *k);
641        Self {
642            quantifier: inst.quantifier,
643            binding,
644        }
645    }
646}
647
648/// Instantiation engine that coordinates all instantiation strategies
649#[derive(Debug)]
650pub struct InstantiationEngine {
651    /// Main quantifier instantiator
652    instantiator: QuantifierInstantiator,
653    /// Pattern matcher
654    pattern_matcher: PatternMatcher,
655    /// Enumerative instantiation
656    enumerative: EnumerativeInstantiator,
657    /// Statistics
658    stats: EngineStats,
659}
660
661impl InstantiationEngine {
662    /// Create a new instantiation engine
663    pub fn new() -> Self {
664        Self {
665            instantiator: QuantifierInstantiator::new(),
666            pattern_matcher: PatternMatcher::new(),
667            enumerative: EnumerativeInstantiator::new(),
668            stats: EngineStats::default(),
669        }
670    }
671
672    /// Generate instantiations for a quantifier
673    pub fn instantiate(
674        &mut self,
675        quantifier: &QuantifiedFormula,
676        model: &CompletedModel,
677        manager: &mut TermManager,
678    ) -> Vec<Instantiation> {
679        let mut instantiations = Vec::new();
680
681        // Strategy 1: Model-based instantiation
682        let model_based = self
683            .instantiator
684            .instantiate_from_model(quantifier, model, manager);
685        instantiations.extend(model_based);
686
687        // Strategy 2: Pattern-based instantiation (if patterns exist)
688        if !quantifier.patterns.is_empty() {
689            let pattern_based = self
690                .pattern_matcher
691                .match_patterns(quantifier, model, manager);
692            instantiations.extend(pattern_based);
693        }
694
695        // Strategy 3: Enumerative instantiation (as fallback, limited)
696        if instantiations.is_empty() {
697            let enumerative = self.enumerative.enumerate(quantifier, model, manager, 3);
698            instantiations.extend(enumerative);
699        }
700
701        self.stats.num_instantiations += instantiations.len();
702        instantiations
703    }
704
705    /// Clear all caches
706    pub fn clear_caches(&mut self) {
707        self.instantiator.clear_cache();
708        self.pattern_matcher.clear_cache();
709    }
710
711    /// Get statistics
712    pub fn stats(&self) -> &EngineStats {
713        &self.stats
714    }
715}
716
717impl Default for InstantiationEngine {
718    fn default() -> Self {
719        Self::new()
720    }
721}
722
723/// Pattern matcher for E-matching style instantiation
724#[derive(Debug)]
725struct PatternMatcher {
726    /// Match cache
727    cache: FxHashMap<TermId, Vec<FxHashMap<Spur, TermId>>>,
728}
729
730impl PatternMatcher {
731    fn new() -> Self {
732        Self {
733            cache: FxHashMap::default(),
734        }
735    }
736
737    fn match_patterns(
738        &mut self,
739        quantifier: &QuantifiedFormula,
740        model: &CompletedModel,
741        manager: &mut TermManager,
742    ) -> Vec<Instantiation> {
743        let mut instantiations = Vec::new();
744
745        // Extract patterns
746        let patterns = InstantiationPattern::extract_patterns(quantifier, manager);
747
748        // Match each pattern
749        for pattern in patterns {
750            let matches = self.match_pattern(&pattern, model, manager);
751            for assignment in matches {
752                let ground_body = self.apply_substitution(quantifier.body, &assignment, manager);
753                let inst = Instantiation::with_reason(
754                    quantifier.term,
755                    assignment,
756                    ground_body,
757                    model.generation,
758                    InstantiationReason::EMatching,
759                );
760                instantiations.push(inst);
761            }
762        }
763
764        instantiations
765    }
766
767    fn match_pattern(
768        &self,
769        _pattern: &InstantiationPattern,
770        _model: &CompletedModel,
771        _manager: &TermManager,
772    ) -> Vec<FxHashMap<Spur, TermId>> {
773        // Simplified pattern matching
774        // A full implementation would use E-matching algorithms
775        Vec::new()
776    }
777
778    fn apply_substitution(
779        &self,
780        term: TermId,
781        subst: &FxHashMap<Spur, TermId>,
782        manager: &mut TermManager,
783    ) -> TermId {
784        // Reuse implementation from QuantifierInstantiator
785        let instantiator = QuantifierInstantiator::new();
786        instantiator.apply_substitution(term, subst, manager)
787    }
788
789    fn clear_cache(&mut self) {
790        self.cache.clear();
791    }
792}
793
794/// Enumerative instantiator (brute-force small domain)
795#[derive(Debug)]
796struct EnumerativeInstantiator;
797
798impl EnumerativeInstantiator {
799    fn new() -> Self {
800        Self
801    }
802
803    fn enumerate(
804        &self,
805        quantifier: &QuantifiedFormula,
806        model: &CompletedModel,
807        manager: &mut TermManager,
808        max_per_var: usize,
809    ) -> Vec<Instantiation> {
810        let mut instantiations = Vec::new();
811
812        // Build small domains for each variable
813        let domains = self.build_small_domains(&quantifier.bound_vars, model, manager, max_per_var);
814
815        // Enumerate all combinations
816        let combinations = self.enumerate_combinations(&domains);
817
818        for combo in combinations {
819            let mut assignment = FxHashMap::default();
820            for (i, &value) in combo.iter().enumerate() {
821                if let Some((name, _)) = quantifier.bound_vars.get(i) {
822                    assignment.insert(*name, value);
823                }
824            }
825
826            let instantiator = QuantifierInstantiator::new();
827            let ground_body =
828                instantiator.apply_substitution(quantifier.body, &assignment, manager);
829
830            let inst = Instantiation::with_reason(
831                quantifier.term,
832                assignment,
833                ground_body,
834                model.generation,
835                InstantiationReason::Enumerative,
836            );
837            instantiations.push(inst);
838        }
839
840        instantiations
841    }
842
843    fn build_small_domains(
844        &self,
845        bound_vars: &[(Spur, SortId)],
846        model: &CompletedModel,
847        manager: &mut TermManager,
848        max_per_var: usize,
849    ) -> Vec<Vec<TermId>> {
850        let mut domains = Vec::new();
851
852        for &(_name, sort) in bound_vars {
853            let mut domain = Vec::new();
854
855            // Use universe if available
856            if let Some(universe) = model.universe(sort) {
857                domain.extend_from_slice(universe);
858            }
859
860            // Add default values
861            if sort == manager.sorts.int_sort {
862                for i in 0..max_per_var.min(3) {
863                    domain.push(manager.mk_int(BigInt::from(i as i32)));
864                }
865            } else if sort == manager.sorts.bool_sort {
866                domain.push(manager.mk_true());
867                domain.push(manager.mk_false());
868            }
869
870            domain.truncate(max_per_var);
871            domains.push(domain);
872        }
873
874        domains
875    }
876
877    fn enumerate_combinations(&self, domains: &[Vec<TermId>]) -> Vec<Vec<TermId>> {
878        if domains.is_empty() {
879            return vec![vec![]];
880        }
881
882        let mut results = Vec::new();
883        let mut indices = vec![0usize; domains.len()];
884        let max_results = 100; // Limit total combinations
885
886        loop {
887            let combo: Vec<TermId> = indices
888                .iter()
889                .enumerate()
890                .filter_map(|(i, &idx)| domains.get(i).and_then(|d| d.get(idx).copied()))
891                .collect();
892
893            if combo.len() == domains.len() {
894                results.push(combo);
895            }
896
897            if results.len() >= max_results {
898                break;
899            }
900
901            // Increment
902            let mut carry = true;
903            for (i, idx) in indices.iter_mut().enumerate() {
904                if carry {
905                    *idx += 1;
906                    let limit = domains.get(i).map_or(1, |d| d.len());
907                    if *idx >= limit {
908                        *idx = 0;
909                    } else {
910                        carry = false;
911                    }
912                }
913            }
914
915            if carry {
916                break;
917            }
918        }
919
920        results
921    }
922}
923
924/// Statistics for instantiator
925#[derive(Debug, Clone, Default)]
926pub struct InstantiatorStats {
927    pub num_instantiation_attempts: usize,
928    pub num_instantiations_generated: usize,
929    pub num_duplicates_filtered: usize,
930}
931
932/// Statistics for engine
933#[derive(Debug, Clone, Default)]
934pub struct EngineStats {
935    pub num_instantiations: usize,
936}
937
938#[cfg(test)]
939mod tests {
940    use super::*;
941    use lasso::Key;
942
943    #[test]
944    fn test_instantiation_context_creation() {
945        let manager = TermManager::new();
946        let ctx = InstantiationContext::new(manager);
947        assert_eq!(ctx.generation, 0);
948    }
949
950    #[test]
951    fn test_instantiation_context_generation() {
952        let manager = TermManager::new();
953        let mut ctx = InstantiationContext::new(manager);
954        ctx.next_generation();
955        assert_eq!(ctx.generation, 1);
956    }
957
958    #[test]
959    fn test_instantiation_pattern_creation() {
960        let pattern = InstantiationPattern::new(vec![TermId::new(1)]);
961        assert_eq!(pattern.terms.len(), 1);
962        assert_eq!(pattern.num_vars, 0);
963    }
964
965    #[test]
966    fn test_quantifier_instantiator_creation() {
967        let inst = QuantifierInstantiator::new();
968        assert_eq!(inst.stats.num_instantiation_attempts, 0);
969    }
970
971    #[test]
972    fn test_instantiation_key_equality() {
973        let key1 = InstantiationKey {
974            quantifier: TermId::new(1),
975            binding: vec![(
976                Spur::try_from_usize(1).expect("valid spur"),
977                TermId::new(10),
978            )],
979        };
980        let key2 = InstantiationKey {
981            quantifier: TermId::new(1),
982            binding: vec![(
983                Spur::try_from_usize(1).expect("valid spur"),
984                TermId::new(10),
985            )],
986        };
987        assert_eq!(key1, key2);
988    }
989
990    #[test]
991    fn test_instantiation_engine_creation() {
992        let engine = InstantiationEngine::new();
993        assert_eq!(engine.stats.num_instantiations, 0);
994    }
995
996    #[test]
997    fn test_pattern_matcher_creation() {
998        let matcher = PatternMatcher::new();
999        assert_eq!(matcher.cache.len(), 0);
1000    }
1001
1002    #[test]
1003    fn test_enumerative_instantiator_creation() {
1004        let _enum_inst = EnumerativeInstantiator::new();
1005    }
1006}