1#[allow(unused_imports)]
18use crate::prelude::*;
19use core::fmt;
20use num_bigint::BigInt;
21use oxiz_core::ast::{TermId, TermKind, TermManager};
22use oxiz_core::interner::Spur;
23use oxiz_core::sort::SortId;
24use smallvec::SmallVec;
25#[cfg(feature = "std")]
26use std::time::{Duration, Instant};
27
28use super::model_completion::CompletedModel;
29use super::{Instantiation, InstantiationReason, QuantifiedFormula};
30
31#[derive(Debug, Clone)]
33pub struct CounterExample {
34 pub quantifier: TermId,
36 pub assignment: FxHashMap<Spur, TermId>,
38 pub witnesses: Vec<TermId>,
40 pub body_value: Option<TermId>,
42 pub quality: f64,
44 pub generation: u32,
46}
47
48impl CounterExample {
49 pub fn new(
51 quantifier: TermId,
52 assignment: FxHashMap<Spur, TermId>,
53 witnesses: Vec<TermId>,
54 generation: u32,
55 ) -> Self {
56 Self {
57 quantifier,
58 assignment,
59 witnesses,
60 body_value: None,
61 quality: 1.0,
62 generation,
63 }
64 }
65
66 pub fn to_instantiation(&self, result: TermId) -> Instantiation {
68 Instantiation::with_reason(
69 self.quantifier,
70 self.assignment.clone(),
71 result,
72 self.generation,
73 InstantiationReason::Conflict,
74 )
75 }
76
77 pub fn calculate_quality(&mut self, manager: &TermManager) {
79 let mut total_size = 0;
80 let mut num_constants = 0;
81
82 for &witness in &self.witnesses {
83 let size = self.term_size(witness, manager);
84 total_size += size;
85
86 if self.is_constant(witness, manager) {
87 num_constants += 1;
88 }
89 }
90
91 let size_factor = 1.0 / (1.0 + total_size as f64);
93 let const_factor = 1.0 + (num_constants as f64 / self.witnesses.len().max(1) as f64);
95
96 self.quality = size_factor * const_factor;
97 }
98
99 fn term_size(&self, term: TermId, manager: &TermManager) -> usize {
100 let mut visited = FxHashSet::default();
101 self.term_size_rec(term, manager, &mut visited)
102 }
103
104 fn term_size_rec(
105 &self,
106 term: TermId,
107 manager: &TermManager,
108 visited: &mut FxHashSet<TermId>,
109 ) -> usize {
110 if visited.contains(&term) {
111 return 0;
112 }
113 visited.insert(term);
114
115 let Some(t) = manager.get(term) else {
116 return 1;
117 };
118
119 let children_size = match &t.kind {
120 TermKind::And(args) | TermKind::Or(args) => args
121 .iter()
122 .map(|&arg| self.term_size_rec(arg, manager, visited))
123 .sum(),
124 TermKind::Not(arg) | TermKind::Neg(arg) => self.term_size_rec(*arg, manager, visited),
125 TermKind::Eq(lhs, rhs) | TermKind::Lt(lhs, rhs) => {
126 self.term_size_rec(*lhs, manager, visited)
127 + self.term_size_rec(*rhs, manager, visited)
128 }
129 _ => 0,
130 };
131
132 1 + children_size
133 }
134
135 fn is_constant(&self, term: TermId, manager: &TermManager) -> bool {
136 let Some(t) = manager.get(term) else {
137 return false;
138 };
139
140 matches!(
141 t.kind,
142 TermKind::True
143 | TermKind::False
144 | TermKind::IntConst(_)
145 | TermKind::RealConst(_)
146 | TermKind::BitVecConst { .. }
147 )
148 }
149}
150
151#[derive(Debug, Clone)]
153pub struct CexGenerationResult {
154 pub counterexamples: Vec<CounterExample>,
156 pub all_evaluations_ground: bool,
160}
161
162#[derive(Debug)]
164pub struct CounterExampleGenerator {
165 max_cex_per_quantifier: usize,
167 max_candidates_per_var: usize,
169 #[cfg(feature = "std")]
171 max_search_time: Duration,
172 generation_bound: u32,
174 stats: CexStats,
176 candidate_cache: FxHashMap<SortId, Vec<TermId>>,
178}
179
180impl CounterExampleGenerator {
181 pub fn new() -> Self {
183 Self {
184 max_cex_per_quantifier: 5,
185 max_candidates_per_var: 10,
186 #[cfg(feature = "std")]
187 max_search_time: Duration::from_secs(1),
188 generation_bound: 0,
189 stats: CexStats::default(),
190 candidate_cache: FxHashMap::default(),
191 }
192 }
193
194 #[cfg(feature = "std")]
196 pub fn with_limits(max_cex: usize, max_candidates: usize, max_time: Duration) -> Self {
197 let mut generator = Self::new();
198 generator.max_cex_per_quantifier = max_cex;
199 generator.max_candidates_per_var = max_candidates;
200 generator.max_search_time = max_time;
201 generator
202 }
203
204 pub fn generate(
209 &mut self,
210 quantifier: &QuantifiedFormula,
211 model: &CompletedModel,
212 manager: &mut TermManager,
213 ) -> CexGenerationResult {
214 #[cfg(feature = "std")]
215 let start_time = Instant::now();
216 let mut counterexamples = Vec::new();
217 let mut all_ground = true;
218 self.stats.num_searches += 1;
219
220 let candidates = self.build_candidate_lists(&quantifier.bound_vars, model, manager);
222
223 let combinations = self.enumerate_combinations(
225 &candidates,
226 self.max_candidates_per_var,
227 self.max_cex_per_quantifier * 20, );
229
230 self.stats.num_combinations_tried += combinations.len();
231
232 if combinations.is_empty() {
235 all_ground = false;
236 }
237
238 for combo in combinations {
239 #[cfg(feature = "std")]
240 if start_time.elapsed() > self.max_search_time {
241 self.stats.num_timeouts += 1;
242 all_ground = false;
244 break;
245 }
246
247 if counterexamples.len() >= self.max_cex_per_quantifier {
248 break;
249 }
250
251 let mut assignment = FxHashMap::default();
253 for (i, &candidate) in combo.iter().enumerate() {
254 if let Some(var_name) = quantifier.var_name(i) {
255 assignment.insert(var_name, candidate);
256 }
257 }
258
259 let substituted = self.apply_substitution(quantifier.body, &assignment, manager);
261 let evaluated = self.evaluate_under_model(substituted, model, manager);
262
263 if !self.is_ground_boolean(evaluated, manager) {
265 all_ground = false;
266 }
267
268 if self.is_counterexample(evaluated, quantifier.is_universal, manager) {
270 let mut cex =
271 CounterExample::new(quantifier.term, assignment, combo, model.generation);
272 cex.body_value = Some(evaluated);
273 cex.calculate_quality(manager);
274 counterexamples.push(cex);
275 self.stats.num_counterexamples_found += 1;
276 }
277 }
278
279 counterexamples.sort_by(|a, b| {
281 b.quality
282 .partial_cmp(&a.quality)
283 .unwrap_or(core::cmp::Ordering::Equal)
284 });
285
286 counterexamples.truncate(self.max_cex_per_quantifier);
288
289 #[cfg(feature = "std")]
290 {
291 self.stats.total_time += start_time.elapsed();
292 }
293
294 CexGenerationResult {
295 counterexamples,
296 all_evaluations_ground: all_ground,
297 }
298 }
299
300 fn build_candidate_lists(
302 &mut self,
303 bound_vars: &[(Spur, SortId)],
304 model: &CompletedModel,
305 manager: &mut TermManager,
306 ) -> Vec<Vec<TermId>> {
307 let mut result = Vec::new();
308
309 for &(_var_name, sort) in bound_vars {
310 if let Some(cached) = self.candidate_cache.get(&sort) {
312 result.push(cached.clone());
313 continue;
314 }
315
316 let mut candidates = Vec::new();
317
318 if let Some(universe) = model.universe(sort) {
320 candidates.extend_from_slice(universe);
321 }
322
323 for (&term, &value) in &model.assignments {
325 if let Some(t) = manager.get(term)
326 && t.sort == sort
327 && !candidates.contains(&value)
328 {
329 candidates.push(value);
330 }
331 }
332
333 self.add_default_candidates(sort, &mut candidates, manager);
335
336 candidates.truncate(self.max_candidates_per_var);
338
339 self.candidate_cache.insert(sort, candidates.clone());
341
342 result.push(candidates);
343 }
344
345 result
346 }
347
348 fn add_default_candidates(
350 &self,
351 sort: SortId,
352 candidates: &mut Vec<TermId>,
353 manager: &mut TermManager,
354 ) {
355 if sort == manager.sorts.int_sort {
356 for i in -2..=5 {
358 let val = manager.mk_int(BigInt::from(i));
359 if !candidates.contains(&val) {
360 candidates.push(val);
361 }
362 }
363 } else if sort == manager.sorts.bool_sort {
364 let true_val = manager.mk_true();
365 let false_val = manager.mk_false();
366 if !candidates.contains(&true_val) {
367 candidates.push(true_val);
368 }
369 if !candidates.contains(&false_val) {
370 candidates.push(false_val);
371 }
372 }
373 }
374
375 fn enumerate_combinations(
377 &self,
378 candidates: &[Vec<TermId>],
379 max_per_dim: usize,
380 max_total: usize,
381 ) -> Vec<Vec<TermId>> {
382 if candidates.is_empty() {
383 return vec![vec![]];
384 }
385
386 let mut results = Vec::new();
387 let mut indices = vec![0usize; candidates.len()];
388
389 loop {
390 let combo: Vec<TermId> = indices
392 .iter()
393 .enumerate()
394 .filter_map(|(i, &idx)| candidates.get(i).and_then(|c| c.get(idx).copied()))
395 .collect();
396
397 if combo.len() == candidates.len() {
398 results.push(combo);
399 }
400
401 if results.len() >= max_total {
402 break;
403 }
404
405 let mut carry = true;
407 for (i, idx) in indices.iter_mut().enumerate() {
408 if carry {
409 *idx += 1;
410 let limit = candidates.get(i).map_or(1, |c| c.len().min(max_per_dim));
411 if *idx >= limit {
412 *idx = 0;
413 } else {
414 carry = false;
415 }
416 }
417 }
418
419 if carry {
420 break;
422 }
423 }
424
425 results
426 }
427
428 fn apply_substitution(
430 &self,
431 term: TermId,
432 subst: &FxHashMap<Spur, TermId>,
433 manager: &mut TermManager,
434 ) -> TermId {
435 let mut cache = FxHashMap::default();
436 self.apply_substitution_cached(term, subst, manager, &mut cache)
437 }
438
439 fn apply_substitution_cached(
440 &self,
441 term: TermId,
442 subst: &FxHashMap<Spur, TermId>,
443 manager: &mut TermManager,
444 cache: &mut FxHashMap<TermId, TermId>,
445 ) -> TermId {
446 if let Some(&cached) = cache.get(&term) {
447 return cached;
448 }
449
450 let Some(t) = manager.get(term).cloned() else {
451 return term;
452 };
453
454 let result = match &t.kind {
455 TermKind::Var(name) => {
456 subst.get(name).copied().unwrap_or(term)
458 }
459 TermKind::Not(arg) => {
460 let new_arg = self.apply_substitution_cached(*arg, subst, manager, cache);
461 manager.mk_not(new_arg)
462 }
463 TermKind::And(args) => {
464 let new_args: Vec<_> = args
465 .iter()
466 .map(|&a| self.apply_substitution_cached(a, subst, manager, cache))
467 .collect();
468 manager.mk_and(new_args)
469 }
470 TermKind::Or(args) => {
471 let new_args: Vec<_> = args
472 .iter()
473 .map(|&a| self.apply_substitution_cached(a, subst, manager, cache))
474 .collect();
475 manager.mk_or(new_args)
476 }
477 TermKind::Implies(lhs, rhs) => {
478 let new_lhs = self.apply_substitution_cached(*lhs, subst, manager, cache);
479 let new_rhs = self.apply_substitution_cached(*rhs, subst, manager, cache);
480 manager.mk_implies(new_lhs, new_rhs)
481 }
482 TermKind::Eq(lhs, rhs) => {
483 let new_lhs = self.apply_substitution_cached(*lhs, subst, manager, cache);
484 let new_rhs = self.apply_substitution_cached(*rhs, subst, manager, cache);
485 manager.mk_eq(new_lhs, new_rhs)
486 }
487 TermKind::Lt(lhs, rhs) => {
488 let new_lhs = self.apply_substitution_cached(*lhs, subst, manager, cache);
489 let new_rhs = self.apply_substitution_cached(*rhs, subst, manager, cache);
490 manager.mk_lt(new_lhs, new_rhs)
491 }
492 TermKind::Le(lhs, rhs) => {
493 let new_lhs = self.apply_substitution_cached(*lhs, subst, manager, cache);
494 let new_rhs = self.apply_substitution_cached(*rhs, subst, manager, cache);
495 manager.mk_le(new_lhs, new_rhs)
496 }
497 TermKind::Gt(lhs, rhs) => {
498 let new_lhs = self.apply_substitution_cached(*lhs, subst, manager, cache);
499 let new_rhs = self.apply_substitution_cached(*rhs, subst, manager, cache);
500 manager.mk_gt(new_lhs, new_rhs)
501 }
502 TermKind::Ge(lhs, rhs) => {
503 let new_lhs = self.apply_substitution_cached(*lhs, subst, manager, cache);
504 let new_rhs = self.apply_substitution_cached(*rhs, subst, manager, cache);
505 manager.mk_ge(new_lhs, new_rhs)
506 }
507 TermKind::Add(args) => {
508 let new_args: SmallVec<[TermId; 4]> = args
509 .iter()
510 .map(|&a| self.apply_substitution_cached(a, subst, manager, cache))
511 .collect();
512 manager.mk_add(new_args)
513 }
514 TermKind::Sub(lhs, rhs) => {
515 let new_lhs = self.apply_substitution_cached(*lhs, subst, manager, cache);
516 let new_rhs = self.apply_substitution_cached(*rhs, subst, manager, cache);
517 manager.mk_sub(new_lhs, new_rhs)
518 }
519 TermKind::Mul(args) => {
520 let new_args: SmallVec<[TermId; 4]> = args
521 .iter()
522 .map(|&a| self.apply_substitution_cached(a, subst, manager, cache))
523 .collect();
524 manager.mk_mul(new_args)
525 }
526 TermKind::Div(lhs, rhs) => {
527 let new_lhs = self.apply_substitution_cached(*lhs, subst, manager, cache);
528 let new_rhs = self.apply_substitution_cached(*rhs, subst, manager, cache);
529 manager.mk_div(new_lhs, new_rhs)
530 }
531 TermKind::Mod(lhs, rhs) => {
532 let new_lhs = self.apply_substitution_cached(*lhs, subst, manager, cache);
533 let new_rhs = self.apply_substitution_cached(*rhs, subst, manager, cache);
534 manager.mk_mod(new_lhs, new_rhs)
535 }
536 TermKind::Neg(arg) => {
537 let new_arg = self.apply_substitution_cached(*arg, subst, manager, cache);
538 manager.mk_neg(new_arg)
539 }
540 TermKind::Ite(cond, then_br, else_br) => {
541 let new_cond = self.apply_substitution_cached(*cond, subst, manager, cache);
542 let new_then = self.apply_substitution_cached(*then_br, subst, manager, cache);
543 let new_else = self.apply_substitution_cached(*else_br, subst, manager, cache);
544 manager.mk_ite(new_cond, new_then, new_else)
545 }
546 TermKind::Apply { func, args } => {
547 let func_name = manager.resolve_str(*func).to_string();
548 let new_args: SmallVec<[TermId; 4]> = args
549 .iter()
550 .map(|&a| self.apply_substitution_cached(a, subst, manager, cache))
551 .collect();
552 manager.mk_apply(&func_name, new_args, t.sort)
553 }
554 TermKind::Select(array, index) => {
558 let new_array = self.apply_substitution_cached(*array, subst, manager, cache);
559 let new_index = self.apply_substitution_cached(*index, subst, manager, cache);
560 manager.mk_select(new_array, new_index)
561 }
562 TermKind::Store(array, index, value) => {
564 let new_array = self.apply_substitution_cached(*array, subst, manager, cache);
565 let new_index = self.apply_substitution_cached(*index, subst, manager, cache);
566 let new_value = self.apply_substitution_cached(*value, subst, manager, cache);
567 manager.mk_store(new_array, new_index, new_value)
568 }
569 _ => term,
571 };
572
573 cache.insert(term, result);
574 result
575 }
576
577 fn evaluate_under_model(
579 &self,
580 term: TermId,
581 model: &CompletedModel,
582 manager: &mut TermManager,
583 ) -> TermId {
584 let mut cache = FxHashMap::default();
585 self.evaluate_under_model_cached(term, model, manager, &mut cache)
586 }
587
588 fn evaluate_under_model_cached(
589 &self,
590 term: TermId,
591 model: &CompletedModel,
592 manager: &mut TermManager,
593 cache: &mut FxHashMap<TermId, TermId>,
594 ) -> TermId {
595 if let Some(&cached) = cache.get(&term) {
596 return cached;
597 }
598
599 if let Some(val) = model.eval(term) {
601 cache.insert(term, val);
602 return val;
603 }
604
605 let Some(t) = manager.get(term).cloned() else {
606 return term;
607 };
608
609 let result = match &t.kind {
610 TermKind::True
612 | TermKind::False
613 | TermKind::IntConst(_)
614 | TermKind::RealConst(_)
615 | TermKind::BitVecConst { .. }
616 | TermKind::StringLit(_) => term,
617
618 TermKind::Not(arg) => {
620 let eval_arg = self.evaluate_under_model_cached(*arg, model, manager, cache);
621 if let Some(arg_t) = manager.get(eval_arg) {
622 match arg_t.kind {
623 TermKind::True => manager.mk_false(),
624 TermKind::False => manager.mk_true(),
625 _ => manager.mk_not(eval_arg),
626 }
627 } else {
628 manager.mk_not(eval_arg)
629 }
630 }
631 TermKind::And(args) => {
632 let mut all_true = true;
633 for &arg in args.iter() {
634 let eval_arg = self.evaluate_under_model_cached(arg, model, manager, cache);
635 if let Some(arg_t) = manager.get(eval_arg) {
636 match arg_t.kind {
637 TermKind::False => {
638 let false_val = manager.mk_false();
639 cache.insert(term, false_val);
640 return false_val;
641 }
642 TermKind::True => { }
643 _ => {
644 all_true = false;
645 }
646 }
647 } else {
648 all_true = false;
649 }
650 }
651 if all_true {
652 manager.mk_true()
653 } else {
654 term
656 }
657 }
658 TermKind::Or(args) => {
659 let mut all_false = true;
660 for &arg in args.iter() {
661 let eval_arg = self.evaluate_under_model_cached(arg, model, manager, cache);
662 if let Some(arg_t) = manager.get(eval_arg) {
663 match arg_t.kind {
664 TermKind::True => {
665 let true_val = manager.mk_true();
666 cache.insert(term, true_val);
667 return true_val;
668 }
669 TermKind::False => { }
670 _ => {
671 all_false = false;
672 }
673 }
674 } else {
675 all_false = false;
676 }
677 }
678 if all_false { manager.mk_false() } else { term }
679 }
680 TermKind::Implies(lhs, rhs) => {
681 let eval_lhs = self.evaluate_under_model_cached(*lhs, model, manager, cache);
682 if let Some(lhs_t) = manager.get(eval_lhs) {
683 match lhs_t.kind {
684 TermKind::False => manager.mk_true(),
685 TermKind::True => {
686 self.evaluate_under_model_cached(*rhs, model, manager, cache)
687 }
688 _ => {
689 let eval_rhs =
690 self.evaluate_under_model_cached(*rhs, model, manager, cache);
691 manager.mk_implies(eval_lhs, eval_rhs)
692 }
693 }
694 } else {
695 let eval_rhs = self.evaluate_under_model_cached(*rhs, model, manager, cache);
696 manager.mk_implies(eval_lhs, eval_rhs)
697 }
698 }
699
700 TermKind::Eq(lhs, rhs) => {
702 let eval_lhs = self.evaluate_under_model_cached(*lhs, model, manager, cache);
703 let eval_rhs = self.evaluate_under_model_cached(*rhs, model, manager, cache);
704 self.eval_eq(eval_lhs, eval_rhs, manager)
705 }
706 TermKind::Lt(lhs, rhs) => {
707 let eval_lhs = self.evaluate_under_model_cached(*lhs, model, manager, cache);
708 let eval_rhs = self.evaluate_under_model_cached(*rhs, model, manager, cache);
709 self.eval_lt(eval_lhs, eval_rhs, manager)
710 }
711 TermKind::Le(lhs, rhs) => {
712 let eval_lhs = self.evaluate_under_model_cached(*lhs, model, manager, cache);
713 let eval_rhs = self.evaluate_under_model_cached(*rhs, model, manager, cache);
714 self.eval_le(eval_lhs, eval_rhs, manager)
715 }
716 TermKind::Gt(lhs, rhs) => {
717 let eval_lhs = self.evaluate_under_model_cached(*lhs, model, manager, cache);
718 let eval_rhs = self.evaluate_under_model_cached(*rhs, model, manager, cache);
719 self.eval_gt(eval_lhs, eval_rhs, manager)
720 }
721 TermKind::Ge(lhs, rhs) => {
722 let eval_lhs = self.evaluate_under_model_cached(*lhs, model, manager, cache);
723 let eval_rhs = self.evaluate_under_model_cached(*rhs, model, manager, cache);
724 self.eval_ge(eval_lhs, eval_rhs, manager)
725 }
726
727 TermKind::Add(args) => {
729 let eval_args: SmallVec<[TermId; 4]> = args
730 .iter()
731 .map(|&a| self.evaluate_under_model_cached(a, model, manager, cache))
732 .collect();
733 self.eval_add(&eval_args, manager)
734 }
735 TermKind::Mul(args) => {
736 let eval_args: SmallVec<[TermId; 4]> = args
737 .iter()
738 .map(|&a| self.evaluate_under_model_cached(a, model, manager, cache))
739 .collect();
740 self.eval_mul(&eval_args, manager)
741 }
742 TermKind::Sub(lhs, rhs) => {
743 let eval_lhs = self.evaluate_under_model_cached(*lhs, model, manager, cache);
744 let eval_rhs = self.evaluate_under_model_cached(*rhs, model, manager, cache);
745 self.eval_sub(eval_lhs, eval_rhs, manager)
746 }
747 TermKind::Div(lhs, rhs) => {
748 let eval_lhs = self.evaluate_under_model_cached(*lhs, model, manager, cache);
749 let eval_rhs = self.evaluate_under_model_cached(*rhs, model, manager, cache);
750 self.eval_div(eval_lhs, eval_rhs, manager)
751 }
752 TermKind::Mod(lhs, rhs) => {
753 let eval_lhs = self.evaluate_under_model_cached(*lhs, model, manager, cache);
754 let eval_rhs = self.evaluate_under_model_cached(*rhs, model, manager, cache);
755 self.eval_modulo(eval_lhs, eval_rhs, manager)
756 }
757 TermKind::Neg(arg) => {
758 let eval_arg = self.evaluate_under_model_cached(*arg, model, manager, cache);
759 self.eval_neg(eval_arg, manager)
760 }
761
762 TermKind::Ite(cond, then_br, else_br) => {
764 let eval_cond = self.evaluate_under_model_cached(*cond, model, manager, cache);
765 if let Some(cond_t) = manager.get(eval_cond) {
766 match cond_t.kind {
767 TermKind::True => {
768 self.evaluate_under_model_cached(*then_br, model, manager, cache)
769 }
770 TermKind::False => {
771 self.evaluate_under_model_cached(*else_br, model, manager, cache)
772 }
773 _ => {
774 let eval_then =
776 self.evaluate_under_model_cached(*then_br, model, manager, cache);
777 let eval_else =
778 self.evaluate_under_model_cached(*else_br, model, manager, cache);
779 manager.mk_ite(eval_cond, eval_then, eval_else)
780 }
781 }
782 } else {
783 term
784 }
785 }
786
787 TermKind::Apply { func, args } => {
789 let evaluated_args: Vec<TermId> = args
790 .iter()
791 .map(|&a| self.evaluate_under_model_cached(a, model, manager, cache))
792 .collect();
793
794 if let Some(result) = model.eval_apply(*func, &evaluated_args) {
796 result
797 } else {
798 let func_name = manager.resolve_str(*func).to_string();
800 let new_term =
801 manager.mk_apply(&func_name, evaluated_args.iter().copied(), t.sort);
802 model.eval(new_term).unwrap_or(new_term)
803 }
804 }
805
806 TermKind::Forall { .. } => term,
808
809 TermKind::Exists {
814 vars,
815 body: exists_body,
816 ..
817 } => {
818 let vars_vec: SmallVec<[(Spur, SortId); 2]> = vars.clone();
819 let body_id = *exists_body;
820 self.evaluate_exists_inline(&vars_vec, body_id, model, manager, cache)
821 }
822
823 TermKind::Select(array, index) => {
833 let eval_index = self.evaluate_under_model_cached(*index, model, manager, cache);
834 let select_with_orig_array = manager.mk_select(*array, eval_index);
837 if let Some(val) = model.eval(select_with_orig_array) {
838 val
839 } else if let Some(val) = model.eval(term) {
840 val
842 } else {
843 let eval_array =
845 self.evaluate_under_model_cached(*array, model, manager, cache);
846 let new_select = manager.mk_select(eval_array, eval_index);
847 if let Some(val) = model.eval(new_select) {
848 val
849 } else {
850 new_select
855 }
856 }
857 }
858
859 TermKind::Var(_) => model.eval(term).unwrap_or(term),
861
862 _ => manager.simplify(term),
864 };
865
866 cache.insert(term, result);
867 result
868 }
869
870 fn evaluate_exists_inline(
877 &self,
878 vars: &SmallVec<[(Spur, SortId); 2]>,
879 body: TermId,
880 model: &CompletedModel,
881 manager: &mut TermManager,
882 parent_cache: &mut FxHashMap<TermId, TermId>,
883 ) -> TermId {
884 let mut candidate_lists: Vec<Vec<TermId>> = Vec::new();
886 for &(_var_name, sort) in vars {
887 let mut cands = Vec::new();
888 if let Some(universe) = model.universe(sort) {
890 cands.extend_from_slice(universe);
891 }
892 for (&term, &value) in &model.assignments {
894 if let Some(t) = manager.get(term)
895 && t.sort == sort
896 && !cands.contains(&value)
897 {
898 cands.push(value);
899 }
900 }
901 if sort == manager.sorts.int_sort {
903 for i in -2i64..=5 {
904 let val = manager.mk_int(BigInt::from(i));
905 if !cands.contains(&val) {
906 cands.push(val);
907 }
908 }
909 } else if sort == manager.sorts.bool_sort {
910 let t = manager.mk_true();
911 let f = manager.mk_false();
912 if !cands.contains(&t) {
913 cands.push(t);
914 }
915 if !cands.contains(&f) {
916 cands.push(f);
917 }
918 }
919 cands.truncate(self.max_candidates_per_var);
920 candidate_lists.push(cands);
921 }
922
923 if candidate_lists.is_empty() || candidate_lists.iter().any(|c| c.is_empty()) {
924 return body; }
927
928 let total_combos: usize = candidate_lists
930 .iter()
931 .map(|c| c.len())
932 .product::<usize>()
933 .min(50);
934 let mut found_true = false;
935 let mut found_symbolic = false;
936 let mut all_false = true;
937 let mut combo_count = 0;
938
939 let mut indices = vec![0usize; candidate_lists.len()];
941 loop {
942 if combo_count >= total_combos {
943 break;
944 }
945 let mut subst: FxHashMap<Spur, TermId> = FxHashMap::default();
947 for (i, &(var_name, _sort)) in vars.iter().enumerate() {
948 if let Some(&candidate) = candidate_lists[i].get(indices[i]) {
949 subst.insert(var_name, candidate);
950 }
951 }
952
953 let mut sub_cache: FxHashMap<TermId, TermId> = FxHashMap::default();
955 let substituted = self.apply_substitution_cached(body, &subst, manager, &mut sub_cache);
956 let evaluated =
957 self.evaluate_under_model_cached(substituted, model, manager, parent_cache);
958
959 if let Some(eval_t) = manager.get(evaluated) {
960 match eval_t.kind {
961 TermKind::True => {
962 found_true = true;
963 break; }
965 TermKind::False => {
966 }
968 _ => {
969 found_symbolic = true;
971 all_false = false;
972 }
973 }
974 } else {
975 found_symbolic = true;
976 all_false = false;
977 }
978
979 combo_count += 1;
980
981 let mut carry = true;
983 for (i, idx) in indices.iter_mut().enumerate() {
984 if carry {
985 *idx += 1;
986 let limit = candidate_lists.get(i).map_or(1, |c| c.len());
987 if *idx >= limit {
988 *idx = 0;
989 } else {
990 carry = false;
991 }
992 }
993 }
994 if carry {
995 break; }
997 }
998
999 if found_true {
1000 manager.mk_true()
1001 } else if all_false && !found_symbolic && combo_count > 0 {
1002 manager.mk_false()
1004 } else {
1005 body }
1008 }
1009
1010 fn eval_eq(&self, lhs: TermId, rhs: TermId, manager: &mut TermManager) -> TermId {
1012 if lhs == rhs {
1013 return manager.mk_true();
1014 }
1015
1016 let lhs_t = manager.get(lhs);
1017 let rhs_t = manager.get(rhs);
1018
1019 if let (Some(l), Some(r)) = (lhs_t, rhs_t) {
1020 match (&l.kind, &r.kind) {
1021 (TermKind::IntConst(a), TermKind::IntConst(b)) => {
1022 if a == b {
1023 manager.mk_true()
1024 } else {
1025 manager.mk_false()
1026 }
1027 }
1028 (TermKind::RealConst(a), TermKind::RealConst(b)) => {
1029 if a == b {
1030 manager.mk_true()
1031 } else {
1032 manager.mk_false()
1033 }
1034 }
1035 (TermKind::True, TermKind::True) | (TermKind::False, TermKind::False) => {
1036 manager.mk_true()
1037 }
1038 (TermKind::True, TermKind::False) | (TermKind::False, TermKind::True) => {
1039 manager.mk_false()
1040 }
1041 _ => manager.mk_eq(lhs, rhs),
1042 }
1043 } else {
1044 manager.mk_eq(lhs, rhs)
1045 }
1046 }
1047
1048 fn eval_lt(&self, lhs: TermId, rhs: TermId, manager: &mut TermManager) -> TermId {
1050 let lhs_t = manager.get(lhs);
1051 let rhs_t = manager.get(rhs);
1052
1053 if let (Some(l), Some(r)) = (lhs_t, rhs_t) {
1054 if let (TermKind::IntConst(a), TermKind::IntConst(b)) = (&l.kind, &r.kind) {
1055 if a < b {
1056 return manager.mk_true();
1057 } else {
1058 return manager.mk_false();
1059 }
1060 }
1061 if let (TermKind::RealConst(a), TermKind::RealConst(b)) = (&l.kind, &r.kind) {
1062 if a < b {
1063 return manager.mk_true();
1064 } else {
1065 return manager.mk_false();
1066 }
1067 }
1068 }
1069
1070 manager.mk_lt(lhs, rhs)
1071 }
1072
1073 fn eval_le(&self, lhs: TermId, rhs: TermId, manager: &mut TermManager) -> TermId {
1075 let lhs_t = manager.get(lhs);
1076 let rhs_t = manager.get(rhs);
1077
1078 if let (Some(l), Some(r)) = (lhs_t, rhs_t) {
1079 if let (TermKind::IntConst(a), TermKind::IntConst(b)) = (&l.kind, &r.kind) {
1080 if a <= b {
1081 return manager.mk_true();
1082 } else {
1083 return manager.mk_false();
1084 }
1085 }
1086 if let (TermKind::RealConst(a), TermKind::RealConst(b)) = (&l.kind, &r.kind) {
1087 if a <= b {
1088 return manager.mk_true();
1089 } else {
1090 return manager.mk_false();
1091 }
1092 }
1093 }
1094
1095 manager.mk_le(lhs, rhs)
1096 }
1097
1098 fn eval_gt(&self, lhs: TermId, rhs: TermId, manager: &mut TermManager) -> TermId {
1100 self.eval_lt(rhs, lhs, manager)
1101 }
1102
1103 fn eval_ge(&self, lhs: TermId, rhs: TermId, manager: &mut TermManager) -> TermId {
1105 self.eval_le(rhs, lhs, manager)
1106 }
1107
1108 fn eval_add(&self, args: &[TermId], manager: &mut TermManager) -> TermId {
1110 let mut result = BigInt::from(0);
1111 let mut all_ints = true;
1112
1113 for &arg in args {
1114 if let Some(arg_t) = manager.get(arg) {
1115 if let TermKind::IntConst(val) = &arg_t.kind {
1116 result += val;
1117 } else {
1118 all_ints = false;
1119 break;
1120 }
1121 } else {
1122 all_ints = false;
1123 break;
1124 }
1125 }
1126
1127 if all_ints {
1128 manager.mk_int(result)
1129 } else {
1130 manager.mk_add(args.iter().copied())
1131 }
1132 }
1133
1134 fn eval_mul(&self, args: &[TermId], manager: &mut TermManager) -> TermId {
1136 let mut result = BigInt::from(1);
1137 let mut all_ints = true;
1138
1139 for &arg in args {
1140 if let Some(arg_t) = manager.get(arg) {
1141 if let TermKind::IntConst(val) = &arg_t.kind {
1142 result *= val;
1143 } else {
1144 all_ints = false;
1145 break;
1146 }
1147 } else {
1148 all_ints = false;
1149 break;
1150 }
1151 }
1152
1153 if all_ints {
1154 manager.mk_int(result)
1155 } else {
1156 manager.mk_mul(args.iter().copied())
1157 }
1158 }
1159
1160 fn eval_sub(&self, lhs: TermId, rhs: TermId, manager: &mut TermManager) -> TermId {
1162 let lhs_t = manager.get(lhs);
1163 let rhs_t = manager.get(rhs);
1164
1165 if let (Some(l), Some(r)) = (lhs_t, rhs_t) {
1166 if let (TermKind::IntConst(a), TermKind::IntConst(b)) = (&l.kind, &r.kind) {
1167 return manager.mk_int(a - b);
1168 }
1169 }
1170
1171 manager.mk_sub(lhs, rhs)
1172 }
1173
1174 fn eval_div(&self, lhs: TermId, rhs: TermId, manager: &mut TermManager) -> TermId {
1176 let lhs_t = manager.get(lhs);
1177 let rhs_t = manager.get(rhs);
1178
1179 if let (Some(l), Some(r)) = (lhs_t, rhs_t) {
1180 if let (TermKind::IntConst(a), TermKind::IntConst(b)) = (&l.kind, &r.kind) {
1181 if *b != BigInt::from(0) {
1182 return manager.mk_int(a / b);
1183 }
1184 }
1185 }
1186
1187 manager.mk_div(lhs, rhs)
1188 }
1189
1190 fn eval_modulo(&self, lhs: TermId, rhs: TermId, manager: &mut TermManager) -> TermId {
1192 let lhs_t = manager.get(lhs);
1193 let rhs_t = manager.get(rhs);
1194
1195 if let (Some(l), Some(r)) = (lhs_t, rhs_t) {
1196 if let (TermKind::IntConst(a), TermKind::IntConst(b)) = (&l.kind, &r.kind) {
1197 if *b != BigInt::from(0) {
1198 return manager.mk_int(a % b);
1199 }
1200 }
1201 }
1202
1203 manager.mk_mod(lhs, rhs)
1204 }
1205
1206 fn eval_neg(&self, arg: TermId, manager: &mut TermManager) -> TermId {
1208 if let Some(arg_t) = manager.get(arg) {
1209 if let TermKind::IntConst(val) = &arg_t.kind {
1210 return manager.mk_int(-val);
1211 }
1212 }
1213
1214 manager.mk_neg(arg)
1215 }
1216
1217 fn is_counterexample(
1229 &self,
1230 evaluated: TermId,
1231 is_universal: bool,
1232 manager: &TermManager,
1233 ) -> bool {
1234 let Some(eval_t) = manager.get(evaluated) else {
1235 return false;
1237 };
1238
1239 if is_universal {
1240 matches!(eval_t.kind, TermKind::False)
1245 } else {
1246 matches!(eval_t.kind, TermKind::True)
1248 }
1249 }
1250
1251 fn is_ground_boolean(&self, evaluated: TermId, manager: &TermManager) -> bool {
1254 let Some(eval_t) = manager.get(evaluated) else {
1255 return false;
1256 };
1257 matches!(eval_t.kind, TermKind::True | TermKind::False)
1258 }
1259
1260 pub fn set_generation_bound(&mut self, bound: u32) {
1262 self.generation_bound = bound;
1263 }
1264
1265 pub fn inject_extra_candidates(&mut self, extras: &FxHashMap<SortId, Vec<TermId>>) {
1270 for (&sort, terms) in extras {
1271 let entry = self.candidate_cache.entry(sort).or_default();
1272 for &t in terms {
1273 if !entry.contains(&t) {
1274 entry.push(t);
1275 }
1276 }
1277 }
1278 }
1279
1280 pub fn clear_cache(&mut self) {
1282 self.candidate_cache.clear();
1283 }
1284
1285 pub fn stats(&self) -> &CexStats {
1287 &self.stats
1288 }
1289}
1290
1291impl Default for CounterExampleGenerator {
1292 fn default() -> Self {
1293 Self::new()
1294 }
1295}
1296
1297#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1299pub enum RefinementStrategy {
1300 None,
1302 BlockCounterexamples,
1304 ConflictLearning,
1306 Generalization,
1308}
1309
1310#[derive(Debug, Clone, Default)]
1312pub struct CexStats {
1313 pub num_searches: usize,
1315 pub num_counterexamples_found: usize,
1317 pub num_combinations_tried: usize,
1319 pub num_timeouts: usize,
1321 #[cfg(feature = "std")]
1323 pub total_time: Duration,
1324}
1325
1326impl fmt::Display for CexStats {
1327 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1328 writeln!(f, "Counterexample Statistics:")?;
1329 writeln!(f, " Searches: {}", self.num_searches)?;
1330 writeln!(f, " CEX found: {}", self.num_counterexamples_found)?;
1331 writeln!(f, " Combinations tried: {}", self.num_combinations_tried)?;
1332 writeln!(f, " Timeouts: {}", self.num_timeouts)?;
1333 #[cfg(feature = "std")]
1334 writeln!(f, " Total time: {:.2}ms", self.total_time.as_millis())?;
1335 Ok(())
1336 }
1337}
1338
1339#[cfg(test)]
1340mod tests {
1341 use super::*;
1342
1343 #[test]
1344 fn test_counterexample_creation() {
1345 let cex = CounterExample::new(TermId::new(1), FxHashMap::default(), vec![], 0);
1346 assert_eq!(cex.quantifier, TermId::new(1));
1347 assert_eq!(cex.quality, 1.0);
1348 }
1349
1350 #[test]
1351 fn test_cex_generator_creation() {
1352 let generator = CounterExampleGenerator::new();
1353 assert_eq!(generator.max_cex_per_quantifier, 5);
1354 assert_eq!(generator.max_candidates_per_var, 10);
1355 }
1356
1357 #[test]
1358 fn test_cex_generator_with_limits() {
1359 let generator = CounterExampleGenerator::with_limits(10, 20, Duration::from_secs(2));
1360 assert_eq!(generator.max_cex_per_quantifier, 10);
1361 assert_eq!(generator.max_candidates_per_var, 20);
1362 assert_eq!(generator.max_search_time, Duration::from_secs(2));
1363 }
1364
1365 #[test]
1366 fn test_enumerate_combinations_empty() {
1367 let generator = CounterExampleGenerator::new();
1368 let combos = generator.enumerate_combinations(&[], 10, 100);
1369 assert_eq!(combos.len(), 1);
1370 assert!(combos[0].is_empty());
1371 }
1372
1373 #[test]
1374 fn test_enumerate_combinations_single() {
1375 let generator = CounterExampleGenerator::new();
1376 let candidates = vec![vec![TermId::new(1), TermId::new(2)]];
1377 let combos = generator.enumerate_combinations(&candidates, 10, 100);
1378 assert_eq!(combos.len(), 2);
1379 }
1380
1381 #[test]
1382 fn test_enumerate_combinations_multiple() {
1383 let generator = CounterExampleGenerator::new();
1384 let candidates = vec![
1385 vec![TermId::new(1), TermId::new(2)],
1386 vec![TermId::new(3), TermId::new(4)],
1387 ];
1388 let combos = generator.enumerate_combinations(&candidates, 10, 100);
1389 assert_eq!(combos.len(), 4); }
1391
1392 #[test]
1393 fn test_enumerate_combinations_limit() {
1394 let generator = CounterExampleGenerator::new();
1395 let candidates = vec![
1396 vec![TermId::new(1), TermId::new(2), TermId::new(3)],
1397 vec![TermId::new(4), TermId::new(5), TermId::new(6)],
1398 ];
1399 let combos = generator.enumerate_combinations(&candidates, 10, 5);
1400 assert!(combos.len() <= 5);
1401 }
1402
1403 #[test]
1404 fn test_cex_stats_display() {
1405 let stats = CexStats {
1406 num_searches: 10,
1407 num_counterexamples_found: 5,
1408 num_combinations_tried: 100,
1409 num_timeouts: 1,
1410 total_time: Duration::from_millis(500),
1411 };
1412 let display = format!("{}", stats);
1413 assert!(display.contains("Searches: 10"));
1414 assert!(display.contains("CEX found: 5"));
1415 }
1416
1417 #[test]
1418 fn test_refinement_strategy() {
1419 assert_ne!(
1420 RefinementStrategy::None,
1421 RefinementStrategy::BlockCounterexamples
1422 );
1423 }
1424}