#![allow(missing_docs)]
#![allow(dead_code)]
#[allow(unused_imports)]
use crate::prelude::*;
use core::fmt;
use oxiz_core::ast::TermId;
use oxiz_core::interner::Spur;
use oxiz_core::sort::SortId;
use smallvec::SmallVec;
pub mod counterexample;
pub mod finite_model;
pub mod heuristics;
pub mod instantiation;
pub mod integration;
pub mod lazy_instantiation;
pub mod macros;
pub mod model_completion;
pub mod patterns;
pub use counterexample::{
CexGenerationResult, CounterExample, CounterExampleGenerator, RefinementStrategy,
};
pub use finite_model::{FiniteModel, FiniteModelFinder, SymmetryBreaker, UniverseSize};
pub use heuristics::{InstantiationHeuristic, MBQIHeuristics, SelectionStrategy, TriggerSelection};
pub use instantiation::{
InstantiationContext, InstantiationEngine, InstantiationPattern, QuantifierInstantiator,
};
pub use integration::{MBQIIntegration, SolverCallback};
pub use lazy_instantiation::{LazyInstantiator, LazyStrategy, MatchingContext};
pub use model_completion::{
MacroSolver, ModelCompleter, ModelFixer, ProjectionFunction, UninterpretedSortHandler,
};
#[derive(Debug, Clone)]
pub struct QuantifiedFormula {
pub term: TermId,
pub bound_vars: SmallVec<[(Spur, SortId); 4]>,
pub body: TermId,
pub is_universal: bool,
pub nesting_depth: u32,
pub instantiation_count: usize,
pub max_instantiations: usize,
pub generation: u32,
pub weight: f64,
pub patterns: Vec<Vec<TermId>>,
pub is_flattened: bool,
pub specialized_body: Option<TermId>,
}
impl QuantifiedFormula {
pub fn new(
term: TermId,
bound_vars: SmallVec<[(Spur, SortId); 4]>,
body: TermId,
is_universal: bool,
) -> Self {
Self {
term,
bound_vars,
body,
is_universal,
nesting_depth: 0,
instantiation_count: 0,
max_instantiations: 1000,
generation: 0,
weight: 1.0,
patterns: Vec::new(),
is_flattened: false,
specialized_body: None,
}
}
pub fn with_params(
term: TermId,
bound_vars: SmallVec<[(Spur, SortId); 4]>,
body: TermId,
is_universal: bool,
max_instantiations: usize,
weight: f64,
) -> Self {
let mut qf = Self::new(term, bound_vars, body, is_universal);
qf.max_instantiations = max_instantiations;
qf.weight = weight;
qf
}
pub fn can_instantiate(&self) -> bool {
self.instantiation_count < self.max_instantiations
}
pub fn num_vars(&self) -> usize {
self.bound_vars.len()
}
pub fn var_name(&self, idx: usize) -> Option<Spur> {
self.bound_vars.get(idx).map(|(name, _)| *name)
}
pub fn var_sort(&self, idx: usize) -> Option<SortId> {
self.bound_vars.get(idx).map(|(_, sort)| *sort)
}
pub fn record_instantiation(&mut self) {
self.instantiation_count += 1;
}
pub fn priority_score(&self) -> f64 {
let inst_factor = 1.0 / (1.0 + self.instantiation_count as f64);
let depth_factor = 1.0 / (1.0 + self.nesting_depth as f64);
self.weight * inst_factor * depth_factor
}
}
#[derive(Debug, Clone)]
pub struct Instantiation {
pub quantifier: TermId,
pub substitution: FxHashMap<Spur, TermId>,
pub result: TermId,
pub generation: u32,
pub reason: InstantiationReason,
pub cost: f64,
}
impl Instantiation {
pub fn new(
quantifier: TermId,
substitution: FxHashMap<Spur, TermId>,
result: TermId,
generation: u32,
) -> Self {
Self {
quantifier,
substitution,
result,
generation,
reason: InstantiationReason::ModelBased,
cost: 1.0,
}
}
pub fn with_reason(
quantifier: TermId,
substitution: FxHashMap<Spur, TermId>,
result: TermId,
generation: u32,
reason: InstantiationReason,
) -> Self {
Self {
quantifier,
substitution,
result,
generation,
reason,
cost: 1.0,
}
}
pub fn binding_key(&self) -> Vec<(Spur, TermId)> {
let mut key: Vec<_> = self.substitution.iter().map(|(&k, &v)| (k, v)).collect();
key.sort_by_key(|(name, _)| *name);
key
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum InstantiationReason {
ModelBased,
EMatching,
Conflict,
Enumerative,
User,
Theory,
}
impl fmt::Display for InstantiationReason {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ModelBased => write!(f, "model-based"),
Self::EMatching => write!(f, "e-matching"),
Self::Conflict => write!(f, "conflict"),
Self::Enumerative => write!(f, "enumerative"),
Self::User => write!(f, "user"),
Self::Theory => write!(f, "theory"),
}
}
}
#[derive(Debug, Clone)]
pub enum MBQIResult {
NoQuantifiers,
Satisfied,
NewInstantiations(Vec<Instantiation>),
Conflict {
quantifier: TermId,
reason: Vec<TermId>,
},
InstantiationLimit,
Unknown,
}
impl MBQIResult {
pub fn is_sat(&self) -> bool {
matches!(self, Self::Satisfied)
}
pub fn is_unsat(&self) -> bool {
matches!(self, Self::Conflict { .. })
}
pub fn has_instantiations(&self) -> bool {
matches!(self, Self::NewInstantiations(_))
}
pub fn num_instantiations(&self) -> usize {
match self {
Self::NewInstantiations(insts) => insts.len(),
_ => 0,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct MBQIStats {
pub num_quantifiers: usize,
pub total_instantiations: usize,
pub max_instantiations: usize,
pub unique_instantiations: usize,
pub num_checks: usize,
pub num_completions: usize,
pub num_counterexamples: usize,
pub num_conflicts: usize,
pub total_time_us: u64,
pub completion_time_us: u64,
pub cex_search_time_us: u64,
}
impl MBQIStats {
pub fn new() -> Self {
Self::default()
}
pub fn reset(&mut self) {
*self = Self::default();
}
pub fn avg_instantiations_per_check(&self) -> f64 {
if self.num_checks == 0 {
0.0
} else {
self.total_instantiations as f64 / self.num_checks as f64
}
}
pub fn avg_time_per_check_us(&self) -> f64 {
if self.num_checks == 0 {
0.0
} else {
self.total_time_us as f64 / self.num_checks as f64
}
}
}
impl fmt::Display for MBQIStats {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "MBQI Statistics:")?;
writeln!(f, " Quantifiers: {}", self.num_quantifiers)?;
writeln!(f, " Total checks: {}", self.num_checks)?;
writeln!(f, " Total instantiations: {}", self.total_instantiations)?;
writeln!(f, " Unique instantiations: {}", self.unique_instantiations)?;
writeln!(
f,
" Avg inst/check: {:.2}",
self.avg_instantiations_per_check()
)?;
writeln!(f, " Counterexamples: {}", self.num_counterexamples)?;
writeln!(f, " Conflicts: {}", self.num_conflicts)?;
writeln!(
f,
" Total time: {:.2}ms",
self.total_time_us as f64 / 1000.0
)?;
writeln!(
f,
" Completion time: {:.2}ms",
self.completion_time_us as f64 / 1000.0
)?;
writeln!(
f,
" CEX search time: {:.2}ms",
self.cex_search_time_us as f64 / 1000.0
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use oxiz_core::interner::Key;
#[test]
fn test_quantified_formula_creation() {
let qf = QuantifiedFormula::new(TermId::new(1), SmallVec::new(), TermId::new(2), true);
assert!(qf.is_universal);
assert_eq!(qf.instantiation_count, 0);
assert!(qf.can_instantiate());
}
#[test]
fn test_quantified_formula_priority() {
let mut qf = QuantifiedFormula::with_params(
TermId::new(1),
SmallVec::new(),
TermId::new(2),
true,
100,
2.0,
);
let initial_priority = qf.priority_score();
qf.record_instantiation();
let after_priority = qf.priority_score();
assert!(after_priority < initial_priority);
}
#[test]
fn test_instantiation_binding_key() {
let mut subst = FxHashMap::default();
subst.insert(
Spur::try_from_usize(1).expect("valid spur"),
TermId::new(10),
);
subst.insert(
Spur::try_from_usize(2).expect("valid spur"),
TermId::new(20),
);
let inst = Instantiation::new(TermId::new(1), subst, TermId::new(3), 0);
let key = inst.binding_key();
assert_eq!(key.len(), 2);
assert!(key[0].0 <= key[1].0);
}
#[test]
fn test_mbqi_result_predicates() {
let sat = MBQIResult::Satisfied;
assert!(sat.is_sat());
assert!(!sat.is_unsat());
assert!(!sat.has_instantiations());
let conflict = MBQIResult::Conflict {
quantifier: TermId::new(1),
reason: vec![],
};
assert!(!conflict.is_sat());
assert!(conflict.is_unsat());
let inst = MBQIResult::NewInstantiations(vec![]);
assert!(inst.has_instantiations());
assert_eq!(inst.num_instantiations(), 0);
}
#[test]
fn test_stats_initialization() {
let stats = MBQIStats::new();
assert_eq!(stats.num_quantifiers, 0);
assert_eq!(stats.total_instantiations, 0);
assert_eq!(stats.avg_instantiations_per_check(), 0.0);
}
#[test]
fn test_stats_averages() {
let mut stats = MBQIStats::new();
stats.num_checks = 10;
stats.total_instantiations = 50;
stats.total_time_us = 1000;
assert_eq!(stats.avg_instantiations_per_check(), 5.0);
assert_eq!(stats.avg_time_per_check_us(), 100.0);
}
#[test]
fn test_instantiation_reason_display() {
assert_eq!(
format!("{}", InstantiationReason::ModelBased),
"model-based"
);
assert_eq!(format!("{}", InstantiationReason::EMatching), "e-matching");
assert_eq!(format!("{}", InstantiationReason::Conflict), "conflict");
}
}