#![allow(missing_docs)]
#![allow(dead_code)]
#[allow(unused_imports)]
use crate::prelude::*;
use core::cmp::Ordering;
use core::fmt;
use num_bigint::BigInt;
use num_rational::Rational64;
use num_traits::ToPrimitive;
use oxiz_core::ast::{TermId, TermKind, TermManager};
use oxiz_core::interner::Spur;
use oxiz_core::sort::SortId;
use smallvec::SmallVec;
type FuncEntry = (SmallVec<[SortId; 4]>, SortId, Vec<TermId>, TermId);
use super::QuantifiedFormula;
const MAX_UNIVERSE_SIZE: usize = 1000;
#[derive(Debug, Clone)]
pub struct CompletedModel {
pub assignments: FxHashMap<TermId, TermId>,
pub function_interps: FxHashMap<Spur, FunctionInterpretation>,
pub universes: FxHashMap<SortId, Vec<TermId>>,
pub defaults: FxHashMap<SortId, TermId>,
pub generation: u32,
}
impl CompletedModel {
pub fn new() -> Self {
Self {
assignments: FxHashMap::default(),
function_interps: FxHashMap::default(),
universes: FxHashMap::default(),
defaults: FxHashMap::default(),
generation: 0,
}
}
pub fn eval(&self, term: TermId) -> Option<TermId> {
self.assignments.get(&term).copied()
}
pub fn set(&mut self, term: TermId, value: TermId) {
self.assignments.insert(term, value);
}
pub fn universe(&self, sort: SortId) -> Option<&[TermId]> {
self.universes.get(&sort).map(|v| v.as_slice())
}
pub fn add_to_universe(&mut self, sort: SortId, value: TermId) {
self.universes.entry(sort).or_default().push(value);
}
pub fn default_value(&self, sort: SortId) -> Option<TermId> {
self.defaults.get(&sort).copied()
}
pub fn set_default(&mut self, sort: SortId, value: TermId) {
self.defaults.insert(sort, value);
}
pub fn has_uninterpreted_sort(&self, sort: SortId) -> bool {
self.universes.contains_key(&sort)
}
pub fn eval_apply(&self, func: Spur, evaluated_args: &[TermId]) -> Option<TermId> {
if let Some(interp) = self.function_interps.get(&func) {
if let Some(result) = interp.lookup(evaluated_args) {
return Some(result);
}
if let Some(else_val) = interp.else_value {
return Some(else_val);
}
if let Some(default) = self.defaults.get(&interp.range) {
return Some(*default);
}
}
None
}
pub fn collect_universes_from_model(
&mut self,
quantifiers: &[QuantifiedFormula],
manager: &TermManager,
) {
let mut needed_sorts: FxHashSet<SortId> = FxHashSet::default();
for quant in quantifiers {
for &(_name, sort) in &quant.bound_vars {
needed_sorts.insert(sort);
}
}
for sort in needed_sorts {
if self.universes.contains_key(&sort) {
continue;
}
let mut universe_values: Vec<TermId> = Vec::new();
let mut seen: FxHashSet<TermId> = FxHashSet::default();
for (&term, &value) in &self.assignments {
if let Some(t) = manager.get(term) {
if t.sort == sort && seen.insert(value) {
universe_values.push(value);
}
}
if let Some(v) = manager.get(value) {
if v.sort == sort && seen.insert(value) {
universe_values.push(value);
}
}
}
for interp in self.function_interps.values() {
for entry in &interp.entries {
for (i, &arg) in entry.args.iter().enumerate() {
if i < interp.domain.len() && interp.domain[i] == sort && seen.insert(arg) {
universe_values.push(arg);
}
}
if interp.range == sort && seen.insert(entry.result) {
universe_values.push(entry.result);
}
}
}
universe_values.truncate(MAX_UNIVERSE_SIZE);
if !universe_values.is_empty() {
self.universes.insert(sort, universe_values);
}
}
}
pub fn complete_function_interpretations(&mut self) {
let updates: Vec<(Spur, TermId)> = self
.function_interps
.iter()
.filter_map(|(&name, interp)| {
if interp.else_value.is_some() {
return None;
}
if !interp.entries.is_empty() {
return None;
}
if let Some(&default) = self.defaults.get(&interp.range) {
return Some((name, default));
}
None
})
.collect();
for (name, else_val) in updates {
if let Some(interp) = self.function_interps.get_mut(&name) {
interp.else_value = Some(else_val);
}
}
}
}
impl Default for CompletedModel {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct FunctionInterpretation {
pub name: Spur,
pub arity: usize,
pub domain: SmallVec<[SortId; 4]>,
pub range: SortId,
pub entries: Vec<FunctionEntry>,
pub else_value: Option<TermId>,
pub projections: Vec<Option<ProjectionFunctionDef>>,
}
impl FunctionInterpretation {
pub fn new(name: Spur, domain: SmallVec<[SortId; 4]>, range: SortId) -> Self {
let arity = domain.len();
Self {
name,
arity,
domain,
range,
entries: Vec::new(),
else_value: None,
projections: vec![None; arity],
}
}
pub fn add_entry(&mut self, args: Vec<TermId>, result: TermId) {
if args.len() == self.arity {
self.entries.push(FunctionEntry { args, result });
}
}
pub fn lookup(&self, args: &[TermId]) -> Option<TermId> {
for entry in &self.entries {
if entry.args == args {
return Some(entry.result);
}
}
self.else_value
}
pub fn is_constant(&self) -> bool {
self.arity == 0
}
pub fn is_partial(&self) -> bool {
self.else_value.is_none() && !self.entries.is_empty()
}
pub fn max_occurrence_result(&self) -> Option<TermId> {
if self.entries.is_empty() {
return None;
}
let mut counts: FxHashMap<TermId, usize> = FxHashMap::default();
for entry in &self.entries {
*counts.entry(entry.result).or_insert(0) += 1;
}
counts
.into_iter()
.max_by_key(|(_, count)| *count)
.map(|(term, _)| term)
}
}
#[derive(Debug, Clone)]
pub struct FunctionEntry {
pub args: Vec<TermId>,
pub result: TermId,
}
#[derive(Debug, Clone)]
pub struct ProjectionFunctionDef {
pub arg_index: usize,
pub sort: SortId,
pub values: Vec<TermId>,
pub value_to_term: FxHashMap<TermId, TermId>,
pub term_to_value: FxHashMap<TermId, TermId>,
}
impl ProjectionFunctionDef {
pub fn new(arg_index: usize, sort: SortId) -> Self {
Self {
arg_index,
sort,
values: Vec::new(),
value_to_term: FxHashMap::default(),
term_to_value: FxHashMap::default(),
}
}
pub fn add_value(&mut self, value: TermId, term: TermId) {
if !self.values.contains(&value) {
self.values.push(value);
}
self.value_to_term.insert(value, term);
self.term_to_value.insert(term, value);
}
pub fn project(&self, value: TermId) -> Option<TermId> {
self.value_to_term.get(&value).copied()
}
}
#[derive(Debug)]
pub struct ModelCompleter {
macro_solver: MacroSolver,
model_fixer: ModelFixer,
uninterp_handler: UninterpretedSortHandler,
cache: FxHashMap<u64, CompletedModel>,
stats: CompletionStats,
}
impl ModelCompleter {
pub fn new() -> Self {
Self {
macro_solver: MacroSolver::new(),
model_fixer: ModelFixer::new(),
uninterp_handler: UninterpretedSortHandler::new(),
cache: FxHashMap::default(),
stats: CompletionStats::default(),
}
}
pub fn complete(
&mut self,
partial_model: &FxHashMap<TermId, TermId>,
quantifiers: &[QuantifiedFormula],
manager: &mut TermManager,
) -> Result<CompletedModel, CompletionError> {
self.stats.num_completions += 1;
let mut completed = CompletedModel::new();
completed.assignments = partial_model.clone();
self.extract_function_interpretations(&mut completed, manager);
let macro_results = self.macro_solver.solve_macros(quantifiers, manager)?;
for (func_name, macro_interp) in macro_results {
completed
.function_interps
.entry(func_name)
.or_insert(macro_interp);
}
self.model_fixer
.fix_model(&mut completed, quantifiers, manager)?;
self.uninterp_handler
.complete_universes(&mut completed, manager)?;
self.set_default_values(&mut completed, manager)?;
completed.collect_universes_from_model(quantifiers, manager);
self.set_default_values(&mut completed, manager)?;
completed.complete_function_interpretations();
Ok(completed)
}
fn eval_to_const(term: TermId, manager: &mut TermManager) -> Option<TermId> {
let t = manager.get(term)?.clone();
match &t.kind {
TermKind::IntConst(_) | TermKind::RealConst(_) => Some(term),
TermKind::Neg(arg) => {
let inner = Self::eval_to_const(*arg, manager)?;
let inner_t = manager.get(inner)?.clone();
match &inner_t.kind {
TermKind::IntConst(n) => {
let neg_n = -n.clone();
Some(manager.mk_int(neg_n))
}
TermKind::RealConst(r) => {
let neg_r = -*r;
Some(manager.mk_real(neg_r))
}
_ => None,
}
}
TermKind::Add(args) => {
let args_cloned: SmallVec<[TermId; 4]> = args.clone();
let mut sum_r = Rational64::from_integer(0);
let mut all_real = true;
let mut all_int = true;
let mut sum_i = num_bigint::BigInt::from(0i64);
for &arg in &args_cloned {
if let Some(c) = Self::eval_to_const(arg, manager) {
let ct = manager.get(c)?.clone();
match &ct.kind {
TermKind::RealConst(r) => {
sum_r += r;
all_int = false;
}
TermKind::IntConst(n) => {
sum_r += Rational64::from_integer(n.to_i64().unwrap_or(0));
sum_i += n.clone();
all_real = false;
}
_ => {
all_real = false;
all_int = false;
}
}
} else {
all_real = false;
all_int = false;
}
}
if all_int && !args_cloned.is_empty() {
Some(manager.mk_int(sum_i))
} else if all_real && !args_cloned.is_empty() {
Some(manager.mk_real(sum_r))
} else {
None
}
}
TermKind::Sub(lhs, rhs) => {
let (lhs_v, rhs_v) = (*lhs, *rhs);
let lc = Self::eval_to_const(lhs_v, manager)?;
let rc = Self::eval_to_const(rhs_v, manager)?;
let lct = manager.get(lc)?.clone();
let rct = manager.get(rc)?.clone();
match (&lct.kind, &rct.kind) {
(TermKind::IntConst(a), TermKind::IntConst(b)) => Some(manager.mk_int(a - b)),
(TermKind::RealConst(a), TermKind::RealConst(b)) => {
Some(manager.mk_real(a - b))
}
_ => None,
}
}
_ => None,
}
}
fn eval_arg(term: TermId, model: &CompletedModel, manager: &mut TermManager) -> TermId {
if let Some(val) = model.eval(term) {
return val;
}
if let Some(const_val) = Self::eval_to_const(term, manager) {
return const_val;
}
term
}
fn extract_function_interpretations(
&self,
model: &mut CompletedModel,
manager: &mut TermManager,
) {
let mut func_entries: FxHashMap<Spur, Vec<FuncEntry>> = FxHashMap::default();
let apply_entries: Vec<(TermId, TermId)> = model
.assignments
.iter()
.filter_map(|(&term, &value)| {
if manager
.get(term)
.is_some_and(|t| matches!(t.kind, TermKind::Apply { .. }))
{
Some((term, value))
} else {
None
}
})
.collect();
for (term, value) in apply_entries {
let Some(t) = manager.get(term).cloned() else {
continue;
};
if let TermKind::Apply { func, args } = &t.kind {
let args_cloned: SmallVec<[TermId; 4]> = args.clone();
let evaluated_args: Vec<TermId> = args_cloned
.iter()
.map(|&arg| Self::eval_arg(arg, model, manager))
.collect();
let domain: SmallVec<[SortId; 4]> = args
.iter()
.map(|&arg| manager.get(arg).map_or(manager.sorts.int_sort, |a| a.sort))
.collect();
func_entries.entry(*func).or_default().push((
domain,
t.sort,
evaluated_args,
value,
));
}
}
for (func_name, entries) in func_entries {
match model.function_interps.entry(func_name) {
std::collections::hash_map::Entry::Occupied(mut occupied) => {
let interp = occupied.get_mut();
for (_domain, _range, args, result) in entries {
let already_exists = interp.entries.iter().any(|e| e.args == args);
if !already_exists {
interp.add_entry(args, result);
}
}
}
std::collections::hash_map::Entry::Vacant(vacant) => {
if let Some((domain, range, first_args, first_result)) = entries.first() {
let mut interp =
FunctionInterpretation::new(func_name, domain.clone(), *range);
interp.add_entry(first_args.clone(), *first_result);
for (_, _, args, result) in entries.iter().skip(1) {
let already_exists = interp.entries.iter().any(|e| &e.args == args);
if !already_exists {
interp.add_entry(args.clone(), *result);
}
}
vacant.insert(interp);
}
}
}
}
}
fn set_default_values(
&mut self,
model: &mut CompletedModel,
manager: &mut TermManager,
) -> Result<(), CompletionError> {
if !model.defaults.contains_key(&manager.sorts.bool_sort) {
model.set_default(manager.sorts.bool_sort, manager.mk_false());
}
if !model.defaults.contains_key(&manager.sorts.int_sort) {
model.set_default(manager.sorts.int_sort, manager.mk_int(BigInt::from(0)));
}
if !model.defaults.contains_key(&manager.sorts.real_sort) {
model.set_default(
manager.sorts.real_sort,
manager.mk_real(Rational64::from_integer(0)),
);
}
let defaults_to_set: Vec<(SortId, TermId)> = model
.universes
.iter()
.filter_map(|(sort, universe)| {
if !model.defaults.contains_key(sort) {
universe.first().map(|&first| (*sort, first))
} else {
None
}
})
.collect();
for (sort, value) in defaults_to_set {
model.set_default(sort, value);
}
Ok(())
}
pub fn stats(&self) -> &CompletionStats {
&self.stats
}
}
impl Default for ModelCompleter {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct MacroSolver {
macros: FxHashMap<Spur, MacroDefinition>,
stats: MacroStats,
}
impl MacroSolver {
pub fn new() -> Self {
Self {
macros: FxHashMap::default(),
stats: MacroStats::default(),
}
}
pub fn solve_macros(
&mut self,
quantifiers: &[QuantifiedFormula],
manager: &mut TermManager,
) -> Result<FxHashMap<Spur, FunctionInterpretation>, CompletionError> {
let mut results = FxHashMap::default();
for quant in quantifiers {
if let Some(macro_def) = self.try_extract_macro(quant, manager)? {
self.stats.num_macros_found += 1;
let interp = self.macro_to_interpretation(¯o_def, manager)?;
results.insert(macro_def.func_name, interp);
self.macros.insert(macro_def.func_name, macro_def);
}
}
Ok(results)
}
fn try_extract_macro(
&self,
quant: &QuantifiedFormula,
manager: &TermManager,
) -> Result<Option<MacroDefinition>, CompletionError> {
let Some(body_term) = manager.get(quant.body) else {
return Ok(None);
};
if let TermKind::Eq(lhs, rhs) = &body_term.kind {
if let Some(macro_def) = self.try_extract_macro_from_eq(*lhs, *rhs, quant, manager)? {
return Ok(Some(macro_def));
}
if let Some(macro_def) = self.try_extract_macro_from_eq(*rhs, *lhs, quant, manager)? {
return Ok(Some(macro_def));
}
}
Ok(None)
}
fn try_extract_macro_from_eq(
&self,
lhs: TermId,
rhs: TermId,
quant: &QuantifiedFormula,
manager: &TermManager,
) -> Result<Option<MacroDefinition>, CompletionError> {
let Some(lhs_term) = manager.get(lhs) else {
return Ok(None);
};
if let TermKind::Apply { func, args } = &lhs_term.kind {
let mut is_macro = true;
for &arg in args.iter() {
if let Some(arg_term) = manager.get(arg)
&& !matches!(arg_term.kind, TermKind::Var(_))
{
is_macro = false;
break;
}
}
if is_macro {
if !self.contains_function(rhs, *func, manager) {
return Ok(Some(MacroDefinition {
quantifier: quant.term,
func_name: *func,
bound_vars: quant.bound_vars.clone(),
body: rhs,
}));
}
}
}
Ok(None)
}
fn contains_function(&self, term: TermId, func: Spur, manager: &TermManager) -> bool {
let mut visited = FxHashSet::default();
self.contains_function_rec(term, func, manager, &mut visited)
}
fn contains_function_rec(
&self,
term: TermId,
func: Spur,
manager: &TermManager,
visited: &mut FxHashSet<TermId>,
) -> bool {
if visited.contains(&term) {
return false;
}
visited.insert(term);
let Some(t) = manager.get(term) else {
return false;
};
match &t.kind {
TermKind::Apply { func: f, args } => {
if *f == func {
return true;
}
for &arg in args.iter() {
if self.contains_function_rec(arg, func, manager, visited) {
return true;
}
}
false
}
_ => {
let children = self.get_children(term, manager);
for child in children {
if self.contains_function_rec(child, func, manager, visited) {
return true;
}
}
false
}
}
}
fn get_children(&self, term: TermId, manager: &TermManager) -> Vec<TermId> {
let Some(t) = manager.get(term) else {
return vec![];
};
match &t.kind {
TermKind::Not(arg) | TermKind::Neg(arg) => vec![*arg],
TermKind::And(args)
| TermKind::Or(args)
| TermKind::Add(args)
| TermKind::Mul(args) => args.to_vec(),
TermKind::Sub(lhs, rhs)
| TermKind::Div(lhs, rhs)
| TermKind::Mod(lhs, rhs)
| TermKind::Eq(lhs, rhs)
| TermKind::Lt(lhs, rhs)
| TermKind::Le(lhs, rhs)
| TermKind::Gt(lhs, rhs)
| TermKind::Ge(lhs, rhs)
| TermKind::Implies(lhs, rhs) => vec![*lhs, *rhs],
TermKind::Ite(cond, then_br, else_br) => vec![*cond, *then_br, *else_br],
TermKind::Apply { args, .. } => args.to_vec(),
_ => vec![],
}
}
fn macro_to_interpretation(
&self,
macro_def: &MacroDefinition,
manager: &mut TermManager,
) -> Result<FunctionInterpretation, CompletionError> {
let func_name = macro_def.func_name;
let domain: SmallVec<[SortId; 4]> =
macro_def.bound_vars.iter().map(|&(_, sort)| sort).collect();
let range = manager
.get(macro_def.body)
.map_or(manager.sorts.bool_sort, |t| t.sort);
let interp = FunctionInterpretation::new(func_name, domain, range);
Ok(interp)
}
pub fn stats(&self) -> &MacroStats {
&self.stats
}
}
impl Default for MacroSolver {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct MacroDefinition {
pub quantifier: TermId,
pub func_name: Spur,
pub bound_vars: SmallVec<[(Spur, SortId); 4]>,
pub body: TermId,
}
#[derive(Debug)]
pub struct ModelFixer {
projections: FxHashMap<SortId, Box<dyn ProjectionFunction>>,
stats: FixerStats,
}
impl ModelFixer {
pub fn new() -> Self {
Self {
projections: FxHashMap::default(),
stats: FixerStats::default(),
}
}
pub fn fix_model(
&mut self,
model: &mut CompletedModel,
quantifiers: &[QuantifiedFormula],
manager: &mut TermManager,
) -> Result<(), CompletionError> {
self.stats.num_fixes += 1;
let partial_functions = self.collect_partial_functions(quantifiers, manager);
for func_name in partial_functions.iter() {
let has_interp = model.function_interps.contains_key(func_name);
if has_interp {
if let Some(interp) = model.function_interps.get_mut(func_name) {
for arg_idx in 0..interp.arity {
let sort = interp.domain[arg_idx];
if self.needs_projection(sort, manager) {
interp.projections[arg_idx] = None;
}
}
}
}
}
Ok(())
}
fn collect_partial_functions(
&self,
quantifiers: &[QuantifiedFormula],
manager: &TermManager,
) -> FxHashSet<Spur> {
let mut functions = FxHashSet::default();
for quant in quantifiers {
self.collect_partial_functions_rec(quant.body, &mut functions, manager);
}
functions
}
fn collect_partial_functions_rec(
&self,
term: TermId,
functions: &mut FxHashSet<Spur>,
manager: &TermManager,
) {
let Some(t) = manager.get(term) else {
return;
};
if let TermKind::Apply { func, args } = &t.kind {
let has_vars = args.iter().any(|&arg| {
if let Some(arg_t) = manager.get(arg) {
matches!(arg_t.kind, TermKind::Var(_))
} else {
false
}
});
if has_vars {
functions.insert(*func);
}
for &arg in args.iter() {
self.collect_partial_functions_rec(arg, functions, manager);
}
}
match &t.kind {
TermKind::Not(arg) | TermKind::Neg(arg) => {
self.collect_partial_functions_rec(*arg, functions, manager);
}
TermKind::And(args) | TermKind::Or(args) => {
for &arg in args.iter() {
self.collect_partial_functions_rec(arg, functions, manager);
}
}
TermKind::Eq(lhs, rhs) | TermKind::Lt(lhs, rhs) | TermKind::Le(lhs, rhs) => {
self.collect_partial_functions_rec(*lhs, functions, manager);
self.collect_partial_functions_rec(*rhs, functions, manager);
}
_ => {}
}
}
fn add_projection_functions(
&mut self,
interp: &mut FunctionInterpretation,
model: &CompletedModel,
manager: &mut TermManager,
) -> Result<(), CompletionError> {
for arg_idx in 0..interp.arity {
let sort = interp.domain[arg_idx];
if self.needs_projection(sort, manager) {
let proj_def = self.create_projection(interp, arg_idx, model, manager)?;
interp.projections[arg_idx] = Some(proj_def);
}
}
Ok(())
}
fn needs_projection(&self, sort: SortId, manager: &TermManager) -> bool {
sort == manager.sorts.int_sort || sort == manager.sorts.real_sort
}
fn create_projection(
&mut self,
interp: &FunctionInterpretation,
arg_idx: usize,
model: &CompletedModel,
manager: &mut TermManager,
) -> Result<ProjectionFunctionDef, CompletionError> {
let sort = interp.domain[arg_idx];
let mut proj_def = ProjectionFunctionDef::new(arg_idx, sort);
for entry in &interp.entries {
if let Some(&arg_term) = entry.args.get(arg_idx) {
let value = model.eval(arg_term).unwrap_or(arg_term);
proj_def.add_value(value, arg_term);
}
}
proj_def
.values
.sort_by(|a, b| self.compare_values(*a, *b, sort, manager));
Ok(proj_def)
}
fn compare_values(
&self,
a: TermId,
b: TermId,
_sort: SortId,
manager: &TermManager,
) -> Ordering {
let a_term = manager.get(a);
let b_term = manager.get(b);
if let (Some(at), Some(bt)) = (a_term, b_term) {
if let (TermKind::IntConst(av), TermKind::IntConst(bv)) = (&at.kind, &bt.kind) {
return av.cmp(bv);
}
if let (TermKind::RealConst(av), TermKind::RealConst(bv)) = (&at.kind, &bt.kind) {
return av.cmp(bv);
}
match (&at.kind, &bt.kind) {
(TermKind::False, TermKind::True) => return Ordering::Less,
(TermKind::True, TermKind::False) => return Ordering::Greater,
(TermKind::False, TermKind::False) | (TermKind::True, TermKind::True) => {
return Ordering::Equal;
}
_ => {}
}
}
a.0.cmp(&b.0)
}
pub fn stats(&self) -> &FixerStats {
&self.stats
}
}
impl Default for ModelFixer {
fn default() -> Self {
Self::new()
}
}
pub trait ProjectionFunction: fmt::Debug + Send + Sync {
fn compare(&self, a: TermId, b: TermId, manager: &TermManager) -> bool;
fn mk_lt(&self, x: TermId, y: TermId, manager: &mut TermManager) -> TermId;
}
#[derive(Debug)]
pub struct ArithmeticProjection {
is_int: bool,
}
impl ArithmeticProjection {
pub fn new(is_int: bool) -> Self {
Self { is_int }
}
}
impl ProjectionFunction for ArithmeticProjection {
fn compare(&self, a: TermId, b: TermId, manager: &TermManager) -> bool {
let a_term = manager.get(a);
let b_term = manager.get(b);
if let (Some(at), Some(bt)) = (a_term, b_term) {
if let (TermKind::IntConst(av), TermKind::IntConst(bv)) = (&at.kind, &bt.kind) {
return av < bv;
}
if let (TermKind::RealConst(av), TermKind::RealConst(bv)) = (&at.kind, &bt.kind) {
return av < bv;
}
}
a.0 < b.0
}
fn mk_lt(&self, x: TermId, y: TermId, manager: &mut TermManager) -> TermId {
manager.mk_lt(x, y)
}
}
#[derive(Debug)]
pub struct UninterpretedSortHandler {
max_universe_size: usize,
stats: UninterpStats,
}
impl UninterpretedSortHandler {
pub fn new() -> Self {
Self {
max_universe_size: 8,
stats: UninterpStats::default(),
}
}
pub fn with_max_size(max_size: usize) -> Self {
let mut handler = Self::new();
handler.max_universe_size = max_size;
handler
}
pub fn complete_universes(
&mut self,
model: &mut CompletedModel,
manager: &mut TermManager,
) -> Result<(), CompletionError> {
let uninterp_sorts = self.identify_uninterpreted_sorts(model, manager);
for sort in uninterp_sorts {
if let crate::prelude::hash_map::Entry::Vacant(e) = model.universes.entry(sort) {
let universe = self.create_finite_universe(sort, manager)?;
e.insert(universe);
self.stats.num_universes_created += 1;
}
}
Ok(())
}
fn identify_uninterpreted_sorts(
&self,
model: &CompletedModel,
manager: &TermManager,
) -> Vec<SortId> {
let mut sorts = Vec::new();
for interp in model.function_interps.values() {
for &sort in &interp.domain {
if self.is_uninterpreted(sort, manager) && !sorts.contains(&sort) {
sorts.push(sort);
}
}
if self.is_uninterpreted(interp.range, manager) && !sorts.contains(&interp.range) {
sorts.push(interp.range);
}
}
sorts
}
fn is_uninterpreted(&self, sort: SortId, manager: &TermManager) -> bool {
sort != manager.sorts.bool_sort
&& sort != manager.sorts.int_sort
&& sort != manager.sorts.real_sort
}
fn create_finite_universe(
&self,
sort: SortId,
manager: &mut TermManager,
) -> Result<Vec<TermId>, CompletionError> {
let mut universe = Vec::new();
for i in 0..self.max_universe_size {
let name = format!("u!{}", i);
let const_id = manager.mk_var(&name, sort);
universe.push(const_id);
}
Ok(universe)
}
pub fn stats(&self) -> &UninterpStats {
&self.stats
}
}
impl Default for UninterpretedSortHandler {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub enum CompletionError {
CompletionFailed(String),
ResourceLimit,
InvalidModel(String),
}
impl fmt::Display for CompletionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::CompletionFailed(msg) => write!(f, "Model completion failed: {}", msg),
Self::ResourceLimit => write!(f, "Resource limit exceeded during completion"),
Self::InvalidModel(msg) => write!(f, "Invalid model: {}", msg),
}
}
}
impl core::error::Error for CompletionError {}
#[derive(Debug, Clone, Default)]
pub struct CompletionStats {
pub num_completions: usize,
pub num_failures: usize,
}
#[derive(Debug, Clone, Default)]
pub struct MacroStats {
pub num_macros_found: usize,
pub num_macros_applied: usize,
}
#[derive(Debug, Clone, Default)]
pub struct FixerStats {
pub num_fixes: usize,
pub num_projections_created: usize,
}
#[derive(Debug, Clone, Default)]
pub struct UninterpStats {
pub num_universes_created: usize,
pub total_universe_size: usize,
}
#[cfg(test)]
mod tests {
use super::*;
use oxiz_core::interner::Key;
#[test]
fn test_completed_model_creation() {
let model = CompletedModel::new();
assert_eq!(model.assignments.len(), 0);
assert_eq!(model.function_interps.len(), 0);
}
#[test]
fn test_completed_model_eval() {
let mut model = CompletedModel::new();
let term = TermId::new(1);
let value = TermId::new(2);
model.set(term, value);
assert_eq!(model.eval(term), Some(value));
assert_eq!(model.eval(TermId::new(99)), None);
}
#[test]
fn test_function_interpretation_lookup() {
let mut domain = SmallVec::new();
domain.push(SortId::new(1));
domain.push(SortId::new(1));
let mut interp = FunctionInterpretation::new(
Spur::try_from_usize(1).expect("valid spur"),
domain,
SortId::new(1),
);
let args = vec![TermId::new(1), TermId::new(2)];
let result = TermId::new(10);
interp.add_entry(args.clone(), result);
assert_eq!(interp.lookup(&args), Some(result));
assert_eq!(interp.lookup(&[TermId::new(99)]), None);
}
#[test]
fn test_function_interpretation_else_value() {
let mut interp = FunctionInterpretation::new(
Spur::try_from_usize(1).expect("valid spur"),
SmallVec::new(),
SortId::new(1),
);
let else_val = TermId::new(42);
interp.else_value = Some(else_val);
assert_eq!(interp.lookup(&[TermId::new(99)]), Some(else_val));
}
#[test]
fn test_function_interpretation_max_occurrence() {
let mut domain = SmallVec::new();
domain.push(SortId::new(1));
let mut interp = FunctionInterpretation::new(
Spur::try_from_usize(1).expect("valid spur"),
domain,
SortId::new(1),
);
let result1 = TermId::new(10);
let result2 = TermId::new(20);
interp.add_entry(vec![TermId::new(1)], result1);
interp.add_entry(vec![TermId::new(2)], result1);
interp.add_entry(vec![TermId::new(3)], result2);
assert_eq!(interp.max_occurrence_result(), Some(result1));
}
#[test]
fn test_projection_function_def() {
let mut proj = ProjectionFunctionDef::new(0, SortId::new(1));
let value1 = TermId::new(1);
let term1 = TermId::new(10);
proj.add_value(value1, term1);
assert_eq!(proj.project(value1), Some(term1));
assert_eq!(proj.values.len(), 1);
}
#[test]
fn test_model_completer_creation() {
let completer = ModelCompleter::new();
assert_eq!(completer.stats.num_completions, 0);
}
#[test]
fn test_macro_solver_creation() {
let solver = MacroSolver::new();
assert_eq!(solver.stats.num_macros_found, 0);
}
#[test]
fn test_model_fixer_creation() {
let fixer = ModelFixer::new();
assert_eq!(fixer.stats.num_fixes, 0);
}
#[test]
fn test_uninterpreted_sort_handler_creation() {
let handler = UninterpretedSortHandler::new();
assert_eq!(handler.max_universe_size, 8);
}
#[test]
fn test_uninterpreted_sort_handler_custom_size() {
let handler = UninterpretedSortHandler::with_max_size(16);
assert_eq!(handler.max_universe_size, 16);
}
#[test]
fn test_arithmetic_projection() {
let proj = ArithmeticProjection::new(true);
assert!(proj.is_int);
}
#[test]
fn test_completion_error_display() {
let err = CompletionError::CompletionFailed("test".to_string());
assert!(format!("{}", err).contains("test"));
}
}