#[allow(unused_imports)]
use crate::prelude::*;
use core::fmt;
use num_bigint::BigInt;
use oxiz_core::ast::{TermId, TermKind, TermManager};
use oxiz_core::interner::Spur;
use oxiz_core::sort::SortId;
use smallvec::SmallVec;
#[cfg(feature = "std")]
use std::time::{Duration, Instant};
use super::model_completion::CompletedModel;
use super::{Instantiation, InstantiationReason, QuantifiedFormula};
#[derive(Debug, Clone)]
pub struct CounterExample {
pub quantifier: TermId,
pub assignment: FxHashMap<Spur, TermId>,
pub witnesses: Vec<TermId>,
pub body_value: Option<TermId>,
pub quality: f64,
pub generation: u32,
}
impl CounterExample {
pub fn new(
quantifier: TermId,
assignment: FxHashMap<Spur, TermId>,
witnesses: Vec<TermId>,
generation: u32,
) -> Self {
Self {
quantifier,
assignment,
witnesses,
body_value: None,
quality: 1.0,
generation,
}
}
pub fn to_instantiation(&self, result: TermId) -> Instantiation {
Instantiation::with_reason(
self.quantifier,
self.assignment.clone(),
result,
self.generation,
InstantiationReason::Conflict,
)
}
pub fn calculate_quality(&mut self, manager: &TermManager) {
let mut total_size = 0;
let mut num_constants = 0;
for &witness in &self.witnesses {
let size = self.term_size(witness, manager);
total_size += size;
if self.is_constant(witness, manager) {
num_constants += 1;
}
}
let size_factor = 1.0 / (1.0 + total_size as f64);
let const_factor = 1.0 + (num_constants as f64 / self.witnesses.len().max(1) as f64);
self.quality = size_factor * const_factor;
}
fn term_size(&self, term: TermId, manager: &TermManager) -> usize {
let mut visited = FxHashSet::default();
self.term_size_rec(term, manager, &mut visited)
}
fn term_size_rec(
&self,
term: TermId,
manager: &TermManager,
visited: &mut FxHashSet<TermId>,
) -> usize {
if visited.contains(&term) {
return 0;
}
visited.insert(term);
let Some(t) = manager.get(term) else {
return 1;
};
let children_size = match &t.kind {
TermKind::And(args) | TermKind::Or(args) => args
.iter()
.map(|&arg| self.term_size_rec(arg, manager, visited))
.sum(),
TermKind::Not(arg) | TermKind::Neg(arg) => self.term_size_rec(*arg, manager, visited),
TermKind::Eq(lhs, rhs) | TermKind::Lt(lhs, rhs) => {
self.term_size_rec(*lhs, manager, visited)
+ self.term_size_rec(*rhs, manager, visited)
}
_ => 0,
};
1 + children_size
}
fn is_constant(&self, term: TermId, manager: &TermManager) -> bool {
let Some(t) = manager.get(term) else {
return false;
};
matches!(
t.kind,
TermKind::True
| TermKind::False
| TermKind::IntConst(_)
| TermKind::RealConst(_)
| TermKind::BitVecConst { .. }
)
}
}
#[derive(Debug, Clone)]
pub struct CexGenerationResult {
pub counterexamples: Vec<CounterExample>,
pub all_evaluations_ground: bool,
}
#[derive(Debug)]
pub struct CounterExampleGenerator {
max_cex_per_quantifier: usize,
max_candidates_per_var: usize,
#[cfg(feature = "std")]
max_search_time: Duration,
generation_bound: u32,
stats: CexStats,
candidate_cache: FxHashMap<SortId, Vec<TermId>>,
}
impl CounterExampleGenerator {
pub fn new() -> Self {
Self {
max_cex_per_quantifier: 5,
max_candidates_per_var: 10,
#[cfg(feature = "std")]
max_search_time: Duration::from_secs(1),
generation_bound: 0,
stats: CexStats::default(),
candidate_cache: FxHashMap::default(),
}
}
#[cfg(feature = "std")]
pub fn with_limits(max_cex: usize, max_candidates: usize, max_time: Duration) -> Self {
let mut generator = Self::new();
generator.max_cex_per_quantifier = max_cex;
generator.max_candidates_per_var = max_candidates;
generator.max_search_time = max_time;
generator
}
pub fn generate(
&mut self,
quantifier: &QuantifiedFormula,
model: &CompletedModel,
manager: &mut TermManager,
) -> CexGenerationResult {
#[cfg(feature = "std")]
let start_time = Instant::now();
let mut counterexamples = Vec::new();
let mut all_ground = true;
self.stats.num_searches += 1;
let candidates = self.build_candidate_lists(&quantifier.bound_vars, model, manager);
let combinations = self.enumerate_combinations(
&candidates,
self.max_candidates_per_var,
self.max_cex_per_quantifier * 20, );
self.stats.num_combinations_tried += combinations.len();
if combinations.is_empty() {
all_ground = false;
}
for combo in combinations {
#[cfg(feature = "std")]
if start_time.elapsed() > self.max_search_time {
self.stats.num_timeouts += 1;
all_ground = false;
break;
}
if counterexamples.len() >= self.max_cex_per_quantifier {
break;
}
let mut assignment = FxHashMap::default();
for (i, &candidate) in combo.iter().enumerate() {
if let Some(var_name) = quantifier.var_name(i) {
assignment.insert(var_name, candidate);
}
}
let substituted = self.apply_substitution(quantifier.body, &assignment, manager);
let evaluated = self.evaluate_under_model(substituted, model, manager);
if !self.is_ground_boolean(evaluated, manager) {
all_ground = false;
}
if self.is_counterexample(evaluated, quantifier.is_universal, manager) {
let mut cex =
CounterExample::new(quantifier.term, assignment, combo, model.generation);
cex.body_value = Some(evaluated);
cex.calculate_quality(manager);
counterexamples.push(cex);
self.stats.num_counterexamples_found += 1;
}
}
counterexamples.sort_by(|a, b| {
b.quality
.partial_cmp(&a.quality)
.unwrap_or(core::cmp::Ordering::Equal)
});
counterexamples.truncate(self.max_cex_per_quantifier);
#[cfg(feature = "std")]
{
self.stats.total_time += start_time.elapsed();
}
CexGenerationResult {
counterexamples,
all_evaluations_ground: all_ground,
}
}
fn build_candidate_lists(
&mut self,
bound_vars: &[(Spur, SortId)],
model: &CompletedModel,
manager: &mut TermManager,
) -> Vec<Vec<TermId>> {
let mut result = Vec::new();
for &(_var_name, sort) in bound_vars {
if let Some(cached) = self.candidate_cache.get(&sort) {
result.push(cached.clone());
continue;
}
let mut candidates = Vec::new();
if let Some(universe) = model.universe(sort) {
candidates.extend_from_slice(universe);
}
for (&term, &value) in &model.assignments {
if let Some(t) = manager.get(term)
&& t.sort == sort
&& !candidates.contains(&value)
{
candidates.push(value);
}
}
self.add_default_candidates(sort, &mut candidates, manager);
candidates.truncate(self.max_candidates_per_var);
self.candidate_cache.insert(sort, candidates.clone());
result.push(candidates);
}
result
}
fn add_default_candidates(
&self,
sort: SortId,
candidates: &mut Vec<TermId>,
manager: &mut TermManager,
) {
if sort == manager.sorts.int_sort {
for i in -2..=5 {
let val = manager.mk_int(BigInt::from(i));
if !candidates.contains(&val) {
candidates.push(val);
}
}
} else if sort == manager.sorts.bool_sort {
let true_val = manager.mk_true();
let false_val = manager.mk_false();
if !candidates.contains(&true_val) {
candidates.push(true_val);
}
if !candidates.contains(&false_val) {
candidates.push(false_val);
}
}
}
fn enumerate_combinations(
&self,
candidates: &[Vec<TermId>],
max_per_dim: usize,
max_total: usize,
) -> Vec<Vec<TermId>> {
if candidates.is_empty() {
return vec![vec![]];
}
let mut results = Vec::new();
let mut indices = vec![0usize; candidates.len()];
loop {
let combo: Vec<TermId> = indices
.iter()
.enumerate()
.filter_map(|(i, &idx)| candidates.get(i).and_then(|c| c.get(idx).copied()))
.collect();
if combo.len() == candidates.len() {
results.push(combo);
}
if results.len() >= max_total {
break;
}
let mut carry = true;
for (i, idx) in indices.iter_mut().enumerate() {
if carry {
*idx += 1;
let limit = candidates.get(i).map_or(1, |c| c.len().min(max_per_dim));
if *idx >= limit {
*idx = 0;
} else {
carry = false;
}
}
}
if carry {
break;
}
}
results
}
fn apply_substitution(
&self,
term: TermId,
subst: &FxHashMap<Spur, TermId>,
manager: &mut TermManager,
) -> TermId {
let mut cache = FxHashMap::default();
self.apply_substitution_cached(term, subst, manager, &mut cache)
}
fn apply_substitution_cached(
&self,
term: TermId,
subst: &FxHashMap<Spur, TermId>,
manager: &mut TermManager,
cache: &mut FxHashMap<TermId, TermId>,
) -> TermId {
if let Some(&cached) = cache.get(&term) {
return cached;
}
let Some(t) = manager.get(term).cloned() else {
return term;
};
let result = match &t.kind {
TermKind::Var(name) => {
subst.get(name).copied().unwrap_or(term)
}
TermKind::Not(arg) => {
let new_arg = self.apply_substitution_cached(*arg, subst, manager, cache);
manager.mk_not(new_arg)
}
TermKind::And(args) => {
let new_args: Vec<_> = args
.iter()
.map(|&a| self.apply_substitution_cached(a, subst, manager, cache))
.collect();
manager.mk_and(new_args)
}
TermKind::Or(args) => {
let new_args: Vec<_> = args
.iter()
.map(|&a| self.apply_substitution_cached(a, subst, manager, cache))
.collect();
manager.mk_or(new_args)
}
TermKind::Implies(lhs, rhs) => {
let new_lhs = self.apply_substitution_cached(*lhs, subst, manager, cache);
let new_rhs = self.apply_substitution_cached(*rhs, subst, manager, cache);
manager.mk_implies(new_lhs, new_rhs)
}
TermKind::Eq(lhs, rhs) => {
let new_lhs = self.apply_substitution_cached(*lhs, subst, manager, cache);
let new_rhs = self.apply_substitution_cached(*rhs, subst, manager, cache);
manager.mk_eq(new_lhs, new_rhs)
}
TermKind::Lt(lhs, rhs) => {
let new_lhs = self.apply_substitution_cached(*lhs, subst, manager, cache);
let new_rhs = self.apply_substitution_cached(*rhs, subst, manager, cache);
manager.mk_lt(new_lhs, new_rhs)
}
TermKind::Le(lhs, rhs) => {
let new_lhs = self.apply_substitution_cached(*lhs, subst, manager, cache);
let new_rhs = self.apply_substitution_cached(*rhs, subst, manager, cache);
manager.mk_le(new_lhs, new_rhs)
}
TermKind::Gt(lhs, rhs) => {
let new_lhs = self.apply_substitution_cached(*lhs, subst, manager, cache);
let new_rhs = self.apply_substitution_cached(*rhs, subst, manager, cache);
manager.mk_gt(new_lhs, new_rhs)
}
TermKind::Ge(lhs, rhs) => {
let new_lhs = self.apply_substitution_cached(*lhs, subst, manager, cache);
let new_rhs = self.apply_substitution_cached(*rhs, subst, manager, cache);
manager.mk_ge(new_lhs, new_rhs)
}
TermKind::Add(args) => {
let new_args: SmallVec<[TermId; 4]> = args
.iter()
.map(|&a| self.apply_substitution_cached(a, subst, manager, cache))
.collect();
manager.mk_add(new_args)
}
TermKind::Sub(lhs, rhs) => {
let new_lhs = self.apply_substitution_cached(*lhs, subst, manager, cache);
let new_rhs = self.apply_substitution_cached(*rhs, subst, manager, cache);
manager.mk_sub(new_lhs, new_rhs)
}
TermKind::Mul(args) => {
let new_args: SmallVec<[TermId; 4]> = args
.iter()
.map(|&a| self.apply_substitution_cached(a, subst, manager, cache))
.collect();
manager.mk_mul(new_args)
}
TermKind::Div(lhs, rhs) => {
let new_lhs = self.apply_substitution_cached(*lhs, subst, manager, cache);
let new_rhs = self.apply_substitution_cached(*rhs, subst, manager, cache);
manager.mk_div(new_lhs, new_rhs)
}
TermKind::Mod(lhs, rhs) => {
let new_lhs = self.apply_substitution_cached(*lhs, subst, manager, cache);
let new_rhs = self.apply_substitution_cached(*rhs, subst, manager, cache);
manager.mk_mod(new_lhs, new_rhs)
}
TermKind::Neg(arg) => {
let new_arg = self.apply_substitution_cached(*arg, subst, manager, cache);
manager.mk_neg(new_arg)
}
TermKind::Ite(cond, then_br, else_br) => {
let new_cond = self.apply_substitution_cached(*cond, subst, manager, cache);
let new_then = self.apply_substitution_cached(*then_br, subst, manager, cache);
let new_else = self.apply_substitution_cached(*else_br, subst, manager, cache);
manager.mk_ite(new_cond, new_then, new_else)
}
TermKind::Apply { func, args } => {
let func_name = manager.resolve_str(*func).to_string();
let new_args: SmallVec<[TermId; 4]> = args
.iter()
.map(|&a| self.apply_substitution_cached(a, subst, manager, cache))
.collect();
manager.mk_apply(&func_name, new_args, t.sort)
}
TermKind::Select(array, index) => {
let new_array = self.apply_substitution_cached(*array, subst, manager, cache);
let new_index = self.apply_substitution_cached(*index, subst, manager, cache);
manager.mk_select(new_array, new_index)
}
TermKind::Store(array, index, value) => {
let new_array = self.apply_substitution_cached(*array, subst, manager, cache);
let new_index = self.apply_substitution_cached(*index, subst, manager, cache);
let new_value = self.apply_substitution_cached(*value, subst, manager, cache);
manager.mk_store(new_array, new_index, new_value)
}
_ => term,
};
cache.insert(term, result);
result
}
fn evaluate_under_model(
&self,
term: TermId,
model: &CompletedModel,
manager: &mut TermManager,
) -> TermId {
let mut cache = FxHashMap::default();
self.evaluate_under_model_cached(term, model, manager, &mut cache)
}
fn evaluate_under_model_cached(
&self,
term: TermId,
model: &CompletedModel,
manager: &mut TermManager,
cache: &mut FxHashMap<TermId, TermId>,
) -> TermId {
if let Some(&cached) = cache.get(&term) {
return cached;
}
if let Some(val) = model.eval(term) {
cache.insert(term, val);
return val;
}
let Some(t) = manager.get(term).cloned() else {
return term;
};
let result = match &t.kind {
TermKind::True
| TermKind::False
| TermKind::IntConst(_)
| TermKind::RealConst(_)
| TermKind::BitVecConst { .. }
| TermKind::StringLit(_) => term,
TermKind::Not(arg) => {
let eval_arg = self.evaluate_under_model_cached(*arg, model, manager, cache);
if let Some(arg_t) = manager.get(eval_arg) {
match arg_t.kind {
TermKind::True => manager.mk_false(),
TermKind::False => manager.mk_true(),
_ => manager.mk_not(eval_arg),
}
} else {
manager.mk_not(eval_arg)
}
}
TermKind::And(args) => {
let mut all_true = true;
for &arg in args.iter() {
let eval_arg = self.evaluate_under_model_cached(arg, model, manager, cache);
if let Some(arg_t) = manager.get(eval_arg) {
match arg_t.kind {
TermKind::False => {
let false_val = manager.mk_false();
cache.insert(term, false_val);
return false_val;
}
TermKind::True => { }
_ => {
all_true = false;
}
}
} else {
all_true = false;
}
}
if all_true {
manager.mk_true()
} else {
term
}
}
TermKind::Or(args) => {
let mut all_false = true;
for &arg in args.iter() {
let eval_arg = self.evaluate_under_model_cached(arg, model, manager, cache);
if let Some(arg_t) = manager.get(eval_arg) {
match arg_t.kind {
TermKind::True => {
let true_val = manager.mk_true();
cache.insert(term, true_val);
return true_val;
}
TermKind::False => { }
_ => {
all_false = false;
}
}
} else {
all_false = false;
}
}
if all_false { manager.mk_false() } else { term }
}
TermKind::Implies(lhs, rhs) => {
let eval_lhs = self.evaluate_under_model_cached(*lhs, model, manager, cache);
if let Some(lhs_t) = manager.get(eval_lhs) {
match lhs_t.kind {
TermKind::False => manager.mk_true(),
TermKind::True => {
self.evaluate_under_model_cached(*rhs, model, manager, cache)
}
_ => {
let eval_rhs =
self.evaluate_under_model_cached(*rhs, model, manager, cache);
manager.mk_implies(eval_lhs, eval_rhs)
}
}
} else {
let eval_rhs = self.evaluate_under_model_cached(*rhs, model, manager, cache);
manager.mk_implies(eval_lhs, eval_rhs)
}
}
TermKind::Eq(lhs, rhs) => {
let eval_lhs = self.evaluate_under_model_cached(*lhs, model, manager, cache);
let eval_rhs = self.evaluate_under_model_cached(*rhs, model, manager, cache);
self.eval_eq(eval_lhs, eval_rhs, manager)
}
TermKind::Lt(lhs, rhs) => {
let eval_lhs = self.evaluate_under_model_cached(*lhs, model, manager, cache);
let eval_rhs = self.evaluate_under_model_cached(*rhs, model, manager, cache);
self.eval_lt(eval_lhs, eval_rhs, manager)
}
TermKind::Le(lhs, rhs) => {
let eval_lhs = self.evaluate_under_model_cached(*lhs, model, manager, cache);
let eval_rhs = self.evaluate_under_model_cached(*rhs, model, manager, cache);
self.eval_le(eval_lhs, eval_rhs, manager)
}
TermKind::Gt(lhs, rhs) => {
let eval_lhs = self.evaluate_under_model_cached(*lhs, model, manager, cache);
let eval_rhs = self.evaluate_under_model_cached(*rhs, model, manager, cache);
self.eval_gt(eval_lhs, eval_rhs, manager)
}
TermKind::Ge(lhs, rhs) => {
let eval_lhs = self.evaluate_under_model_cached(*lhs, model, manager, cache);
let eval_rhs = self.evaluate_under_model_cached(*rhs, model, manager, cache);
self.eval_ge(eval_lhs, eval_rhs, manager)
}
TermKind::Add(args) => {
let eval_args: SmallVec<[TermId; 4]> = args
.iter()
.map(|&a| self.evaluate_under_model_cached(a, model, manager, cache))
.collect();
self.eval_add(&eval_args, manager)
}
TermKind::Mul(args) => {
let eval_args: SmallVec<[TermId; 4]> = args
.iter()
.map(|&a| self.evaluate_under_model_cached(a, model, manager, cache))
.collect();
self.eval_mul(&eval_args, manager)
}
TermKind::Sub(lhs, rhs) => {
let eval_lhs = self.evaluate_under_model_cached(*lhs, model, manager, cache);
let eval_rhs = self.evaluate_under_model_cached(*rhs, model, manager, cache);
self.eval_sub(eval_lhs, eval_rhs, manager)
}
TermKind::Div(lhs, rhs) => {
let eval_lhs = self.evaluate_under_model_cached(*lhs, model, manager, cache);
let eval_rhs = self.evaluate_under_model_cached(*rhs, model, manager, cache);
self.eval_div(eval_lhs, eval_rhs, manager)
}
TermKind::Mod(lhs, rhs) => {
let eval_lhs = self.evaluate_under_model_cached(*lhs, model, manager, cache);
let eval_rhs = self.evaluate_under_model_cached(*rhs, model, manager, cache);
self.eval_modulo(eval_lhs, eval_rhs, manager)
}
TermKind::Neg(arg) => {
let eval_arg = self.evaluate_under_model_cached(*arg, model, manager, cache);
self.eval_neg(eval_arg, manager)
}
TermKind::Ite(cond, then_br, else_br) => {
let eval_cond = self.evaluate_under_model_cached(*cond, model, manager, cache);
if let Some(cond_t) = manager.get(eval_cond) {
match cond_t.kind {
TermKind::True => {
self.evaluate_under_model_cached(*then_br, model, manager, cache)
}
TermKind::False => {
self.evaluate_under_model_cached(*else_br, model, manager, cache)
}
_ => {
let eval_then =
self.evaluate_under_model_cached(*then_br, model, manager, cache);
let eval_else =
self.evaluate_under_model_cached(*else_br, model, manager, cache);
manager.mk_ite(eval_cond, eval_then, eval_else)
}
}
} else {
term
}
}
TermKind::Apply { func, args } => {
let evaluated_args: Vec<TermId> = args
.iter()
.map(|&a| self.evaluate_under_model_cached(a, model, manager, cache))
.collect();
if let Some(result) = model.eval_apply(*func, &evaluated_args) {
result
} else {
let func_name = manager.resolve_str(*func).to_string();
let new_term =
manager.mk_apply(&func_name, evaluated_args.iter().copied(), t.sort);
model.eval(new_term).unwrap_or(new_term)
}
}
TermKind::Forall { .. } => term,
TermKind::Exists {
vars,
body: exists_body,
..
} => {
let vars_vec: SmallVec<[(Spur, SortId); 2]> = vars.clone();
let body_id = *exists_body;
self.evaluate_exists_inline(&vars_vec, body_id, model, manager, cache)
}
TermKind::Select(array, index) => {
let eval_index = self.evaluate_under_model_cached(*index, model, manager, cache);
let select_with_orig_array = manager.mk_select(*array, eval_index);
if let Some(val) = model.eval(select_with_orig_array) {
val
} else if let Some(val) = model.eval(term) {
val
} else {
let eval_array =
self.evaluate_under_model_cached(*array, model, manager, cache);
let new_select = manager.mk_select(eval_array, eval_index);
if let Some(val) = model.eval(new_select) {
val
} else {
new_select
}
}
}
TermKind::Var(_) => model.eval(term).unwrap_or(term),
_ => manager.simplify(term),
};
cache.insert(term, result);
result
}
fn evaluate_exists_inline(
&self,
vars: &SmallVec<[(Spur, SortId); 2]>,
body: TermId,
model: &CompletedModel,
manager: &mut TermManager,
parent_cache: &mut FxHashMap<TermId, TermId>,
) -> TermId {
let mut candidate_lists: Vec<Vec<TermId>> = Vec::new();
for &(_var_name, sort) in vars {
let mut cands = Vec::new();
if let Some(universe) = model.universe(sort) {
cands.extend_from_slice(universe);
}
for (&term, &value) in &model.assignments {
if let Some(t) = manager.get(term)
&& t.sort == sort
&& !cands.contains(&value)
{
cands.push(value);
}
}
if sort == manager.sorts.int_sort {
for i in -2i64..=5 {
let val = manager.mk_int(BigInt::from(i));
if !cands.contains(&val) {
cands.push(val);
}
}
} else if sort == manager.sorts.bool_sort {
let t = manager.mk_true();
let f = manager.mk_false();
if !cands.contains(&t) {
cands.push(t);
}
if !cands.contains(&f) {
cands.push(f);
}
}
cands.truncate(self.max_candidates_per_var);
candidate_lists.push(cands);
}
if candidate_lists.is_empty() || candidate_lists.iter().any(|c| c.is_empty()) {
return body; }
let total_combos: usize = candidate_lists
.iter()
.map(|c| c.len())
.product::<usize>()
.min(50);
let mut found_true = false;
let mut found_symbolic = false;
let mut all_false = true;
let mut combo_count = 0;
let mut indices = vec![0usize; candidate_lists.len()];
loop {
if combo_count >= total_combos {
break;
}
let mut subst: FxHashMap<Spur, TermId> = FxHashMap::default();
for (i, &(var_name, _sort)) in vars.iter().enumerate() {
if let Some(&candidate) = candidate_lists[i].get(indices[i]) {
subst.insert(var_name, candidate);
}
}
let mut sub_cache: FxHashMap<TermId, TermId> = FxHashMap::default();
let substituted = self.apply_substitution_cached(body, &subst, manager, &mut sub_cache);
let evaluated =
self.evaluate_under_model_cached(substituted, model, manager, parent_cache);
if let Some(eval_t) = manager.get(evaluated) {
match eval_t.kind {
TermKind::True => {
found_true = true;
break; }
TermKind::False => {
}
_ => {
found_symbolic = true;
all_false = false;
}
}
} else {
found_symbolic = true;
all_false = false;
}
combo_count += 1;
let mut carry = true;
for (i, idx) in indices.iter_mut().enumerate() {
if carry {
*idx += 1;
let limit = candidate_lists.get(i).map_or(1, |c| c.len());
if *idx >= limit {
*idx = 0;
} else {
carry = false;
}
}
}
if carry {
break; }
}
if found_true {
manager.mk_true()
} else if all_false && !found_symbolic && combo_count > 0 {
manager.mk_false()
} else {
body }
}
fn eval_eq(&self, lhs: TermId, rhs: TermId, manager: &mut TermManager) -> TermId {
if lhs == rhs {
return manager.mk_true();
}
let lhs_t = manager.get(lhs);
let rhs_t = manager.get(rhs);
if let (Some(l), Some(r)) = (lhs_t, rhs_t) {
match (&l.kind, &r.kind) {
(TermKind::IntConst(a), TermKind::IntConst(b)) => {
if a == b {
manager.mk_true()
} else {
manager.mk_false()
}
}
(TermKind::RealConst(a), TermKind::RealConst(b)) => {
if a == b {
manager.mk_true()
} else {
manager.mk_false()
}
}
(TermKind::True, TermKind::True) | (TermKind::False, TermKind::False) => {
manager.mk_true()
}
(TermKind::True, TermKind::False) | (TermKind::False, TermKind::True) => {
manager.mk_false()
}
_ => manager.mk_eq(lhs, rhs),
}
} else {
manager.mk_eq(lhs, rhs)
}
}
fn eval_lt(&self, lhs: TermId, rhs: TermId, manager: &mut TermManager) -> TermId {
let lhs_t = manager.get(lhs);
let rhs_t = manager.get(rhs);
if let (Some(l), Some(r)) = (lhs_t, rhs_t) {
if let (TermKind::IntConst(a), TermKind::IntConst(b)) = (&l.kind, &r.kind) {
if a < b {
return manager.mk_true();
} else {
return manager.mk_false();
}
}
if let (TermKind::RealConst(a), TermKind::RealConst(b)) = (&l.kind, &r.kind) {
if a < b {
return manager.mk_true();
} else {
return manager.mk_false();
}
}
}
manager.mk_lt(lhs, rhs)
}
fn eval_le(&self, lhs: TermId, rhs: TermId, manager: &mut TermManager) -> TermId {
let lhs_t = manager.get(lhs);
let rhs_t = manager.get(rhs);
if let (Some(l), Some(r)) = (lhs_t, rhs_t) {
if let (TermKind::IntConst(a), TermKind::IntConst(b)) = (&l.kind, &r.kind) {
if a <= b {
return manager.mk_true();
} else {
return manager.mk_false();
}
}
if let (TermKind::RealConst(a), TermKind::RealConst(b)) = (&l.kind, &r.kind) {
if a <= b {
return manager.mk_true();
} else {
return manager.mk_false();
}
}
}
manager.mk_le(lhs, rhs)
}
fn eval_gt(&self, lhs: TermId, rhs: TermId, manager: &mut TermManager) -> TermId {
self.eval_lt(rhs, lhs, manager)
}
fn eval_ge(&self, lhs: TermId, rhs: TermId, manager: &mut TermManager) -> TermId {
self.eval_le(rhs, lhs, manager)
}
fn eval_add(&self, args: &[TermId], manager: &mut TermManager) -> TermId {
let mut result = BigInt::from(0);
let mut all_ints = true;
for &arg in args {
if let Some(arg_t) = manager.get(arg) {
if let TermKind::IntConst(val) = &arg_t.kind {
result += val;
} else {
all_ints = false;
break;
}
} else {
all_ints = false;
break;
}
}
if all_ints {
manager.mk_int(result)
} else {
manager.mk_add(args.iter().copied())
}
}
fn eval_mul(&self, args: &[TermId], manager: &mut TermManager) -> TermId {
let mut result = BigInt::from(1);
let mut all_ints = true;
for &arg in args {
if let Some(arg_t) = manager.get(arg) {
if let TermKind::IntConst(val) = &arg_t.kind {
result *= val;
} else {
all_ints = false;
break;
}
} else {
all_ints = false;
break;
}
}
if all_ints {
manager.mk_int(result)
} else {
manager.mk_mul(args.iter().copied())
}
}
fn eval_sub(&self, lhs: TermId, rhs: TermId, manager: &mut TermManager) -> TermId {
let lhs_t = manager.get(lhs);
let rhs_t = manager.get(rhs);
if let (Some(l), Some(r)) = (lhs_t, rhs_t) {
if let (TermKind::IntConst(a), TermKind::IntConst(b)) = (&l.kind, &r.kind) {
return manager.mk_int(a - b);
}
}
manager.mk_sub(lhs, rhs)
}
fn eval_div(&self, lhs: TermId, rhs: TermId, manager: &mut TermManager) -> TermId {
let lhs_t = manager.get(lhs);
let rhs_t = manager.get(rhs);
if let (Some(l), Some(r)) = (lhs_t, rhs_t) {
if let (TermKind::IntConst(a), TermKind::IntConst(b)) = (&l.kind, &r.kind) {
if *b != BigInt::from(0) {
return manager.mk_int(a / b);
}
}
}
manager.mk_div(lhs, rhs)
}
fn eval_modulo(&self, lhs: TermId, rhs: TermId, manager: &mut TermManager) -> TermId {
let lhs_t = manager.get(lhs);
let rhs_t = manager.get(rhs);
if let (Some(l), Some(r)) = (lhs_t, rhs_t) {
if let (TermKind::IntConst(a), TermKind::IntConst(b)) = (&l.kind, &r.kind) {
if *b != BigInt::from(0) {
return manager.mk_int(a % b);
}
}
}
manager.mk_mod(lhs, rhs)
}
fn eval_neg(&self, arg: TermId, manager: &mut TermManager) -> TermId {
if let Some(arg_t) = manager.get(arg) {
if let TermKind::IntConst(val) = &arg_t.kind {
return manager.mk_int(-val);
}
}
manager.mk_neg(arg)
}
fn is_counterexample(
&self,
evaluated: TermId,
is_universal: bool,
manager: &TermManager,
) -> bool {
let Some(eval_t) = manager.get(evaluated) else {
return false;
};
if is_universal {
matches!(eval_t.kind, TermKind::False)
} else {
matches!(eval_t.kind, TermKind::True)
}
}
fn is_ground_boolean(&self, evaluated: TermId, manager: &TermManager) -> bool {
let Some(eval_t) = manager.get(evaluated) else {
return false;
};
matches!(eval_t.kind, TermKind::True | TermKind::False)
}
pub fn set_generation_bound(&mut self, bound: u32) {
self.generation_bound = bound;
}
pub fn inject_extra_candidates(&mut self, extras: &FxHashMap<SortId, Vec<TermId>>) {
for (&sort, terms) in extras {
let entry = self.candidate_cache.entry(sort).or_default();
for &t in terms {
if !entry.contains(&t) {
entry.push(t);
}
}
}
}
pub fn clear_cache(&mut self) {
self.candidate_cache.clear();
}
pub fn stats(&self) -> &CexStats {
&self.stats
}
}
impl Default for CounterExampleGenerator {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RefinementStrategy {
None,
BlockCounterexamples,
ConflictLearning,
Generalization,
}
#[derive(Debug, Clone, Default)]
pub struct CexStats {
pub num_searches: usize,
pub num_counterexamples_found: usize,
pub num_combinations_tried: usize,
pub num_timeouts: usize,
#[cfg(feature = "std")]
pub total_time: Duration,
}
impl fmt::Display for CexStats {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "Counterexample Statistics:")?;
writeln!(f, " Searches: {}", self.num_searches)?;
writeln!(f, " CEX found: {}", self.num_counterexamples_found)?;
writeln!(f, " Combinations tried: {}", self.num_combinations_tried)?;
writeln!(f, " Timeouts: {}", self.num_timeouts)?;
#[cfg(feature = "std")]
writeln!(f, " Total time: {:.2}ms", self.total_time.as_millis())?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_counterexample_creation() {
let cex = CounterExample::new(TermId::new(1), FxHashMap::default(), vec![], 0);
assert_eq!(cex.quantifier, TermId::new(1));
assert_eq!(cex.quality, 1.0);
}
#[test]
fn test_cex_generator_creation() {
let generator = CounterExampleGenerator::new();
assert_eq!(generator.max_cex_per_quantifier, 5);
assert_eq!(generator.max_candidates_per_var, 10);
}
#[test]
fn test_cex_generator_with_limits() {
let generator = CounterExampleGenerator::with_limits(10, 20, Duration::from_secs(2));
assert_eq!(generator.max_cex_per_quantifier, 10);
assert_eq!(generator.max_candidates_per_var, 20);
assert_eq!(generator.max_search_time, Duration::from_secs(2));
}
#[test]
fn test_enumerate_combinations_empty() {
let generator = CounterExampleGenerator::new();
let combos = generator.enumerate_combinations(&[], 10, 100);
assert_eq!(combos.len(), 1);
assert!(combos[0].is_empty());
}
#[test]
fn test_enumerate_combinations_single() {
let generator = CounterExampleGenerator::new();
let candidates = vec![vec![TermId::new(1), TermId::new(2)]];
let combos = generator.enumerate_combinations(&candidates, 10, 100);
assert_eq!(combos.len(), 2);
}
#[test]
fn test_enumerate_combinations_multiple() {
let generator = CounterExampleGenerator::new();
let candidates = vec![
vec![TermId::new(1), TermId::new(2)],
vec![TermId::new(3), TermId::new(4)],
];
let combos = generator.enumerate_combinations(&candidates, 10, 100);
assert_eq!(combos.len(), 4); }
#[test]
fn test_enumerate_combinations_limit() {
let generator = CounterExampleGenerator::new();
let candidates = vec![
vec![TermId::new(1), TermId::new(2), TermId::new(3)],
vec![TermId::new(4), TermId::new(5), TermId::new(6)],
];
let combos = generator.enumerate_combinations(&candidates, 10, 5);
assert!(combos.len() <= 5);
}
#[test]
fn test_cex_stats_display() {
let stats = CexStats {
num_searches: 10,
num_counterexamples_found: 5,
num_combinations_tried: 100,
num_timeouts: 1,
total_time: Duration::from_millis(500),
};
let display = format!("{}", stats);
assert!(display.contains("Searches: 10"));
assert!(display.contains("CEX found: 5"));
}
#[test]
fn test_refinement_strategy() {
assert_ne!(
RefinementStrategy::None,
RefinementStrategy::BlockCounterexamples
);
}
}