use std::collections::HashMap;
use std::rc::Rc;
use oxiz_core::ast::{TermId, TermManager};
use oxiz_core::sort::SortId;
use oxiz_core::tactic::DepthProbe;
use oxiz_core::tactic::{
Goal, HasArrayProbe, HasBitVectorProbe, HasQuantifierProbe, IsLinearProbe, NodeCountProbe,
Probe, SizeProbe, TacticResult,
};
use oxiz_theories::datatype::{Constructor, DatatypeDecl};
use crate::solver::SolverConfig;
use crate::z3_compat::{Bool, SatResult, Z3Context, Z3Solver};
pub struct Z3Statistics {
pairs: Vec<(&'static str, f64)>,
}
impl Z3Statistics {
fn from_solver_stats(stats: &crate::solver::Statistics) -> Self {
let pairs: Vec<(&'static str, f64)> = vec![
("decisions", stats.decisions as f64),
("propagations", stats.propagations as f64),
("conflicts", stats.conflicts as f64),
("restarts", stats.restarts as f64),
("learned-clauses", stats.learned_clauses as f64),
("theory-propagations", stats.theory_propagations as f64),
("theory-conflicts", stats.theory_conflicts as f64),
];
Self { pairs }
}
#[must_use]
pub fn num_keys(&self) -> usize {
self.pairs.len()
}
#[must_use]
pub fn key(&self, i: usize) -> &str {
self.pairs[i].0
}
#[must_use]
pub fn value(&self, i: usize) -> f64 {
self.pairs[i].1
}
#[must_use]
pub fn get(&self, key: &str) -> Option<f64> {
self.pairs.iter().find(|(k, _)| *k == key).map(|(_, v)| *v)
}
#[must_use]
pub fn to_stat_string(&self) -> String {
let mut s = String::new();
for (k, v) in &self.pairs {
s.push_str(&format!(" {} = {}\n", k, v));
}
s
}
}
impl std::fmt::Display for Z3Statistics {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_stat_string())
}
}
impl Z3Solver {
#[must_use]
pub fn statistics(&self) -> Z3Statistics {
Z3Statistics::from_solver_stats(self.ctx.raw_statistics())
}
}
#[derive(Debug, Clone)]
pub enum ParamVal {
Bool(bool),
UInt(u64),
Double(f64),
Str(String),
}
#[derive(Debug, Clone, Default)]
pub struct Z3Params {
map: HashMap<String, ParamVal>,
}
impl Z3Params {
#[must_use]
pub fn new(_ctx: &Z3Context) -> Self {
Self {
map: HashMap::new(),
}
}
pub fn set_bool(&mut self, key: &str, val: bool) {
self.map.insert(key.to_string(), ParamVal::Bool(val));
}
pub fn set_u32(&mut self, key: &str, val: u64) {
self.map.insert(key.to_string(), ParamVal::UInt(val));
}
pub fn set_double(&mut self, key: &str, val: f64) {
self.map.insert(key.to_string(), ParamVal::Double(val));
}
pub fn set_str(&mut self, key: &str, val: &str) {
self.map
.insert(key.to_string(), ParamVal::Str(val.to_string()));
}
#[must_use]
pub fn as_map(&self) -> &HashMap<String, ParamVal> {
&self.map
}
}
impl Z3Solver {
pub fn set_params(&mut self, params: &Z3Params) {
let mut config: SolverConfig = self.ctx.solver_config().clone();
for (key, val) in ¶ms.map {
match (key.as_str(), val) {
("timeout" | "timeout_ms", ParamVal::UInt(ms)) => {
config.timeout_ms = *ms;
}
("timeout", ParamVal::Double(ms)) => {
config.timeout_ms = *ms as u64;
}
("max-conflicts", ParamVal::UInt(n)) => {
config.max_conflicts = *n;
}
("max-decisions", ParamVal::UInt(n)) => {
config.max_decisions = *n;
}
("proof", ParamVal::Bool(b)) => {
config.proof = *b;
}
_ => {}
}
}
self.ctx.set_solver_config(config);
}
}
pub struct Z3Goal {
inner: Goal,
ctx_tm: Rc<std::cell::RefCell<TermManager>>,
}
impl Z3Goal {
#[must_use]
pub fn new(ctx: &Z3Context) -> Self {
Self {
inner: Goal::empty(),
ctx_tm: ctx.tm.clone(),
}
}
pub fn assert(&mut self, b: &Bool) {
self.inner.add(b.id);
}
#[must_use]
pub fn size(&self) -> usize {
self.inner.len()
}
#[must_use]
pub fn get_formula(&self, i: usize) -> Bool {
Bool {
id: self.inner.assertions[i],
}
}
#[must_use]
pub fn is_decided_sat(&self) -> bool {
if self.inner.is_empty() {
return true;
}
let tm = self.ctx_tm.borrow();
let true_id = tm.mk_true_ro();
self.inner.assertions.iter().all(|&a| a == true_id)
}
#[must_use]
pub fn is_decided_unsat(&self) -> bool {
let tm = self.ctx_tm.borrow();
let false_id = tm.mk_false_ro();
self.inner.assertions.contains(&false_id)
}
#[must_use]
pub fn as_inner(&self) -> &Goal {
&self.inner
}
}
pub struct Z3ApplyResult {
subgoals: Vec<Z3Goal>,
}
impl Z3ApplyResult {
#[must_use]
pub fn num_subgoals(&self) -> usize {
self.subgoals.len()
}
#[must_use]
pub fn get_subgoal(&self, i: usize) -> &Z3Goal {
&self.subgoals[i]
}
}
#[derive(Clone)]
enum TacticKind {
Named(String),
Then(Box<TacticKind>, Box<TacticKind>),
OrElse(Box<TacticKind>, Box<TacticKind>),
Repeat(Box<TacticKind>),
TryFor(Box<TacticKind>, u64),
}
impl TacticKind {
fn apply_to_goal(&self, goal: &Goal) -> TacticResult {
match self {
TacticKind::Named(name) => apply_named_tactic(name.as_str(), goal),
TacticKind::Then(a, b) => {
let first_result = a.apply_to_goal(goal);
match first_result {
TacticResult::SubGoals(sub) => {
let mut combined: Vec<Goal> = Vec::new();
for sg in sub {
match b.apply_to_goal(&sg) {
TacticResult::SubGoals(more) => combined.extend(more),
TacticResult::Solved(r) => {
return TacticResult::Solved(r);
}
TacticResult::NotApplicable => combined.push(sg),
TacticResult::Failed(msg) => {
return TacticResult::Failed(msg);
}
}
}
TacticResult::SubGoals(combined)
}
other => other,
}
}
TacticKind::OrElse(a, b) => {
let r = a.apply_to_goal(goal);
if matches!(r, TacticResult::NotApplicable) {
b.apply_to_goal(goal)
} else {
r
}
}
TacticKind::Repeat(inner) => {
let mut current = goal.clone();
for _ in 0..1000_usize {
match inner.apply_to_goal(¤t) {
TacticResult::Solved(r) => return TacticResult::Solved(r),
TacticResult::SubGoals(sub) if sub.len() == 1 => {
if sub[0].assertions == current.assertions {
break; }
current = sub
.into_iter()
.next()
.expect("sub.len() == 1 guarantees exactly one element");
}
TacticResult::SubGoals(sub) => {
return TacticResult::SubGoals(sub);
}
TacticResult::NotApplicable => break,
TacticResult::Failed(msg) => return TacticResult::Failed(msg),
}
}
TacticResult::SubGoals(vec![current])
}
TacticKind::TryFor(inner, _ms) => {
inner.apply_to_goal(goal)
}
}
}
}
fn tactic_registry() -> &'static oxiz_core::tactic::TacticRegistry {
use oxiz_core::tactic::{TacticRegistry, default_registry};
use std::sync::OnceLock;
static REG: OnceLock<TacticRegistry> = OnceLock::new();
REG.get_or_init(default_registry)
}
fn apply_named_tactic(name: &str, goal: &Goal) -> TacticResult {
let canonical = match name {
"ctx-simplify" => "ctx-solver-simplify",
other => other,
};
match tactic_registry().create(canonical) {
Some(tactic) => tactic.apply(goal).unwrap_or(TacticResult::NotApplicable),
None => {
TacticResult::SubGoals(vec![goal.clone()])
}
}
}
#[derive(Clone)]
pub struct Z3Tactic {
kind: TacticKind,
}
impl Z3Tactic {
#[must_use]
pub fn new(_ctx: &Z3Context, name: &str) -> Self {
Self {
kind: TacticKind::Named(name.to_string()),
}
}
#[must_use]
pub fn apply(&self, ctx: &Z3Context, goal: &Z3Goal) -> Z3ApplyResult {
let raw_result = self.kind.apply_to_goal(goal.as_inner());
let ctx_tm = ctx.tm.clone();
let subgoals = match raw_result {
TacticResult::SubGoals(goals) => goals
.into_iter()
.map(|g| Z3Goal {
inner: g,
ctx_tm: ctx_tm.clone(),
})
.collect(),
TacticResult::Solved(_) | TacticResult::NotApplicable | TacticResult::Failed(_) => {
Vec::new()
}
};
Z3ApplyResult { subgoals }
}
#[must_use]
pub fn then(&self, other: &Z3Tactic) -> Self {
Self {
kind: TacticKind::Then(Box::new(self.kind.clone()), Box::new(other.kind.clone())),
}
}
#[must_use]
pub fn or_else(&self, other: &Z3Tactic) -> Self {
Self {
kind: TacticKind::OrElse(Box::new(self.kind.clone()), Box::new(other.kind.clone())),
}
}
#[must_use]
pub fn repeat(&self) -> Self {
Self {
kind: TacticKind::Repeat(Box::new(self.kind.clone())),
}
}
#[must_use]
pub fn try_for(&self, ms: u64) -> Self {
Self {
kind: TacticKind::TryFor(Box::new(self.kind.clone()), ms),
}
}
}
#[derive(Clone)]
enum ProbeKind {
Size,
NodeCount,
Depth,
HasQuantifier,
IsLinear,
HasBitVector,
HasArray,
Const(f64),
Lt(Box<ProbeKind>, Box<ProbeKind>),
Gt(Box<ProbeKind>, Box<ProbeKind>),
}
impl ProbeKind {
fn evaluate(&self, goal: &Goal, tm: &TermManager) -> f64 {
match self {
ProbeKind::Size => SizeProbe.evaluate(goal, tm),
ProbeKind::NodeCount => NodeCountProbe.evaluate(goal, tm),
ProbeKind::Depth => DepthProbe.evaluate(goal, tm),
ProbeKind::HasQuantifier => HasQuantifierProbe.evaluate(goal, tm),
ProbeKind::IsLinear => IsLinearProbe.evaluate(goal, tm),
ProbeKind::HasBitVector => HasBitVectorProbe.evaluate(goal, tm),
ProbeKind::HasArray => HasArrayProbe.evaluate(goal, tm),
ProbeKind::Const(v) => *v,
ProbeKind::Lt(a, b) => {
if a.evaluate(goal, tm) < b.evaluate(goal, tm) {
1.0
} else {
0.0
}
}
ProbeKind::Gt(a, b) => {
if a.evaluate(goal, tm) > b.evaluate(goal, tm) {
1.0
} else {
0.0
}
}
}
}
}
#[derive(Clone)]
pub struct Z3Probe {
kind: ProbeKind,
}
impl Z3Probe {
#[must_use]
pub fn new(_ctx: &Z3Context, name: &str) -> Self {
let kind = match name {
"size" => ProbeKind::Size,
"num-exprs" | "num-consts" => ProbeKind::NodeCount,
"depth" => ProbeKind::Depth,
"has-quantifiers" | "is-quantified" => ProbeKind::HasQuantifier,
"is-linear" | "is-qflia" => ProbeKind::IsLinear,
"is-qfbv" | "has-bitvector" => ProbeKind::HasBitVector,
"has-array" => ProbeKind::HasArray,
_ => ProbeKind::Const(0.0),
};
Self { kind }
}
#[must_use]
pub fn apply(&self, ctx: &Z3Context, goal: &Z3Goal) -> f64 {
let tm = ctx.tm.borrow();
self.kind.evaluate(goal.as_inner(), &tm)
}
#[must_use]
pub fn lt(self, other: Z3Probe) -> Z3Probe {
Z3Probe {
kind: ProbeKind::Lt(Box::new(self.kind), Box::new(other.kind)),
}
}
#[must_use]
pub fn gt(self, other: Z3Probe) -> Z3Probe {
Z3Probe {
kind: ProbeKind::Gt(Box::new(self.kind), Box::new(other.kind)),
}
}
}
#[derive(Debug, Clone)]
pub struct Z3Field {
pub name: String,
pub sort_name: String,
}
#[derive(Debug, Clone)]
pub struct Z3Constructor {
pub name: String,
pub fields: Vec<Z3Field>,
}
pub struct Z3DatatypeSort {
decl: DatatypeDecl,
sort_id: SortId,
}
impl Z3DatatypeSort {
#[must_use]
pub fn new(ctx: &Z3Context, name: &str, constructors: &[Z3Constructor]) -> Self {
assert!(
!constructors.is_empty(),
"Z3DatatypeSort requires at least one constructor"
);
let mut decl = DatatypeDecl::new(name);
for (tag, z3_con) in constructors.iter().enumerate() {
let mut con = Constructor::new(z3_con.name.clone(), tag as u32);
for field in &z3_con.fields {
con = con.with_field(field.name.clone(), field.sort_name.clone());
}
decl = decl.with_constructor(con);
}
let sort_id = ctx.tm.borrow_mut().sorts.mk_datatype_sort(name);
Self { decl, sort_id }
}
#[must_use]
pub fn num_constructors(&self) -> usize {
self.decl.constructors.len()
}
#[must_use]
pub fn constructor(&self, ctx: &Z3Context, i: usize) -> crate::z3_compat::ext::FuncDecl {
let con = &self.decl.constructors[i];
let domain: Vec<SortId> = con
.fields
.iter()
.map(|f| sort_name_to_id(ctx, &f.sort))
.collect();
crate::z3_compat::ext::FuncDecl::new(ctx, &con.name, &domain, self.sort_id)
}
#[must_use]
pub fn recognizer(&self, ctx: &Z3Context, i: usize) -> crate::z3_compat::ext::FuncDecl {
let con = &self.decl.constructors[i];
let name = format!("is-{}", con.name);
let bool_sort = ctx.bool_sort();
crate::z3_compat::ext::FuncDecl::new(ctx, &name, &[self.sort_id], bool_sort)
}
#[must_use]
pub fn accessor(
&self,
ctx: &Z3Context,
con_i: usize,
field_i: usize,
) -> crate::z3_compat::ext::FuncDecl {
let con = &self.decl.constructors[con_i];
let field = &con.fields[field_i];
let field_sort = sort_name_to_id(ctx, &field.sort);
crate::z3_compat::ext::FuncDecl::new(ctx, &field.name, &[self.sort_id], field_sort)
}
#[must_use]
pub fn sort_id(&self) -> SortId {
self.sort_id
}
#[must_use]
pub fn decl(&self) -> &DatatypeDecl {
&self.decl
}
}
#[must_use]
pub fn mk_constructor(name: &str, fields: &[(&str, &str)]) -> Z3Constructor {
Z3Constructor {
name: name.to_string(),
fields: fields
.iter()
.map(|&(fname, fsort)| Z3Field {
name: fname.to_string(),
sort_name: fsort.to_string(),
})
.collect(),
}
}
fn sort_name_to_id(ctx: &Z3Context, name: &str) -> SortId {
match name {
"Bool" => ctx.bool_sort(),
"Int" => ctx.int_sort(),
"Real" => ctx.real_sort(),
other => ctx.tm.borrow_mut().sorts.mk_datatype_sort(other),
}
}
impl Z3Solver {
pub fn check_assumptions(&mut self, assumptions: &[Bool]) -> SatResult {
let term_ids: Vec<TermId> = assumptions.iter().map(|b| b.id).collect();
self.ctx.check_with_assumptions_raw(&term_ids).into()
}
#[must_use]
pub fn unsat_core(&self) -> Vec<Bool> {
match self.ctx.get_unsat_core_raw() {
None => Vec::new(),
Some(core) => {
let assertions = self.ctx.get_assertions();
core.indices
.iter()
.filter_map(|&idx| assertions.get(idx as usize).copied())
.map(|id| Bool { id })
.collect()
}
}
}
}
pub struct Z3AstVector {
terms: Vec<TermId>,
ctx_tm: Rc<std::cell::RefCell<TermManager>>,
}
impl Z3AstVector {
#[must_use]
pub fn new(ctx: &Z3Context) -> Self {
Self {
terms: Vec::new(),
ctx_tm: ctx.tm.clone(),
}
}
pub fn push(&mut self, term: &Bool) {
self.terms.push(term.id);
}
#[must_use]
pub fn len(&self) -> usize {
self.terms.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.terms.is_empty()
}
#[must_use]
pub fn get(&self, i: usize) -> Bool {
Bool { id: self.terms[i] }
}
pub fn iter(&self) -> impl Iterator<Item = Bool> + '_ {
self.terms.iter().map(|&id| Bool { id })
}
#[must_use]
pub fn any_true(&self) -> bool {
use oxiz_core::ast::TermKind;
let tm = self.ctx_tm.borrow();
self.terms
.iter()
.any(|&id| tm.get(id).is_some_and(|t| matches!(t.kind, TermKind::True)))
}
}
trait TermManagerExt {
fn mk_true_ro(&self) -> TermId;
fn mk_false_ro(&self) -> TermId;
}
impl TermManagerExt for TermManager {
fn mk_true_ro(&self) -> TermId {
use oxiz_core::ast::TermKind;
for idx in 0..8u32 {
let tid = TermId(idx);
if let Some(t) = self.get(tid) {
if matches!(t.kind, TermKind::True) {
return tid;
}
}
}
TermId(0) }
fn mk_false_ro(&self) -> TermId {
use oxiz_core::ast::TermKind;
for idx in 0..8u32 {
let tid = TermId(idx);
if let Some(t) = self.get(tid) {
if matches!(t.kind, TermKind::False) {
return tid;
}
}
}
TermId(1) }
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Z3Value {
pub inner: String,
}
impl Z3Value {
#[must_use]
pub fn from_string(s: String) -> Self {
Self { inner: s }
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.inner
}
}
impl std::fmt::Display for Z3Value {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.inner)
}
}
#[derive(Debug, Clone)]
pub struct Z3FuncEntry {
pub args: Vec<Z3Value>,
pub value: Z3Value,
}
pub struct Z3FuncInterp {
entries: Vec<Z3FuncEntry>,
else_value: Z3Value,
arity: usize,
}
impl Z3FuncInterp {
pub(crate) fn from_raw(raw: &crate::z3_compat::FuncInterpRaw) -> Self {
let (raw_entries, else_str, arity) = raw;
let entries = raw_entries
.iter()
.map(|(arg_strs, val_str)| Z3FuncEntry {
args: arg_strs
.iter()
.map(|s| Z3Value::from_string(s.clone()))
.collect(),
value: Z3Value::from_string(val_str.clone()),
})
.collect();
Self {
entries,
else_value: Z3Value::from_string(else_str.clone()),
arity: *arity,
}
}
#[must_use]
pub fn num_entries(&self) -> usize {
self.entries.len()
}
#[must_use]
pub fn arity(&self) -> usize {
self.arity
}
#[must_use]
pub fn else_value(&self) -> &Z3Value {
&self.else_value
}
#[must_use]
pub fn get_entry(&self, i: usize) -> &Z3FuncEntry {
&self.entries[i]
}
pub fn entries(&self) -> impl Iterator<Item = &Z3FuncEntry> {
self.entries.iter()
}
}
impl crate::z3_compat::Z3Model {
#[must_use]
pub fn get_func_interp(&self, f: &crate::z3_compat::ext::FuncDecl) -> Option<Z3FuncInterp> {
self.func_interp_raw(&f.name).map(Z3FuncInterp::from_raw)
}
}
pub use oxiz_theories::datatype::{
Constructor as DtConstructor, DatatypeDecl as DtDecl, DatatypeSort as DtSort, Field as DtField,
Selector as DtSelector,
};