use rz3::ast::{Expr, Type};
use rz3::Rz3Solver;
use rz3::SolverResult;
fn iv(n: &str) -> Expr {
Expr::Var(n.into(), Type::Int)
}
fn rv(n: &str) -> Expr {
Expr::Var(n.into(), Type::Real)
}
fn gt(a: Expr, b: Expr) -> Expr {
Expr::Gt(Box::new(a), Box::new(b))
}
fn ge(a: Expr, b: Expr) -> Expr {
Expr::Ge(Box::new(a), Box::new(b))
}
fn i(n: i64) -> Expr {
Expr::Int(n)
}
fn add(a: Expr, b: Expr) -> Expr {
Expr::Add(vec![a, b])
}
fn sub(a: Expr, b: Expr) -> Expr {
Expr::Sub(vec![a, b])
}
#[test]
fn nonstrict_zero_cycle_is_sat() {
let mut s = Rz3Solver::new();
s.assert(&ge(iv("x"), iv("y")));
s.assert(&ge(iv("y"), iv("z")));
s.assert(&ge(iv("z"), iv("x")));
assert!(
matches!(s.check(), SolverResult::Sat),
"x≥y ∧ y≥z ∧ z≥x must be SAT"
);
}
#[test]
fn strict_path_no_cycle_is_sat() {
let mut s = Rz3Solver::new();
s.assert(>(iv("x"), iv("y")));
s.assert(>(iv("y"), iv("z")));
assert!(
matches!(s.check(), SolverResult::Sat),
"x>y ∧ y>z must be SAT"
);
}
#[test]
fn sum_constraint_must_not_become_edge_sat() {
let mut s = Rz3Solver::new();
s.assert(>(add(iv("x"), iv("y")), i(0)));
s.assert(>(iv("x"), iv("y")));
assert!(
matches!(s.check(), SolverResult::Sat),
"x+y>0 ∧ x>y must be SAT"
);
}
#[test]
fn weighted_cycle_with_slack_is_sat() {
let mut s = Rz3Solver::new();
s.assert(>(sub(rv("x"), rv("y")), i(1)));
s.assert(>(sub(rv("y"), rv("z")), i(1)));
s.assert(>(sub(rv("z"), rv("x")), i(-3)));
assert!(
matches!(s.check(), SolverResult::Sat),
"feasible weighted cycle must be SAT"
);
}
#[test]
fn strict_zero_cycle_is_unsat() {
let mut s = Rz3Solver::new();
s.assert(>(iv("x"), iv("y")));
s.assert(>(iv("y"), iv("z")));
s.assert(>(iv("z"), iv("x")));
assert!(
matches!(s.check(), SolverResult::Unsat),
"x>y ∧ y>z ∧ z>x must be UNSAT"
);
}
#[test]
fn two_var_strict_cycle_is_unsat() {
let mut s = Rz3Solver::new();
s.assert(>(iv("x"), iv("y")));
s.assert(>(iv("y"), iv("x")));
assert!(
matches!(s.check(), SolverResult::Unsat),
"x>y ∧ y>x must be UNSAT"
);
}
#[test]
fn weighted_negative_cycle_is_unsat() {
let mut s = Rz3Solver::new();
s.assert(>(sub(rv("x"), rv("y")), i(3)));
s.assert(>(sub(rv("y"), rv("z")), i(3)));
s.assert(>(sub(rv("z"), rv("x")), i(-5)));
assert!(
matches!(s.check(), SolverResult::Unsat),
"weighted negative cycle must be UNSAT"
);
}
#[test]
fn four_var_cycle_is_unsat() {
let mut s = Rz3Solver::new();
s.assert(>(iv("a"), iv("b")));
s.assert(>(iv("b"), iv("c")));
s.assert(>(iv("c"), iv("d")));
s.assert(>(iv("d"), iv("a")));
assert!(
matches!(s.check(), SolverResult::Unsat),
"4-var strict cycle must be UNSAT"
);
}
#[test]
fn sub_is_real_subtraction_not_addition() {
let mut s = Rz3Solver::new();
s.assert(&Expr::Eq(Box::new(sub(iv("x"), iv("y"))), Box::new(i(2))));
s.assert(&Expr::Eq(Box::new(iv("x")), Box::new(i(5))));
s.assert(&Expr::Eq(Box::new(iv("y")), Box::new(i(3))));
assert!(
matches!(s.check(), SolverResult::Sat),
"x−y==2 ∧ x==5 ∧ y==3 must be SAT"
);
}
#[test]
fn determinism_dl_unsat_stable() {
for _ in 0..30 {
let mut s = Rz3Solver::new();
s.assert(>(iv("x"), iv("y")));
s.assert(>(iv("y"), iv("z")));
s.assert(>(iv("z"), iv("x")));
assert!(matches!(s.check(), SolverResult::Unsat));
}
}