use num_rational::Rational64;
use oxiz_core::ast::TermId;
use oxiz_core::sort::SortId;
use crate::optimization::{OptimizationResult, Optimizer};
use crate::z3_compat::{BV, Bool, Int, Real, SatResult, Z3Context};
macro_rules! build {
($ctx:expr, $method:ident $(, $arg:expr)* ) => {
$ctx.tm.borrow_mut().$method($($arg),*)
};
}
impl Real {
#[must_use]
pub fn gt(ctx: &Z3Context, lhs: &Real, rhs: &Real) -> Bool {
let id = build!(ctx, mk_gt, lhs.id, rhs.id);
Bool { id }
}
#[must_use]
pub fn ge(ctx: &Z3Context, lhs: &Real, rhs: &Real) -> Bool {
let id = build!(ctx, mk_ge, lhs.id, rhs.id);
Bool { id }
}
#[must_use]
pub fn neg(ctx: &Z3Context, arg: &Real) -> Real {
let id = build!(ctx, mk_neg, arg.id);
Real { id }
}
#[must_use]
pub fn div(ctx: &Z3Context, lhs: &Real, rhs: &Real) -> Real {
let id = build!(ctx, mk_div, lhs.id, rhs.id);
Real { id }
}
#[must_use]
pub fn from_i64(ctx: &Z3Context, value: i64) -> Real {
let id = build!(ctx, mk_real, Rational64::new(value, 1));
Real { id }
}
}
#[must_use]
pub fn ite_bool(ctx: &Z3Context, cond: &Bool, then_branch: &Bool, else_branch: &Bool) -> Bool {
let id = build!(ctx, mk_ite, cond.id, then_branch.id, else_branch.id);
Bool { id }
}
#[must_use]
pub fn ite_int(ctx: &Z3Context, cond: &Bool, then_branch: &Int, else_branch: &Int) -> Int {
let id = build!(ctx, mk_ite, cond.id, then_branch.id, else_branch.id);
Int { id }
}
#[must_use]
pub fn ite_real(ctx: &Z3Context, cond: &Bool, then_branch: &Real, else_branch: &Real) -> Real {
let id = build!(ctx, mk_ite, cond.id, then_branch.id, else_branch.id);
Real { id }
}
#[must_use]
pub fn ite_bv(ctx: &Z3Context, cond: &Bool, then_branch: &BV, else_branch: &BV) -> BV {
let width = then_branch.width;
let id = build!(ctx, mk_ite, cond.id, then_branch.id, else_branch.id);
BV { id, width }
}
#[must_use]
pub fn distinct_int(ctx: &Z3Context, args: &[Int]) -> Bool {
let ids = args.iter().map(|x| x.id);
let id = build!(ctx, mk_distinct, ids);
Bool { id }
}
#[must_use]
pub fn distinct_real(ctx: &Z3Context, args: &[Real]) -> Bool {
let ids = args.iter().map(|x| x.id);
let id = build!(ctx, mk_distinct, ids);
Bool { id }
}
#[must_use]
pub fn distinct_bv(ctx: &Z3Context, args: &[BV]) -> Bool {
let ids = args.iter().map(|x| x.id);
let id = build!(ctx, mk_distinct, ids);
Bool { id }
}
#[derive(Debug, Clone)]
pub struct Array {
pub id: TermId,
pub domain: SortId,
pub range: SortId,
}
impl Array {
#[must_use]
pub fn from_id(id: TermId, domain: SortId, range: SortId) -> Self {
Self { id, domain, range }
}
#[must_use]
pub fn new_const(ctx: &Z3Context, name: &str, domain: SortId, range: SortId) -> Self {
let arr_sort = ctx.tm.borrow_mut().sorts.array(domain, range);
let id = build!(ctx, mk_var, name, arr_sort);
Self { id, domain, range }
}
#[must_use]
pub fn select(ctx: &Z3Context, arr: &Array, idx: TermId) -> TermId {
build!(ctx, mk_select, arr.id, idx)
}
#[must_use]
pub fn store(ctx: &Z3Context, arr: &Array, idx: TermId, val: TermId) -> Array {
let id = build!(ctx, mk_store, arr.id, idx, val);
Array {
id,
domain: arr.domain,
range: arr.range,
}
}
#[must_use]
pub fn eq(ctx: &Z3Context, lhs: &Array, rhs: &Array) -> Bool {
let id = build!(ctx, mk_eq, lhs.id, rhs.id);
Bool { id }
}
}
impl From<Array> for TermId {
fn from(a: Array) -> Self {
a.id
}
}
#[derive(Debug, Clone)]
pub struct FuncDecl {
pub name: String,
pub domain: Vec<SortId>,
pub range: SortId,
}
impl FuncDecl {
#[must_use]
pub fn new(_ctx: &Z3Context, name: &str, domain: &[SortId], range: SortId) -> Self {
Self {
name: name.to_string(),
domain: domain.to_vec(),
range,
}
}
#[must_use]
pub fn apply(&self, ctx: &Z3Context, args: &[TermId]) -> TermId {
debug_assert_eq!(
args.len(),
self.domain.len(),
"FuncDecl::apply arity mismatch: declared {}, got {}",
self.domain.len(),
args.len()
);
let range = self.range;
build!(ctx, mk_apply, &self.name, args.iter().copied(), range)
}
}
#[must_use]
pub fn forall_bool<'a>(
ctx: &Z3Context,
vars: impl IntoIterator<Item = (&'a str, SortId)>,
body: &Bool,
) -> Bool {
let id = ctx.tm.borrow_mut().mk_forall(vars, body.id);
Bool { id }
}
#[must_use]
pub fn exists_bool<'a>(
ctx: &Z3Context,
vars: impl IntoIterator<Item = (&'a str, SortId)>,
body: &Bool,
) -> Bool {
let id = ctx.tm.borrow_mut().mk_exists(vars, body.id);
Bool { id }
}
pub struct Z3Optimize {
ctx_tm: std::rc::Rc<std::cell::RefCell<oxiz_core::ast::TermManager>>,
opt: Optimizer,
objectives: Vec<(TermId, ObjectiveDir)>,
lower_bounds: Vec<Option<String>>,
upper_bounds: Vec<Option<String>>,
last_result: SatResult,
}
#[derive(Debug, Clone, Copy)]
enum ObjectiveDir {
Minimize,
Maximize,
}
impl Z3Optimize {
#[must_use]
pub fn new(ctx: &Z3Context) -> Self {
Self {
ctx_tm: ctx.tm.clone(),
opt: Optimizer::new(),
objectives: Vec::new(),
lower_bounds: Vec::new(),
upper_bounds: Vec::new(),
last_result: SatResult::Unknown,
}
}
pub fn assert(&mut self, b: &Bool) {
self.opt.assert(b.id);
}
pub fn minimize(&mut self, t: TermId) -> usize {
let idx = self.objectives.len();
self.opt.minimize(t);
self.objectives.push((t, ObjectiveDir::Minimize));
self.lower_bounds.push(None);
self.upper_bounds.push(None);
idx
}
pub fn maximize(&mut self, t: TermId) -> usize {
let idx = self.objectives.len();
self.opt.maximize(t);
self.objectives.push((t, ObjectiveDir::Maximize));
self.lower_bounds.push(None);
self.upper_bounds.push(None);
idx
}
pub fn check(&mut self) -> SatResult {
let result = self.opt.optimize(&mut self.ctx_tm.borrow_mut());
match &result {
OptimizationResult::Optimal { value, model: _ } => {
let tm = self.ctx_tm.borrow();
let val_str = Self::term_to_string(&tm, *value);
drop(tm);
for idx in 0..self.objectives.len() {
self.lower_bounds[idx] = Some(val_str.clone());
self.upper_bounds[idx] = Some(val_str.clone());
}
self.last_result = SatResult::Sat;
}
OptimizationResult::Unsat => {
self.last_result = SatResult::Unsat;
}
_ => {
self.last_result = SatResult::Unknown;
}
}
self.last_result
}
#[must_use]
pub fn get_lower(&self, idx: usize) -> Option<String> {
self.lower_bounds.get(idx).and_then(|b| b.clone())
}
#[must_use]
pub fn get_upper(&self, idx: usize) -> Option<String> {
self.upper_bounds.get(idx).and_then(|b| b.clone())
}
fn term_to_string(tm: &oxiz_core::ast::TermManager, id: TermId) -> String {
use oxiz_core::ast::TermKind;
if let Some(t) = tm.get(id) {
match &t.kind {
TermKind::IntConst(n) => return n.to_string(),
TermKind::RealConst(r) => return r.to_string(),
TermKind::True => return "true".to_string(),
TermKind::False => return "false".to_string(),
_ => {}
}
}
format!("term#{}", id.0)
}
}
impl Z3Context {
#[must_use]
pub fn array_sort(&self, domain: SortId, range: SortId) -> SortId {
self.tm.borrow_mut().sorts.array(domain, range)
}
}
#[must_use]
pub fn real_numeral(ctx: &Z3Context, num: i64, den: i64) -> Real {
Real::from_frac(ctx, num, den)
}
#[must_use]
pub fn int_numeral(ctx: &Z3Context, value: i64) -> Int {
Int::from_i64(ctx, value)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::z3_compat::{Z3Config, Z3Context};
use num_bigint::BigInt;
fn ctx() -> Z3Context {
Z3Context::new(&Z3Config::new())
}
#[test]
fn test_real_gt_smoke() {
let ctx = ctx();
let a = Real::new_const(&ctx, "a");
let b = Real::new_const(&ctx, "b");
let _gt = Real::gt(&ctx, &a, &b);
}
#[test]
fn test_real_ge_smoke() {
let ctx = ctx();
let a = Real::new_const(&ctx, "a");
let b = Real::new_const(&ctx, "b");
let _ge = Real::ge(&ctx, &a, &b);
}
#[test]
fn test_real_neg_smoke() {
let ctx = ctx();
let a = Real::new_const(&ctx, "a");
let _neg = Real::neg(&ctx, &a);
}
#[test]
fn test_real_div_smoke() {
let ctx = ctx();
let a = Real::new_const(&ctx, "a");
let b = Real::new_const(&ctx, "b");
let _div = Real::div(&ctx, &a, &b);
}
#[test]
fn test_real_from_i64_smoke() {
let ctx = ctx();
let _r = Real::from_i64(&ctx, 42);
}
#[test]
fn test_ite_bool_smoke() {
let ctx = ctx();
let c = Bool::new_const(&ctx, "c");
let t = Bool::from_bool(&ctx, true);
let e = Bool::from_bool(&ctx, false);
let _ite = ite_bool(&ctx, &c, &t, &e);
}
#[test]
fn test_ite_int_smoke() {
let ctx = ctx();
let c = Bool::new_const(&ctx, "c");
let t = Int::from_i64(&ctx, 1);
let e = Int::from_i64(&ctx, 0);
let _ite = ite_int(&ctx, &c, &t, &e);
}
#[test]
fn test_ite_real_smoke() {
let ctx = ctx();
let c = Bool::new_const(&ctx, "c");
let t = Real::from_i64(&ctx, 1);
let e = Real::from_i64(&ctx, 0);
let _ite = ite_real(&ctx, &c, &t, &e);
}
#[test]
fn test_ite_bv_width() {
let ctx = ctx();
let c = Bool::new_const(&ctx, "c");
let t = BV::from_u64(&ctx, 1, 32);
let e = BV::from_u64(&ctx, 0, 32);
let ite = ite_bv(&ctx, &c, &t, &e);
assert_eq!(ite.width, 32);
}
#[test]
fn test_distinct_int_smoke() {
let ctx = ctx();
let x = Int::from_i64(&ctx, 1);
let y = Int::from_i64(&ctx, 2);
let z = Int::from_i64(&ctx, 3);
let _d = distinct_int(&ctx, &[x, y, z]);
}
#[test]
fn test_distinct_real_smoke() {
let ctx = ctx();
let a = Real::from_i64(&ctx, 1);
let b = Real::from_i64(&ctx, 2);
let _d = distinct_real(&ctx, &[a, b]);
}
#[test]
fn test_distinct_bv_smoke() {
let ctx = ctx();
let a = BV::from_u64(&ctx, 0, 8);
let b = BV::from_u64(&ctx, 1, 8);
let _d = distinct_bv(&ctx, &[a, b]);
}
#[test]
fn test_array_new_const_smoke() {
let ctx = ctx();
let dom = ctx.int_sort();
let rng = ctx.int_sort();
let _a = Array::new_const(&ctx, "arr", dom, rng);
}
#[test]
fn test_array_select_store_terms_constructed() {
let ctx = ctx();
let dom = ctx.int_sort();
let rng = ctx.int_sort();
let arr = Array::new_const(&ctx, "arr", dom, rng);
let idx = Int::from_i64(&ctx, 0);
let val = Int::from_i64(&ctx, 42);
let arr2 = Array::store(&ctx, &arr, idx.id, val.id);
let _selected = Array::select(&ctx, &arr2, idx.id);
}
#[test]
fn test_array_eq_smoke() {
let ctx = ctx();
let dom = ctx.int_sort();
let rng = ctx.int_sort();
let a = Array::new_const(&ctx, "a", dom, rng);
let b = Array::new_const(&ctx, "b", dom, rng);
let _eq = Array::eq(&ctx, &a, &b);
}
#[test]
fn test_array_sort_helper() {
let ctx = ctx();
let dom = ctx.int_sort();
let rng = ctx.bool_sort();
let _sort = ctx.array_sort(dom, rng);
}
#[test]
fn test_func_decl_apply_smoke() {
let ctx = ctx();
let int_sort = ctx.int_sort();
let f = FuncDecl::new(&ctx, "f", &[int_sort], int_sort);
let arg = Int::new_const(&ctx, "x");
let _result = f.apply(&ctx, &[arg.id]);
}
#[test]
fn test_func_decl_two_args() {
let ctx = ctx();
let int_sort = ctx.int_sort();
let bool_sort = ctx.bool_sort();
let g = FuncDecl::new(&ctx, "g", &[int_sort, int_sort], bool_sort);
let x = Int::new_const(&ctx, "x");
let y = Int::new_const(&ctx, "y");
let _r = g.apply(&ctx, &[x.id, y.id]);
}
#[test]
fn test_forall_bool_smoke() {
let ctx = ctx();
let int_sort = ctx.int_sort();
let x_var = ctx.tm.borrow_mut().mk_var("x", int_sort);
let zero = ctx.tm.borrow_mut().mk_int(BigInt::from(0));
let body_id = ctx.tm.borrow_mut().mk_ge(x_var, zero);
let body = Bool { id: body_id };
let _q = forall_bool(&ctx, [("x", int_sort)], &body);
}
#[test]
fn test_exists_bool_smoke() {
let ctx = ctx();
let int_sort = ctx.int_sort();
let x_var = ctx.tm.borrow_mut().mk_var("x", int_sort);
let zero = ctx.tm.borrow_mut().mk_int(BigInt::from(0));
let body_id = ctx.tm.borrow_mut().mk_ge(x_var, zero);
let body = Bool { id: body_id };
let _q = exists_bool(&ctx, [("x", int_sort)], &body);
}
#[test]
fn test_optimize_sat_no_objectives() {
let cfg = Z3Config::new();
let ctx = Z3Context::new(&cfg);
let mut opt = Z3Optimize::new(&ctx);
let t = Bool::from_bool(&ctx, true);
opt.assert(&t);
let result = opt.check();
assert!(
result == SatResult::Sat || result == SatResult::Unknown,
"Expected Sat or Unknown, got {:?}",
result
);
}
#[test]
fn test_optimize_minimize_term_constructed() {
let cfg = Z3Config::new();
let ctx = Z3Context::new(&cfg);
let mut opt = Z3Optimize::new(&ctx);
let x = Int::new_const(&ctx, "x");
let zero = Int::from_i64(&ctx, 0);
let ge = Int::ge(&ctx, &x, &zero);
opt.assert(&ge);
let _idx = opt.minimize(x.id);
let _result = opt.check();
}
#[test]
fn test_optimize_get_lower_before_check_is_none() {
let cfg = Z3Config::new();
let ctx = Z3Context::new(&cfg);
let mut opt = Z3Optimize::new(&ctx);
let x = Int::new_const(&ctx, "x");
let _idx = opt.minimize(x.id);
assert!(opt.get_lower(0).is_none());
assert!(opt.get_upper(0).is_none());
}
}