Skip to main content

oxiz_solver/mbqi/
finite_model.rs

1//! Finite Model Finding
2//!
3//! This module implements finite model finding for quantified formulas.
4//! The goal is to find a finite model that satisfies all quantified formulas,
5//! or prove that no such model exists within a given bound.
6//!
7//! # Algorithm
8//!
9//! 1. **Bounded Search**: Search for models up to a maximum universe size
10//! 2. **Symmetry Breaking**: Add constraints to eliminate symmetric models
11//! 3. **Incremental Refinement**: Start with small domains and expand
12//! 4. **Conflict-Driven**: Learn from failed attempts
13
14#![allow(missing_docs)]
15#![allow(dead_code)]
16
17use oxiz_core::ast::{TermId, TermManager};
18use oxiz_core::sort::SortId;
19use rustc_hash::{FxHashMap, FxHashSet};
20use std::fmt;
21
22use super::QuantifiedFormula;
23use super::model_completion::CompletedModel;
24
25/// Size of a finite universe
26#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
27pub struct UniverseSize(pub usize);
28
29impl UniverseSize {
30    /// Create a new universe size
31    pub fn new(size: usize) -> Self {
32        Self(size)
33    }
34
35    /// Get the size
36    pub fn size(&self) -> usize {
37        self.0
38    }
39
40    /// Check if this is a trivial size
41    pub fn is_trivial(&self) -> bool {
42        self.0 <= 1
43    }
44
45    /// Get the next larger size
46    pub fn next(&self) -> Self {
47        Self(self.0 + 1)
48    }
49
50    /// Get total number of possible assignments for n variables
51    pub fn total_assignments(&self, num_vars: usize) -> usize {
52        self.0.pow(num_vars as u32)
53    }
54}
55
56impl fmt::Display for UniverseSize {
57    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58        write!(f, "|U|={}", self.0)
59    }
60}
61
62/// A finite model with bounded universes
63#[derive(Debug, Clone)]
64pub struct FiniteModel {
65    /// Base completed model
66    pub model: CompletedModel,
67    /// Universe sizes for each sort
68    pub universe_sizes: FxHashMap<SortId, UniverseSize>,
69    /// Total number of elements in all universes
70    pub total_elements: usize,
71}
72
73impl FiniteModel {
74    /// Create a new finite model
75    pub fn new(model: CompletedModel) -> Self {
76        let mut total_elements = 0;
77        let mut universe_sizes = FxHashMap::default();
78
79        for (sort, universe) in &model.universes {
80            let size = UniverseSize::new(universe.len());
81            universe_sizes.insert(*sort, size);
82            total_elements += universe.len();
83        }
84
85        Self {
86            model,
87            universe_sizes,
88            total_elements,
89        }
90    }
91
92    /// Get the universe size for a sort
93    pub fn universe_size(&self, sort: SortId) -> Option<UniverseSize> {
94        self.universe_sizes.get(&sort).copied()
95    }
96
97    /// Check if this model satisfies a quantified formula (approximation)
98    pub fn satisfies(
99        &self,
100        _quantifier: &QuantifiedFormula,
101        _manager: &TermManager,
102    ) -> Option<bool> {
103        // Placeholder - full implementation would evaluate the quantifier
104        None
105    }
106}
107
108/// Finite model finder
109#[derive(Debug)]
110pub struct FiniteModelFinder {
111    /// Minimum universe size to try
112    min_size: UniverseSize,
113    /// Maximum universe size to try
114    max_size: UniverseSize,
115    /// Current search bound
116    current_bound: UniverseSize,
117    /// Symmetry breaker
118    symmetry_breaker: SymmetryBreaker,
119    /// Statistics
120    stats: FinderStats,
121    /// Learned constraints (for conflict-driven search)
122    learned_constraints: Vec<TermId>,
123}
124
125impl FiniteModelFinder {
126    /// Create a new finite model finder
127    pub fn new() -> Self {
128        Self {
129            min_size: UniverseSize::new(1),
130            max_size: UniverseSize::new(8),
131            current_bound: UniverseSize::new(1),
132            symmetry_breaker: SymmetryBreaker::new(),
133            stats: FinderStats::default(),
134            learned_constraints: Vec::new(),
135        }
136    }
137
138    /// Create with custom size bounds
139    pub fn with_bounds(min: usize, max: usize) -> Self {
140        let mut finder = Self::new();
141        finder.min_size = UniverseSize::new(min);
142        finder.max_size = UniverseSize::new(max);
143        finder.current_bound = UniverseSize::new(min);
144        finder
145    }
146
147    /// Find a finite model for the given quantifiers
148    pub fn find_model(
149        &mut self,
150        quantifiers: &[QuantifiedFormula],
151        partial_model: &FxHashMap<TermId, TermId>,
152        manager: &mut TermManager,
153    ) -> Result<FiniteModel, FinderError> {
154        self.stats.num_searches += 1;
155        self.current_bound = self.min_size;
156
157        // Incremental search with increasing bounds
158        while self.current_bound <= self.max_size {
159            self.stats.num_iterations += 1;
160
161            match self.find_model_with_bound(
162                quantifiers,
163                partial_model,
164                manager,
165                self.current_bound,
166            ) {
167                Ok(model) => {
168                    self.stats.num_models_found += 1;
169                    return Ok(model);
170                }
171                Err(FinderError::UnsatAtBound) => {
172                    // Try larger bound
173                    self.current_bound = self.current_bound.next();
174                }
175                Err(e) => return Err(e),
176            }
177        }
178
179        Err(FinderError::ExceededMaxBound)
180    }
181
182    /// Find a model with a specific universe size bound
183    fn find_model_with_bound(
184        &mut self,
185        quantifiers: &[QuantifiedFormula],
186        partial_model: &FxHashMap<TermId, TermId>,
187        manager: &mut TermManager,
188        bound: UniverseSize,
189    ) -> Result<FiniteModel, FinderError> {
190        // Build base model
191        let mut model = CompletedModel::new();
192        model.assignments = partial_model.clone();
193
194        // Identify sorts that need finite universes
195        let uninterp_sorts = self.identify_uninterpreted_sorts(quantifiers, manager);
196
197        // Create universes for each sort
198        for sort in uninterp_sorts {
199            let universe = self.create_universe(sort, bound, manager);
200            model.universes.insert(sort, universe);
201        }
202
203        // Add symmetry breaking constraints
204        let sb_constraints =
205            self.symmetry_breaker
206                .generate_constraints(&model, quantifiers, manager);
207        self.learned_constraints.extend(sb_constraints);
208
209        // Check if this model works (simplified)
210        let finite_model = FiniteModel::new(model);
211
212        // Verify against quantifiers
213        for quantifier in quantifiers {
214            if let Some(false) = finite_model.satisfies(quantifier, manager) {
215                return Err(FinderError::UnsatAtBound);
216            }
217        }
218
219        Ok(finite_model)
220    }
221
222    /// Identify uninterpreted sorts in quantifiers
223    fn identify_uninterpreted_sorts(
224        &self,
225        quantifiers: &[QuantifiedFormula],
226        manager: &TermManager,
227    ) -> Vec<SortId> {
228        let mut sorts = Vec::new();
229        let mut seen = FxHashSet::default();
230
231        for quantifier in quantifiers {
232            for &(_name, sort) in &quantifier.bound_vars {
233                if self.is_uninterpreted(sort, manager) && seen.insert(sort) {
234                    sorts.push(sort);
235                }
236            }
237        }
238
239        sorts
240    }
241
242    fn is_uninterpreted(&self, sort: SortId, manager: &TermManager) -> bool {
243        sort != manager.sorts.bool_sort
244            && sort != manager.sorts.int_sort
245            && sort != manager.sorts.real_sort
246    }
247
248    /// Create a universe of the given size for a sort
249    fn create_universe(
250        &self,
251        sort: SortId,
252        size: UniverseSize,
253        manager: &mut TermManager,
254    ) -> Vec<TermId> {
255        let mut universe = Vec::new();
256
257        for i in 0..size.size() {
258            let name = format!("u!{}!{}", sort.0, i);
259            let elem = manager.mk_var(&name, sort);
260            universe.push(elem);
261        }
262
263        universe
264    }
265
266    /// Get statistics
267    pub fn stats(&self) -> &FinderStats {
268        &self.stats
269    }
270
271    /// Reset the finder
272    pub fn reset(&mut self) {
273        self.current_bound = self.min_size;
274        self.learned_constraints.clear();
275    }
276}
277
278impl Default for FiniteModelFinder {
279    fn default() -> Self {
280        Self::new()
281    }
282}
283
284/// Symmetry breaker for reducing search space
285#[derive(Debug)]
286pub struct SymmetryBreaker {
287    /// Strategy for breaking symmetries
288    strategy: SymmetryStrategy,
289    /// Statistics
290    stats: SymmetryStats,
291}
292
293impl SymmetryBreaker {
294    /// Create a new symmetry breaker
295    pub fn new() -> Self {
296        Self {
297            strategy: SymmetryStrategy::LeastNumber,
298            stats: SymmetryStats::default(),
299        }
300    }
301
302    /// Create with specific strategy
303    pub fn with_strategy(strategy: SymmetryStrategy) -> Self {
304        let mut breaker = Self::new();
305        breaker.strategy = strategy;
306        breaker
307    }
308
309    /// Generate symmetry breaking constraints
310    pub fn generate_constraints(
311        &mut self,
312        model: &CompletedModel,
313        quantifiers: &[QuantifiedFormula],
314        manager: &mut TermManager,
315    ) -> Vec<TermId> {
316        let mut constraints = Vec::new();
317
318        match self.strategy {
319            SymmetryStrategy::None => {}
320            SymmetryStrategy::LeastNumber => {
321                constraints.extend(self.generate_least_number_constraints(model, manager));
322            }
323            SymmetryStrategy::OrderConstraints => {
324                constraints.extend(self.generate_order_constraints(model, quantifiers, manager));
325            }
326            SymmetryStrategy::PredecessorConstraints => {
327                constraints.extend(self.generate_predecessor_constraints(model, manager));
328            }
329        }
330
331        self.stats.num_constraints_generated += constraints.len();
332        constraints
333    }
334
335    /// Generate least-number symmetry breaking constraints
336    /// Ensures that if element i is used, all elements 0..i-1 must be used
337    fn generate_least_number_constraints(
338        &self,
339        model: &CompletedModel,
340        manager: &mut TermManager,
341    ) -> Vec<TermId> {
342        let mut constraints = Vec::new();
343
344        for universe in model.universes.values() {
345            // For each pair (i, j) where i < j,
346            // if j is used then i must be used
347            for i in 0..universe.len() {
348                for j in (i + 1)..universe.len() {
349                    // Create: used(j) => used(i)
350                    // We approximate "used" by existence in model
351                    let elem_i = universe[i];
352                    let elem_j = universe[j];
353
354                    // Placeholder constraint (in real impl, would track usage)
355                    // Build arguments first to avoid multiple mutable borrows
356                    let cond = manager.mk_eq(elem_j, elem_j); // j is used
357                    let conseq = manager.mk_eq(elem_i, elem_i); // i is used
358                    let constraint = manager.mk_implies(cond, conseq);
359                    constraints.push(constraint);
360                }
361            }
362        }
363
364        constraints
365    }
366
367    /// Generate order constraints for breaking symmetries
368    fn generate_order_constraints(
369        &self,
370        model: &CompletedModel,
371        _quantifiers: &[QuantifiedFormula],
372        manager: &mut TermManager,
373    ) -> Vec<TermId> {
374        let mut constraints = Vec::new();
375
376        for universe in model.universes.values() {
377            // Add ordering constraints between elements
378            for i in 0..(universe.len().saturating_sub(1)) {
379                let elem_i = universe[i];
380                let elem_j = universe[i + 1];
381
382                // Add: elem_i < elem_j (if applicable)
383                // This is a simplification - real implementation would use
384                // a designated ordering predicate
385                let constraint = manager.mk_lt(elem_i, elem_j);
386                constraints.push(constraint);
387            }
388        }
389
390        constraints
391    }
392
393    /// Generate predecessor constraints
394    fn generate_predecessor_constraints(
395        &self,
396        model: &CompletedModel,
397        manager: &mut TermManager,
398    ) -> Vec<TermId> {
399        let mut constraints = Vec::new();
400
401        for universe in model.universes.values() {
402            if universe.len() < 2 {
403                continue;
404            }
405
406            // For each element except the first, require a distinct predecessor
407            for i in 1..universe.len() {
408                let elem = universe[i];
409                let mut predecessors = Vec::new();
410
411                #[allow(clippy::needless_range_loop)]
412                for j in 0..i {
413                    predecessors.push(universe[j]);
414                }
415
416                // Create disjunction: elem has at least one predecessor
417                if !predecessors.is_empty() {
418                    let mut disj_terms = Vec::new();
419                    for pred in predecessors {
420                        // Create some relation (simplified)
421                        let rel = manager.mk_eq(pred, elem);
422                        disj_terms.push(rel);
423                    }
424                    let constraint = manager.mk_or(disj_terms);
425                    constraints.push(constraint);
426                }
427            }
428        }
429
430        constraints
431    }
432
433    /// Get statistics
434    pub fn stats(&self) -> &SymmetryStats {
435        &self.stats
436    }
437}
438
439impl Default for SymmetryBreaker {
440    fn default() -> Self {
441        Self::new()
442    }
443}
444
445/// Strategy for symmetry breaking
446#[derive(Debug, Clone, Copy, PartialEq, Eq)]
447pub enum SymmetryStrategy {
448    /// No symmetry breaking
449    None,
450    /// Least number heuristic
451    LeastNumber,
452    /// Order-based constraints
453    OrderConstraints,
454    /// Predecessor constraints
455    PredecessorConstraints,
456}
457
458/// Domain enumerator for exploring all possible assignments
459#[derive(Debug)]
460pub struct DomainEnumerator {
461    /// Maximum assignments to enumerate
462    max_assignments: usize,
463    /// Current assignment index
464    current_index: usize,
465}
466
467impl DomainEnumerator {
468    /// Create a new enumerator
469    pub fn new(max_assignments: usize) -> Self {
470        Self {
471            max_assignments,
472            current_index: 0,
473        }
474    }
475
476    /// Enumerate all assignments for variables over domains
477    pub fn enumerate(&mut self, domains: &[Vec<TermId>]) -> Vec<Vec<TermId>> {
478        let mut results = Vec::new();
479        let mut indices = vec![0usize; domains.len()];
480
481        while results.len() < self.max_assignments {
482            // Build current assignment
483            let assignment: Vec<TermId> = indices
484                .iter()
485                .enumerate()
486                .filter_map(|(i, &idx)| domains.get(i).and_then(|d| d.get(idx).copied()))
487                .collect();
488
489            if assignment.len() == domains.len() {
490                results.push(assignment);
491            }
492
493            // Increment indices
494            let mut carry = true;
495            for (i, idx) in indices.iter_mut().enumerate() {
496                if carry {
497                    *idx += 1;
498                    let limit = domains.get(i).map_or(1, |d| d.len());
499                    if *idx >= limit {
500                        *idx = 0;
501                    } else {
502                        carry = false;
503                    }
504                }
505            }
506
507            if carry {
508                break; // Exhausted all assignments
509            }
510        }
511
512        results
513    }
514
515    /// Reset the enumerator
516    pub fn reset(&mut self) {
517        self.current_index = 0;
518    }
519}
520
521/// Bounded model checker for quantified formulas
522#[derive(Debug)]
523pub struct BoundedModelChecker {
524    /// Current depth bound
525    depth_bound: usize,
526    /// Maximum depth to explore
527    max_depth: usize,
528}
529
530impl BoundedModelChecker {
531    /// Create a new bounded model checker
532    pub fn new() -> Self {
533        Self {
534            depth_bound: 0,
535            max_depth: 10,
536        }
537    }
538
539    /// Check if quantifiers are satisfiable within bound
540    pub fn check(
541        &mut self,
542        _quantifiers: &[QuantifiedFormula],
543        _model: &CompletedModel,
544        _manager: &TermManager,
545    ) -> CheckResult {
546        // Placeholder implementation
547        CheckResult::Unknown
548    }
549
550    /// Increment depth bound
551    pub fn increase_bound(&mut self) {
552        self.depth_bound += 1;
553    }
554
555    /// Check if at max depth
556    pub fn at_max_depth(&self) -> bool {
557        self.depth_bound >= self.max_depth
558    }
559}
560
561impl Default for BoundedModelChecker {
562    fn default() -> Self {
563        Self::new()
564    }
565}
566
567/// Result of bounded model checking
568#[derive(Debug, Clone, Copy, PartialEq, Eq)]
569pub enum CheckResult {
570    /// Satisfiable
571    Sat,
572    /// Unsatisfiable within bound
573    Unsat,
574    /// Unknown
575    Unknown,
576}
577
578/// Error during finite model finding
579#[derive(Debug, Clone)]
580pub enum FinderError {
581    /// Unsatisfiable at the current bound
582    UnsatAtBound,
583    /// Exceeded maximum universe size bound
584    ExceededMaxBound,
585    /// Resource limit exceeded
586    ResourceLimit,
587    /// Invalid configuration
588    InvalidConfig(String),
589}
590
591impl fmt::Display for FinderError {
592    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
593        match self {
594            Self::UnsatAtBound => write!(f, "Unsatisfiable at current bound"),
595            Self::ExceededMaxBound => write!(f, "Exceeded maximum universe size"),
596            Self::ResourceLimit => write!(f, "Resource limit exceeded"),
597            Self::InvalidConfig(msg) => write!(f, "Invalid configuration: {}", msg),
598        }
599    }
600}
601
602impl std::error::Error for FinderError {}
603
604/// Statistics for finite model finding
605#[derive(Debug, Clone, Default)]
606pub struct FinderStats {
607    /// Number of search attempts
608    pub num_searches: usize,
609    /// Number of iterations (bound increases)
610    pub num_iterations: usize,
611    /// Number of models found
612    pub num_models_found: usize,
613}
614
615/// Statistics for symmetry breaking
616#[derive(Debug, Clone, Default)]
617pub struct SymmetryStats {
618    /// Number of constraints generated
619    pub num_constraints_generated: usize,
620}
621
622#[cfg(test)]
623mod tests {
624    use super::*;
625
626    #[test]
627    fn test_universe_size_creation() {
628        let size = UniverseSize::new(5);
629        assert_eq!(size.size(), 5);
630    }
631
632    #[test]
633    fn test_universe_size_trivial() {
634        assert!(UniverseSize::new(0).is_trivial());
635        assert!(UniverseSize::new(1).is_trivial());
636        assert!(!UniverseSize::new(2).is_trivial());
637    }
638
639    #[test]
640    fn test_universe_size_next() {
641        let size = UniverseSize::new(3);
642        assert_eq!(size.next().size(), 4);
643    }
644
645    #[test]
646    fn test_universe_size_assignments() {
647        let size = UniverseSize::new(2);
648        assert_eq!(size.total_assignments(3), 8); // 2^3
649    }
650
651    #[test]
652    fn test_universe_size_display() {
653        let size = UniverseSize::new(5);
654        assert_eq!(format!("{}", size), "|U|=5");
655    }
656
657    #[test]
658    fn test_finite_model_creation() {
659        let model = CompletedModel::new();
660        let finite = FiniteModel::new(model);
661        assert_eq!(finite.total_elements, 0);
662    }
663
664    #[test]
665    fn test_finite_model_finder_creation() {
666        let finder = FiniteModelFinder::new();
667        assert_eq!(finder.min_size.size(), 1);
668        assert_eq!(finder.max_size.size(), 8);
669    }
670
671    #[test]
672    fn test_finite_model_finder_with_bounds() {
673        let finder = FiniteModelFinder::with_bounds(2, 10);
674        assert_eq!(finder.min_size.size(), 2);
675        assert_eq!(finder.max_size.size(), 10);
676    }
677
678    #[test]
679    fn test_symmetry_breaker_creation() {
680        let breaker = SymmetryBreaker::new();
681        assert_eq!(breaker.strategy, SymmetryStrategy::LeastNumber);
682    }
683
684    #[test]
685    fn test_symmetry_breaker_with_strategy() {
686        let breaker = SymmetryBreaker::with_strategy(SymmetryStrategy::OrderConstraints);
687        assert_eq!(breaker.strategy, SymmetryStrategy::OrderConstraints);
688    }
689
690    #[test]
691    fn test_symmetry_strategy_equality() {
692        assert_eq!(SymmetryStrategy::None, SymmetryStrategy::None);
693        assert_ne!(SymmetryStrategy::None, SymmetryStrategy::LeastNumber);
694    }
695
696    #[test]
697    fn test_domain_enumerator_creation() {
698        let enumerator = DomainEnumerator::new(100);
699        assert_eq!(enumerator.max_assignments, 100);
700    }
701
702    #[test]
703    fn test_domain_enumerator_enumerate_empty() {
704        let mut enumerator = DomainEnumerator::new(100);
705        let results = enumerator.enumerate(&[]);
706        assert_eq!(results.len(), 1);
707        assert!(results[0].is_empty());
708    }
709
710    #[test]
711    fn test_domain_enumerator_enumerate_single() {
712        let mut enumerator = DomainEnumerator::new(100);
713        let domains = vec![vec![TermId::new(1), TermId::new(2)]];
714        let results = enumerator.enumerate(&domains);
715        assert_eq!(results.len(), 2);
716    }
717
718    #[test]
719    fn test_bounded_model_checker_creation() {
720        let checker = BoundedModelChecker::new();
721        assert_eq!(checker.depth_bound, 0);
722        assert_eq!(checker.max_depth, 10);
723    }
724
725    #[test]
726    fn test_bounded_model_checker_depth() {
727        let mut checker = BoundedModelChecker::new();
728        assert!(!checker.at_max_depth());
729        for _ in 0..10 {
730            checker.increase_bound();
731        }
732        assert!(checker.at_max_depth());
733    }
734
735    #[test]
736    fn test_check_result_equality() {
737        assert_eq!(CheckResult::Sat, CheckResult::Sat);
738        assert_ne!(CheckResult::Sat, CheckResult::Unsat);
739    }
740
741    #[test]
742    fn test_finder_error_display() {
743        let err = FinderError::UnsatAtBound;
744        assert!(format!("{}", err).contains("Unsatisfiable"));
745    }
746}