Skip to main content

oxiz_solver/mbqi/
counterexample.rs

1//! Counter-example Generation and Refinement
2//!
3//! This module implements counterexample generation for MBQI. A counterexample
4//! is an assignment to quantified variables that falsifies the quantifier body
5//! under the current model.
6//!
7//! For a universal quantifier ∀x.φ(x), a counterexample is an assignment σ such
8//! that ¬φ(σ(x)) holds in the current model.
9//!
10//! # Strategy
11//!
12//! 1. **Model Evaluation**: Evaluate quantifier body under candidate assignments
13//! 2. **Satisfiability Checking**: Use auxiliary solver to find counterexamples
14//! 3. **Conflict Analysis**: When no counterexamples exist, analyze why
15//! 4. **Refinement**: Use counterexamples to refine the search space
16
17use lasso::Spur;
18use num_bigint::BigInt;
19use oxiz_core::ast::{TermId, TermKind, TermManager};
20use oxiz_core::sort::SortId;
21use rustc_hash::{FxHashMap, FxHashSet};
22use smallvec::SmallVec;
23use std::fmt;
24use std::time::{Duration, Instant};
25
26use super::model_completion::CompletedModel;
27use super::{Instantiation, InstantiationReason, QuantifiedFormula};
28
29/// A counter-example to a quantified formula
30#[derive(Debug, Clone)]
31pub struct CounterExample {
32    /// The quantifier this is a counterexample for
33    pub quantifier: TermId,
34    /// Assignment to bound variables
35    pub assignment: FxHashMap<Spur, TermId>,
36    /// Witness terms (the concrete values assigned)
37    pub witnesses: Vec<TermId>,
38    /// Evaluation of the body under this assignment
39    pub body_value: Option<TermId>,
40    /// Quality score (higher = better counterexample)
41    pub quality: f64,
42    /// Generation at which this was found
43    pub generation: u32,
44}
45
46impl CounterExample {
47    /// Create a new counter-example
48    pub fn new(
49        quantifier: TermId,
50        assignment: FxHashMap<Spur, TermId>,
51        witnesses: Vec<TermId>,
52        generation: u32,
53    ) -> Self {
54        Self {
55            quantifier,
56            assignment,
57            witnesses,
58            body_value: None,
59            quality: 1.0,
60            generation,
61        }
62    }
63
64    /// Convert to an instantiation
65    pub fn to_instantiation(&self, result: TermId) -> Instantiation {
66        Instantiation::with_reason(
67            self.quantifier,
68            self.assignment.clone(),
69            result,
70            self.generation,
71            InstantiationReason::Conflict,
72        )
73    }
74
75    /// Calculate quality score based on term complexity
76    pub fn calculate_quality(&mut self, manager: &TermManager) {
77        let mut total_size = 0;
78        let mut num_constants = 0;
79
80        for &witness in &self.witnesses {
81            let size = self.term_size(witness, manager);
82            total_size += size;
83
84            if self.is_constant(witness, manager) {
85                num_constants += 1;
86            }
87        }
88
89        // Prefer simpler terms (smaller size)
90        let size_factor = 1.0 / (1.0 + total_size as f64);
91        // Prefer more constants (ground terms)
92        let const_factor = 1.0 + (num_constants as f64 / self.witnesses.len().max(1) as f64);
93
94        self.quality = size_factor * const_factor;
95    }
96
97    fn term_size(&self, term: TermId, manager: &TermManager) -> usize {
98        let mut visited = FxHashSet::default();
99        self.term_size_rec(term, manager, &mut visited)
100    }
101
102    fn term_size_rec(
103        &self,
104        term: TermId,
105        manager: &TermManager,
106        visited: &mut FxHashSet<TermId>,
107    ) -> usize {
108        if visited.contains(&term) {
109            return 0;
110        }
111        visited.insert(term);
112
113        let Some(t) = manager.get(term) else {
114            return 1;
115        };
116
117        let children_size = match &t.kind {
118            TermKind::And(args) | TermKind::Or(args) => args
119                .iter()
120                .map(|&arg| self.term_size_rec(arg, manager, visited))
121                .sum(),
122            TermKind::Not(arg) | TermKind::Neg(arg) => self.term_size_rec(*arg, manager, visited),
123            TermKind::Eq(lhs, rhs) | TermKind::Lt(lhs, rhs) => {
124                self.term_size_rec(*lhs, manager, visited)
125                    + self.term_size_rec(*rhs, manager, visited)
126            }
127            _ => 0,
128        };
129
130        1 + children_size
131    }
132
133    fn is_constant(&self, term: TermId, manager: &TermManager) -> bool {
134        let Some(t) = manager.get(term) else {
135            return false;
136        };
137
138        matches!(
139            t.kind,
140            TermKind::True
141                | TermKind::False
142                | TermKind::IntConst(_)
143                | TermKind::RealConst(_)
144                | TermKind::BitVecConst { .. }
145        )
146    }
147}
148
149/// Counter-example generator
150#[derive(Debug)]
151pub struct CounterExampleGenerator {
152    /// Maximum number of counterexamples to generate per quantifier
153    max_cex_per_quantifier: usize,
154    /// Maximum number of candidates to try per variable
155    max_candidates_per_var: usize,
156    /// Maximum total search time
157    max_search_time: Duration,
158    /// Current generation bound for term selection
159    generation_bound: u32,
160    /// Statistics
161    stats: CexStats,
162    /// Candidate cache
163    candidate_cache: FxHashMap<SortId, Vec<TermId>>,
164}
165
166impl CounterExampleGenerator {
167    /// Create a new counterexample generator
168    pub fn new() -> Self {
169        Self {
170            max_cex_per_quantifier: 5,
171            max_candidates_per_var: 10,
172            max_search_time: Duration::from_secs(1),
173            generation_bound: 0,
174            stats: CexStats::default(),
175            candidate_cache: FxHashMap::default(),
176        }
177    }
178
179    /// Create with custom limits
180    pub fn with_limits(max_cex: usize, max_candidates: usize, max_time: Duration) -> Self {
181        let mut generator = Self::new();
182        generator.max_cex_per_quantifier = max_cex;
183        generator.max_candidates_per_var = max_candidates;
184        generator.max_search_time = max_time;
185        generator
186    }
187
188    /// Generate counterexamples for a quantifier
189    pub fn generate(
190        &mut self,
191        quantifier: &QuantifiedFormula,
192        model: &CompletedModel,
193        manager: &mut TermManager,
194    ) -> Vec<CounterExample> {
195        let start_time = Instant::now();
196        let mut counterexamples = Vec::new();
197        self.stats.num_searches += 1;
198
199        // Build candidate lists for each bound variable
200        let candidates = self.build_candidate_lists(&quantifier.bound_vars, model, manager);
201
202        // Enumerate combinations of candidates
203        let combinations = self.enumerate_combinations(
204            &candidates,
205            self.max_candidates_per_var,
206            self.max_cex_per_quantifier * 20, // Generate more combinations than we need
207        );
208
209        self.stats.num_combinations_tried += combinations.len();
210
211        for combo in combinations {
212            if start_time.elapsed() > self.max_search_time {
213                self.stats.num_timeouts += 1;
214                break;
215            }
216
217            if counterexamples.len() >= self.max_cex_per_quantifier {
218                break;
219            }
220
221            // Build assignment from combination
222            let mut assignment = FxHashMap::default();
223            for (i, &candidate) in combo.iter().enumerate() {
224                if let Some(var_name) = quantifier.var_name(i) {
225                    assignment.insert(var_name, candidate);
226                }
227            }
228
229            // Apply substitution and evaluate
230            let substituted = self.apply_substitution(quantifier.body, &assignment, manager);
231            let evaluated = self.evaluate_under_model(substituted, model, manager);
232
233            // Check if this is a counterexample
234            if self.is_counterexample(evaluated, quantifier.is_universal, manager) {
235                let mut cex =
236                    CounterExample::new(quantifier.term, assignment, combo, model.generation);
237                cex.body_value = Some(evaluated);
238                cex.calculate_quality(manager);
239                counterexamples.push(cex);
240                self.stats.num_counterexamples_found += 1;
241            }
242        }
243
244        // Sort by quality (best first)
245        counterexamples.sort_by(|a, b| {
246            b.quality
247                .partial_cmp(&a.quality)
248                .unwrap_or(std::cmp::Ordering::Equal)
249        });
250
251        // Limit to max
252        counterexamples.truncate(self.max_cex_per_quantifier);
253
254        self.stats.total_time += start_time.elapsed();
255
256        counterexamples
257    }
258
259    /// Build candidate lists for bound variables
260    fn build_candidate_lists(
261        &mut self,
262        bound_vars: &[(Spur, SortId)],
263        model: &CompletedModel,
264        manager: &mut TermManager,
265    ) -> Vec<Vec<TermId>> {
266        let mut result = Vec::new();
267
268        for &(_var_name, sort) in bound_vars {
269            // Check cache first
270            if let Some(cached) = self.candidate_cache.get(&sort) {
271                result.push(cached.clone());
272                continue;
273            }
274
275            let mut candidates = Vec::new();
276
277            // Strategy 1: Use values from the universe (for uninterpreted sorts)
278            if let Some(universe) = model.universe(sort) {
279                candidates.extend_from_slice(universe);
280            }
281
282            // Strategy 2: Use values from the model
283            for (&term, &value) in &model.assignments {
284                if let Some(t) = manager.get(term)
285                    && t.sort == sort
286                    && !candidates.contains(&value)
287                {
288                    candidates.push(value);
289                }
290            }
291
292            // Strategy 3: Add default values based on sort
293            self.add_default_candidates(sort, &mut candidates, manager);
294
295            // Limit candidates
296            candidates.truncate(self.max_candidates_per_var);
297
298            // Cache for future use
299            self.candidate_cache.insert(sort, candidates.clone());
300
301            result.push(candidates);
302        }
303
304        result
305    }
306
307    /// Add default candidate values for a sort
308    fn add_default_candidates(
309        &self,
310        sort: SortId,
311        candidates: &mut Vec<TermId>,
312        manager: &mut TermManager,
313    ) {
314        if sort == manager.sorts.int_sort {
315            // Add small integers
316            for i in -2..=5 {
317                let val = manager.mk_int(BigInt::from(i));
318                if !candidates.contains(&val) {
319                    candidates.push(val);
320                }
321            }
322        } else if sort == manager.sorts.bool_sort {
323            let true_val = manager.mk_true();
324            let false_val = manager.mk_false();
325            if !candidates.contains(&true_val) {
326                candidates.push(true_val);
327            }
328            if !candidates.contains(&false_val) {
329                candidates.push(false_val);
330            }
331        }
332    }
333
334    /// Enumerate combinations of candidates
335    fn enumerate_combinations(
336        &self,
337        candidates: &[Vec<TermId>],
338        max_per_dim: usize,
339        max_total: usize,
340    ) -> Vec<Vec<TermId>> {
341        if candidates.is_empty() {
342            return vec![vec![]];
343        }
344
345        let mut results = Vec::new();
346        let mut indices = vec![0usize; candidates.len()];
347
348        loop {
349            // Build current combination
350            let combo: Vec<TermId> = indices
351                .iter()
352                .enumerate()
353                .filter_map(|(i, &idx)| candidates.get(i).and_then(|c| c.get(idx).copied()))
354                .collect();
355
356            if combo.len() == candidates.len() {
357                results.push(combo);
358            }
359
360            if results.len() >= max_total {
361                break;
362            }
363
364            // Increment indices (like odometer)
365            let mut carry = true;
366            for (i, idx) in indices.iter_mut().enumerate() {
367                if carry {
368                    *idx += 1;
369                    let limit = candidates.get(i).map_or(1, |c| c.len().min(max_per_dim));
370                    if *idx >= limit {
371                        *idx = 0;
372                    } else {
373                        carry = false;
374                    }
375                }
376            }
377
378            if carry {
379                // Overflow - tried all combinations
380                break;
381            }
382        }
383
384        results
385    }
386
387    /// Apply substitution to a term
388    fn apply_substitution(
389        &self,
390        term: TermId,
391        subst: &FxHashMap<Spur, TermId>,
392        manager: &mut TermManager,
393    ) -> TermId {
394        let mut cache = FxHashMap::default();
395        self.apply_substitution_cached(term, subst, manager, &mut cache)
396    }
397
398    fn apply_substitution_cached(
399        &self,
400        term: TermId,
401        subst: &FxHashMap<Spur, TermId>,
402        manager: &mut TermManager,
403        cache: &mut FxHashMap<TermId, TermId>,
404    ) -> TermId {
405        if let Some(&cached) = cache.get(&term) {
406            return cached;
407        }
408
409        let Some(t) = manager.get(term).cloned() else {
410            return term;
411        };
412
413        let result = match &t.kind {
414            TermKind::Var(name) => {
415                // Check if this variable should be substituted
416                subst.get(name).copied().unwrap_or(term)
417            }
418            TermKind::Not(arg) => {
419                let new_arg = self.apply_substitution_cached(*arg, subst, manager, cache);
420                manager.mk_not(new_arg)
421            }
422            TermKind::And(args) => {
423                let new_args: Vec<_> = args
424                    .iter()
425                    .map(|&a| self.apply_substitution_cached(a, subst, manager, cache))
426                    .collect();
427                manager.mk_and(new_args)
428            }
429            TermKind::Or(args) => {
430                let new_args: Vec<_> = args
431                    .iter()
432                    .map(|&a| self.apply_substitution_cached(a, subst, manager, cache))
433                    .collect();
434                manager.mk_or(new_args)
435            }
436            TermKind::Implies(lhs, rhs) => {
437                let new_lhs = self.apply_substitution_cached(*lhs, subst, manager, cache);
438                let new_rhs = self.apply_substitution_cached(*rhs, subst, manager, cache);
439                manager.mk_implies(new_lhs, new_rhs)
440            }
441            TermKind::Eq(lhs, rhs) => {
442                let new_lhs = self.apply_substitution_cached(*lhs, subst, manager, cache);
443                let new_rhs = self.apply_substitution_cached(*rhs, subst, manager, cache);
444                manager.mk_eq(new_lhs, new_rhs)
445            }
446            TermKind::Lt(lhs, rhs) => {
447                let new_lhs = self.apply_substitution_cached(*lhs, subst, manager, cache);
448                let new_rhs = self.apply_substitution_cached(*rhs, subst, manager, cache);
449                manager.mk_lt(new_lhs, new_rhs)
450            }
451            TermKind::Le(lhs, rhs) => {
452                let new_lhs = self.apply_substitution_cached(*lhs, subst, manager, cache);
453                let new_rhs = self.apply_substitution_cached(*rhs, subst, manager, cache);
454                manager.mk_le(new_lhs, new_rhs)
455            }
456            TermKind::Gt(lhs, rhs) => {
457                let new_lhs = self.apply_substitution_cached(*lhs, subst, manager, cache);
458                let new_rhs = self.apply_substitution_cached(*rhs, subst, manager, cache);
459                manager.mk_gt(new_lhs, new_rhs)
460            }
461            TermKind::Ge(lhs, rhs) => {
462                let new_lhs = self.apply_substitution_cached(*lhs, subst, manager, cache);
463                let new_rhs = self.apply_substitution_cached(*rhs, subst, manager, cache);
464                manager.mk_ge(new_lhs, new_rhs)
465            }
466            TermKind::Add(args) => {
467                let new_args: SmallVec<[TermId; 4]> = args
468                    .iter()
469                    .map(|&a| self.apply_substitution_cached(a, subst, manager, cache))
470                    .collect();
471                manager.mk_add(new_args)
472            }
473            TermKind::Sub(lhs, rhs) => {
474                let new_lhs = self.apply_substitution_cached(*lhs, subst, manager, cache);
475                let new_rhs = self.apply_substitution_cached(*rhs, subst, manager, cache);
476                manager.mk_sub(new_lhs, new_rhs)
477            }
478            TermKind::Mul(args) => {
479                let new_args: SmallVec<[TermId; 4]> = args
480                    .iter()
481                    .map(|&a| self.apply_substitution_cached(a, subst, manager, cache))
482                    .collect();
483                manager.mk_mul(new_args)
484            }
485            TermKind::Div(lhs, rhs) => {
486                let new_lhs = self.apply_substitution_cached(*lhs, subst, manager, cache);
487                let new_rhs = self.apply_substitution_cached(*rhs, subst, manager, cache);
488                manager.mk_div(new_lhs, new_rhs)
489            }
490            TermKind::Mod(lhs, rhs) => {
491                let new_lhs = self.apply_substitution_cached(*lhs, subst, manager, cache);
492                let new_rhs = self.apply_substitution_cached(*rhs, subst, manager, cache);
493                manager.mk_mod(new_lhs, new_rhs)
494            }
495            TermKind::Neg(arg) => {
496                let new_arg = self.apply_substitution_cached(*arg, subst, manager, cache);
497                manager.mk_neg(new_arg)
498            }
499            TermKind::Ite(cond, then_br, else_br) => {
500                let new_cond = self.apply_substitution_cached(*cond, subst, manager, cache);
501                let new_then = self.apply_substitution_cached(*then_br, subst, manager, cache);
502                let new_else = self.apply_substitution_cached(*else_br, subst, manager, cache);
503                manager.mk_ite(new_cond, new_then, new_else)
504            }
505            TermKind::Apply { func, args } => {
506                let func_name = manager.resolve_str(*func).to_string();
507                let new_args: SmallVec<[TermId; 4]> = args
508                    .iter()
509                    .map(|&a| self.apply_substitution_cached(a, subst, manager, cache))
510                    .collect();
511                manager.mk_apply(&func_name, new_args, t.sort)
512            }
513            // Constants and other terms don't need substitution
514            _ => term,
515        };
516
517        cache.insert(term, result);
518        result
519    }
520
521    /// Evaluate a term under a model
522    fn evaluate_under_model(
523        &self,
524        term: TermId,
525        model: &CompletedModel,
526        manager: &mut TermManager,
527    ) -> TermId {
528        let mut cache = FxHashMap::default();
529        self.evaluate_under_model_cached(term, model, manager, &mut cache)
530    }
531
532    fn evaluate_under_model_cached(
533        &self,
534        term: TermId,
535        model: &CompletedModel,
536        manager: &mut TermManager,
537        cache: &mut FxHashMap<TermId, TermId>,
538    ) -> TermId {
539        if let Some(&cached) = cache.get(&term) {
540            return cached;
541        }
542
543        // Check if we have a direct model value
544        if let Some(val) = model.eval(term) {
545            cache.insert(term, val);
546            return val;
547        }
548
549        let Some(t) = manager.get(term).cloned() else {
550            return term;
551        };
552
553        let result = match &t.kind {
554            TermKind::True | TermKind::False | TermKind::IntConst(_) | TermKind::RealConst(_) => {
555                term
556            }
557            TermKind::Not(arg) => {
558                let eval_arg = self.evaluate_under_model_cached(*arg, model, manager, cache);
559                if let Some(arg_t) = manager.get(eval_arg) {
560                    match arg_t.kind {
561                        TermKind::True => manager.mk_false(),
562                        TermKind::False => manager.mk_true(),
563                        _ => manager.mk_not(eval_arg),
564                    }
565                } else {
566                    manager.mk_not(eval_arg)
567                }
568            }
569            TermKind::And(args) => {
570                for &arg in args.iter() {
571                    let eval_arg = self.evaluate_under_model_cached(arg, model, manager, cache);
572                    if let Some(arg_t) = manager.get(eval_arg)
573                        && matches!(arg_t.kind, TermKind::False)
574                    {
575                        cache.insert(term, manager.mk_false());
576                        return manager.mk_false();
577                    }
578                }
579                manager.mk_true()
580            }
581            TermKind::Or(args) => {
582                for &arg in args.iter() {
583                    let eval_arg = self.evaluate_under_model_cached(arg, model, manager, cache);
584                    if let Some(arg_t) = manager.get(eval_arg)
585                        && matches!(arg_t.kind, TermKind::True)
586                    {
587                        cache.insert(term, manager.mk_true());
588                        return manager.mk_true();
589                    }
590                }
591                manager.mk_false()
592            }
593            TermKind::Eq(lhs, rhs) => {
594                let eval_lhs = self.evaluate_under_model_cached(*lhs, model, manager, cache);
595                let eval_rhs = self.evaluate_under_model_cached(*rhs, model, manager, cache);
596                self.eval_eq(eval_lhs, eval_rhs, manager)
597            }
598            TermKind::Lt(lhs, rhs) => {
599                let eval_lhs = self.evaluate_under_model_cached(*lhs, model, manager, cache);
600                let eval_rhs = self.evaluate_under_model_cached(*rhs, model, manager, cache);
601                self.eval_lt(eval_lhs, eval_rhs, manager)
602            }
603            TermKind::Le(lhs, rhs) => {
604                let eval_lhs = self.evaluate_under_model_cached(*lhs, model, manager, cache);
605                let eval_rhs = self.evaluate_under_model_cached(*rhs, model, manager, cache);
606                self.eval_le(eval_lhs, eval_rhs, manager)
607            }
608            TermKind::Gt(lhs, rhs) => {
609                let eval_lhs = self.evaluate_under_model_cached(*lhs, model, manager, cache);
610                let eval_rhs = self.evaluate_under_model_cached(*rhs, model, manager, cache);
611                self.eval_gt(eval_lhs, eval_rhs, manager)
612            }
613            TermKind::Ge(lhs, rhs) => {
614                let eval_lhs = self.evaluate_under_model_cached(*lhs, model, manager, cache);
615                let eval_rhs = self.evaluate_under_model_cached(*rhs, model, manager, cache);
616                self.eval_ge(eval_lhs, eval_rhs, manager)
617            }
618            TermKind::Add(args) => {
619                let eval_args: SmallVec<[TermId; 4]> = args
620                    .iter()
621                    .map(|&a| self.evaluate_under_model_cached(a, model, manager, cache))
622                    .collect();
623                self.eval_add(&eval_args, manager)
624            }
625            TermKind::Mul(args) => {
626                let eval_args: SmallVec<[TermId; 4]> = args
627                    .iter()
628                    .map(|&a| self.evaluate_under_model_cached(a, model, manager, cache))
629                    .collect();
630                self.eval_mul(&eval_args, manager)
631            }
632            _ => {
633                // For complex terms, try simplification
634                manager.simplify(term)
635            }
636        };
637
638        cache.insert(term, result);
639        result
640    }
641
642    /// Evaluate equality
643    fn eval_eq(&self, lhs: TermId, rhs: TermId, manager: &mut TermManager) -> TermId {
644        if lhs == rhs {
645            return manager.mk_true();
646        }
647
648        let lhs_t = manager.get(lhs);
649        let rhs_t = manager.get(rhs);
650
651        if let (Some(l), Some(r)) = (lhs_t, rhs_t) {
652            match (&l.kind, &r.kind) {
653                (TermKind::IntConst(a), TermKind::IntConst(b)) => {
654                    if a == b {
655                        manager.mk_true()
656                    } else {
657                        manager.mk_false()
658                    }
659                }
660                (TermKind::RealConst(a), TermKind::RealConst(b)) => {
661                    if a == b {
662                        manager.mk_true()
663                    } else {
664                        manager.mk_false()
665                    }
666                }
667                (TermKind::True, TermKind::True) | (TermKind::False, TermKind::False) => {
668                    manager.mk_true()
669                }
670                (TermKind::True, TermKind::False) | (TermKind::False, TermKind::True) => {
671                    manager.mk_false()
672                }
673                _ => manager.mk_eq(lhs, rhs),
674            }
675        } else {
676            manager.mk_eq(lhs, rhs)
677        }
678    }
679
680    /// Evaluate less-than
681    fn eval_lt(&self, lhs: TermId, rhs: TermId, manager: &mut TermManager) -> TermId {
682        let lhs_t = manager.get(lhs);
683        let rhs_t = manager.get(rhs);
684
685        if let (Some(l), Some(r)) = (lhs_t, rhs_t) {
686            if let (TermKind::IntConst(a), TermKind::IntConst(b)) = (&l.kind, &r.kind) {
687                if a < b {
688                    return manager.mk_true();
689                } else {
690                    return manager.mk_false();
691                }
692            }
693            if let (TermKind::RealConst(a), TermKind::RealConst(b)) = (&l.kind, &r.kind) {
694                if a < b {
695                    return manager.mk_true();
696                } else {
697                    return manager.mk_false();
698                }
699            }
700        }
701
702        manager.mk_lt(lhs, rhs)
703    }
704
705    /// Evaluate less-than-or-equal
706    fn eval_le(&self, lhs: TermId, rhs: TermId, manager: &mut TermManager) -> TermId {
707        let lhs_t = manager.get(lhs);
708        let rhs_t = manager.get(rhs);
709
710        if let (Some(l), Some(r)) = (lhs_t, rhs_t) {
711            if let (TermKind::IntConst(a), TermKind::IntConst(b)) = (&l.kind, &r.kind) {
712                if a <= b {
713                    return manager.mk_true();
714                } else {
715                    return manager.mk_false();
716                }
717            }
718            if let (TermKind::RealConst(a), TermKind::RealConst(b)) = (&l.kind, &r.kind) {
719                if a <= b {
720                    return manager.mk_true();
721                } else {
722                    return manager.mk_false();
723                }
724            }
725        }
726
727        manager.mk_le(lhs, rhs)
728    }
729
730    /// Evaluate greater-than
731    fn eval_gt(&self, lhs: TermId, rhs: TermId, manager: &mut TermManager) -> TermId {
732        self.eval_lt(rhs, lhs, manager)
733    }
734
735    /// Evaluate greater-than-or-equal
736    fn eval_ge(&self, lhs: TermId, rhs: TermId, manager: &mut TermManager) -> TermId {
737        self.eval_le(rhs, lhs, manager)
738    }
739
740    /// Evaluate addition
741    fn eval_add(&self, args: &[TermId], manager: &mut TermManager) -> TermId {
742        let mut result = BigInt::from(0);
743        let mut all_ints = true;
744
745        for &arg in args {
746            if let Some(arg_t) = manager.get(arg) {
747                if let TermKind::IntConst(val) = &arg_t.kind {
748                    result += val;
749                } else {
750                    all_ints = false;
751                    break;
752                }
753            } else {
754                all_ints = false;
755                break;
756            }
757        }
758
759        if all_ints {
760            manager.mk_int(result)
761        } else {
762            manager.mk_add(args.iter().copied())
763        }
764    }
765
766    /// Evaluate multiplication
767    fn eval_mul(&self, args: &[TermId], manager: &mut TermManager) -> TermId {
768        let mut result = BigInt::from(1);
769        let mut all_ints = true;
770
771        for &arg in args {
772            if let Some(arg_t) = manager.get(arg) {
773                if let TermKind::IntConst(val) = &arg_t.kind {
774                    result *= val;
775                } else {
776                    all_ints = false;
777                    break;
778                }
779            } else {
780                all_ints = false;
781                break;
782            }
783        }
784
785        if all_ints {
786            manager.mk_int(result)
787        } else {
788            manager.mk_mul(args.iter().copied())
789        }
790    }
791
792    /// Check if an evaluated term is a counterexample
793    fn is_counterexample(
794        &self,
795        evaluated: TermId,
796        is_universal: bool,
797        manager: &TermManager,
798    ) -> bool {
799        let Some(eval_t) = manager.get(evaluated) else {
800            return false;
801        };
802
803        if is_universal {
804            // For ∀x.φ(x), a counterexample is when φ(x) = false
805            matches!(eval_t.kind, TermKind::False)
806        } else {
807            // For ∃x.φ(x), a counterexample is when φ(x) = true
808            matches!(eval_t.kind, TermKind::True)
809        }
810    }
811
812    /// Set generation bound for candidate selection
813    pub fn set_generation_bound(&mut self, bound: u32) {
814        self.generation_bound = bound;
815    }
816
817    /// Clear the candidate cache
818    pub fn clear_cache(&mut self) {
819        self.candidate_cache.clear();
820    }
821
822    /// Get statistics
823    pub fn stats(&self) -> &CexStats {
824        &self.stats
825    }
826}
827
828impl Default for CounterExampleGenerator {
829    fn default() -> Self {
830        Self::new()
831    }
832}
833
834/// Refinement strategy for narrowing the search space
835#[derive(Debug, Clone, Copy, PartialEq, Eq)]
836pub enum RefinementStrategy {
837    /// No refinement
838    None,
839    /// Block found counterexamples
840    BlockCounterexamples,
841    /// Learn from conflicts
842    ConflictLearning,
843    /// Generalize from counterexamples
844    Generalization,
845}
846
847/// Statistics for counterexample generation
848#[derive(Debug, Clone, Default)]
849pub struct CexStats {
850    /// Number of search attempts
851    pub num_searches: usize,
852    /// Number of counterexamples found
853    pub num_counterexamples_found: usize,
854    /// Number of combinations tried
855    pub num_combinations_tried: usize,
856    /// Number of timeouts
857    pub num_timeouts: usize,
858    /// Total time spent
859    pub total_time: Duration,
860}
861
862impl fmt::Display for CexStats {
863    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
864        writeln!(f, "Counterexample Statistics:")?;
865        writeln!(f, "  Searches: {}", self.num_searches)?;
866        writeln!(f, "  CEX found: {}", self.num_counterexamples_found)?;
867        writeln!(f, "  Combinations tried: {}", self.num_combinations_tried)?;
868        writeln!(f, "  Timeouts: {}", self.num_timeouts)?;
869        writeln!(f, "  Total time: {:.2}ms", self.total_time.as_millis())
870    }
871}
872
873#[cfg(test)]
874mod tests {
875    use super::*;
876
877    #[test]
878    fn test_counterexample_creation() {
879        let cex = CounterExample::new(TermId::new(1), FxHashMap::default(), vec![], 0);
880        assert_eq!(cex.quantifier, TermId::new(1));
881        assert_eq!(cex.quality, 1.0);
882    }
883
884    #[test]
885    fn test_cex_generator_creation() {
886        let generator = CounterExampleGenerator::new();
887        assert_eq!(generator.max_cex_per_quantifier, 5);
888        assert_eq!(generator.max_candidates_per_var, 10);
889    }
890
891    #[test]
892    fn test_cex_generator_with_limits() {
893        let generator = CounterExampleGenerator::with_limits(10, 20, Duration::from_secs(2));
894        assert_eq!(generator.max_cex_per_quantifier, 10);
895        assert_eq!(generator.max_candidates_per_var, 20);
896        assert_eq!(generator.max_search_time, Duration::from_secs(2));
897    }
898
899    #[test]
900    fn test_enumerate_combinations_empty() {
901        let generator = CounterExampleGenerator::new();
902        let combos = generator.enumerate_combinations(&[], 10, 100);
903        assert_eq!(combos.len(), 1);
904        assert!(combos[0].is_empty());
905    }
906
907    #[test]
908    fn test_enumerate_combinations_single() {
909        let generator = CounterExampleGenerator::new();
910        let candidates = vec![vec![TermId::new(1), TermId::new(2)]];
911        let combos = generator.enumerate_combinations(&candidates, 10, 100);
912        assert_eq!(combos.len(), 2);
913    }
914
915    #[test]
916    fn test_enumerate_combinations_multiple() {
917        let generator = CounterExampleGenerator::new();
918        let candidates = vec![
919            vec![TermId::new(1), TermId::new(2)],
920            vec![TermId::new(3), TermId::new(4)],
921        ];
922        let combos = generator.enumerate_combinations(&candidates, 10, 100);
923        assert_eq!(combos.len(), 4); // 2 * 2
924    }
925
926    #[test]
927    fn test_enumerate_combinations_limit() {
928        let generator = CounterExampleGenerator::new();
929        let candidates = vec![
930            vec![TermId::new(1), TermId::new(2), TermId::new(3)],
931            vec![TermId::new(4), TermId::new(5), TermId::new(6)],
932        ];
933        let combos = generator.enumerate_combinations(&candidates, 10, 5);
934        assert!(combos.len() <= 5);
935    }
936
937    #[test]
938    fn test_cex_stats_display() {
939        let stats = CexStats {
940            num_searches: 10,
941            num_counterexamples_found: 5,
942            num_combinations_tried: 100,
943            num_timeouts: 1,
944            total_time: Duration::from_millis(500),
945        };
946        let display = format!("{}", stats);
947        assert!(display.contains("Searches: 10"));
948        assert!(display.contains("CEX found: 5"));
949    }
950
951    #[test]
952    fn test_refinement_strategy() {
953        assert_ne!(
954            RefinementStrategy::None,
955            RefinementStrategy::BlockCounterexamples
956        );
957    }
958}