1use 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#[derive(Debug, Clone)]
31pub struct CounterExample {
32 pub quantifier: TermId,
34 pub assignment: FxHashMap<Spur, TermId>,
36 pub witnesses: Vec<TermId>,
38 pub body_value: Option<TermId>,
40 pub quality: f64,
42 pub generation: u32,
44}
45
46impl CounterExample {
47 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 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 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 let size_factor = 1.0 / (1.0 + total_size as f64);
91 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#[derive(Debug)]
151pub struct CounterExampleGenerator {
152 max_cex_per_quantifier: usize,
154 max_candidates_per_var: usize,
156 max_search_time: Duration,
158 generation_bound: u32,
160 stats: CexStats,
162 candidate_cache: FxHashMap<SortId, Vec<TermId>>,
164}
165
166impl CounterExampleGenerator {
167 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 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 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 let candidates = self.build_candidate_lists(&quantifier.bound_vars, model, manager);
201
202 let combinations = self.enumerate_combinations(
204 &candidates,
205 self.max_candidates_per_var,
206 self.max_cex_per_quantifier * 20, );
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 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 let substituted = self.apply_substitution(quantifier.body, &assignment, manager);
231 let evaluated = self.evaluate_under_model(substituted, model, manager);
232
233 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 counterexamples.sort_by(|a, b| {
246 b.quality
247 .partial_cmp(&a.quality)
248 .unwrap_or(std::cmp::Ordering::Equal)
249 });
250
251 counterexamples.truncate(self.max_cex_per_quantifier);
253
254 self.stats.total_time += start_time.elapsed();
255
256 counterexamples
257 }
258
259 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 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 if let Some(universe) = model.universe(sort) {
279 candidates.extend_from_slice(universe);
280 }
281
282 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 self.add_default_candidates(sort, &mut candidates, manager);
294
295 candidates.truncate(self.max_candidates_per_var);
297
298 self.candidate_cache.insert(sort, candidates.clone());
300
301 result.push(candidates);
302 }
303
304 result
305 }
306
307 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 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 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 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 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 break;
381 }
382 }
383
384 results
385 }
386
387 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 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 _ => term,
515 };
516
517 cache.insert(term, result);
518 result
519 }
520
521 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 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 manager.simplify(term)
635 }
636 };
637
638 cache.insert(term, result);
639 result
640 }
641
642 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 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 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 fn eval_gt(&self, lhs: TermId, rhs: TermId, manager: &mut TermManager) -> TermId {
732 self.eval_lt(rhs, lhs, manager)
733 }
734
735 fn eval_ge(&self, lhs: TermId, rhs: TermId, manager: &mut TermManager) -> TermId {
737 self.eval_le(rhs, lhs, manager)
738 }
739
740 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 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 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 matches!(eval_t.kind, TermKind::False)
806 } else {
807 matches!(eval_t.kind, TermKind::True)
809 }
810 }
811
812 pub fn set_generation_bound(&mut self, bound: u32) {
814 self.generation_bound = bound;
815 }
816
817 pub fn clear_cache(&mut self) {
819 self.candidate_cache.clear();
820 }
821
822 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
836pub enum RefinementStrategy {
837 None,
839 BlockCounterexamples,
841 ConflictLearning,
843 Generalization,
845}
846
847#[derive(Debug, Clone, Default)]
849pub struct CexStats {
850 pub num_searches: usize,
852 pub num_counterexamples_found: usize,
854 pub num_combinations_tried: usize,
856 pub num_timeouts: usize,
858 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); }
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}