3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-07-15 17:06:39 +00:00
z3/src/test/polysat.cpp
2023-02-17 15:32:43 +01:00

2075 lines
71 KiB
C++

#include "math/polysat/log.h"
#include "math/polysat/solver.h"
#include "math/polysat/variable_elimination.h"
#include "ast/ast.h"
#include "parsers/smt2/smt2parser.h"
#include "util/util.h"
#include <vector>
#include <signal.h>
// TODO: collect stats on how often each inference rule is used, so we can see which ones are useful or if any are useless/untested
namespace {
using namespace dd;
template <typename... Args>
std::string concat(Args... args) {
std::stringstream s;
int dummy[sizeof...(Args)] = {
// use dummy initializer list to sequence stream writes, cf. https://en.cppreference.com/w/cpp/language/parameter_pack
(s << args, 0)...
};
(void)dummy;
return s.str();
}
void permute_args(unsigned k, pdd& a, pdd& b, pdd& c) {
SASSERT(k < 6);
unsigned i = k % 3;
unsigned j = k % 2;
if (i == 1)
std::swap(a, b);
else if (i == 2)
std::swap(a, c);
if (j == 1)
std::swap(b, c);
}
void permute_args(unsigned n, pdd& a, pdd& b, pdd& c, pdd& d) {
SASSERT(n < 24);
switch (n % 4) {
case 1:
std::swap(a, b);
break;
case 2:
std::swap(a, c);
break;
case 3:
std::swap(a, d);
break;
default:
break;
}
switch (n % 3) {
case 1:
std::swap(b, c);
break;
case 2:
std::swap(b, d);
break;
default:
break;
}
switch (n % 2) {
case 1:
std::swap(c, d);
break;
default:
break;
}
}
}
namespace polysat {
enum class test_result {
undefined,
ok,
wrong_answer,
wrong_model,
resource_out, // solver hit the resource limit
error,
};
template <typename T>
T* with_default(T* value, T* default_value) {
return value ? value : default_value;
}
struct test_record {
std::string m_name;
unsigned m_index = 0; ///< m_index-th check_sat() call in this unit test.
lbool m_answer = l_undef; ///< what the solver returned
lbool m_expected = l_undef; ///< the answer we expect (l_undef if unspecified)
test_result m_result = test_result::undefined;
std::string m_error_message;
bool m_finished = false;
using clock_t = std::chrono::steady_clock;
clock_t::time_point m_start;
clock_t::time_point m_end;
void set_error(char const* msg) {
m_result = test_result::error;
m_error_message = msg;
m_finished = true;
}
std::ostream& display(std::ostream& out, unsigned name_width = 0) const;
};
std::ostream& operator<<(std::ostream& out, test_record const& r) { return r.display(out); }
class test_record_manager {
scoped_ptr_vector<test_record> m_records;
std::string m_name;
unsigned m_index = 0;
public:
void begin_batch(std::string name);
void end_batch();
test_record* new_record();
test_record* active_or_new_record();
void display(std::ostream& out) const;
};
void test_record_manager::begin_batch(std::string name) {
end_batch();
if (m_name != name) {
m_name = name;
m_index = 0;
}
}
void test_record_manager::end_batch() {
// Kick out unfinished records (they have the wrong name)
while (!m_records.empty() && !m_records.back()->m_finished)
m_records.pop_back();
}
test_record* test_record_manager::new_record() {
auto* rec = alloc(test_record);
rec->m_name = m_name;
rec->m_index = ++m_index;
m_records.push_back(rec);
return rec;
}
test_record* test_record_manager::active_or_new_record() {
if (m_records.empty() || m_records.back()->m_finished)
return new_record();
else
return m_records.back();
}
std::ostream& test_record::display(std::ostream& out, unsigned name_width) const {
if (!m_finished)
out << "UNFINISHED ";
out << m_name;
if (m_index != 1)
out << " (" << m_index << ") ";
else
out << " ";
for (size_t i = m_name.length(); i < name_width; ++i)
out << ' ';
std::chrono::duration<double> d = m_end - m_start;
if (d.count() >= 0.15) {
out << std::fixed << std::setprecision(1);
out << std::setw(4) << d.count() << "s ";
}
else
out << " ";
switch (m_answer) {
case l_undef: out << " "; break;
case l_true: out << "SAT "; break;
case l_false: out << "UNSAT "; break;
}
switch (m_result) {
case test_result::undefined: out << "???"; break;
case test_result::ok: out << "ok"; break;
case test_result::wrong_answer: out << color_red() << "wrong answer, expected " << m_expected << color_reset(); break;
case test_result::wrong_model: out << color_red() << "wrong model" << color_reset(); break;
case test_result::resource_out: out << color_yellow() << "resource out" << color_reset(); break;
case test_result::error: out << color_red() << "error: " << m_error_message << color_reset(); break;
}
return out;
}
void test_record_manager::display(std::ostream& out) const {
out << "\n\n\nTest Results:\n\n";
size_t max_name_len = 0;
for (test_record const* r : m_records) {
if (!r->m_finished)
continue;
max_name_len = std::max(max_name_len, r->m_name.length());
}
size_t n_total = m_records.size();
size_t n_sat = 0;
size_t n_unsat = 0;
size_t n_wrong = 0;
size_t n_error = 0;
for (test_record const* r : m_records) {
if (!r->m_finished)
continue;
r->display(out, static_cast<unsigned>(max_name_len));
out << std::endl;
if (r->m_result == test_result::ok && r->m_answer == l_true)
n_sat++;
if (r->m_result == test_result::ok && r->m_answer == l_false)
n_unsat++;
if (r->m_result == test_result::wrong_answer || r->m_result == test_result::wrong_model)
n_wrong++;
if (r->m_result == test_result::error)
n_error++;
}
out << "\n" << n_total << " tests, " << (n_sat + n_unsat) << " ok (" << n_sat << " sat, " << n_unsat << " unsat)";
if (n_wrong)
out << ", " << color_red() << n_wrong << " wrong!" << color_reset();
if (n_error)
out << ", " << color_red() << n_error << " error" << color_reset();
out << "\n" << std::endl;
}
test_record_manager test_records;
bool collect_test_records = true;
unsigned test_max_conflicts = 10;
// test resolve, factoring routines
// auxiliary
struct solver_scope {
reslimit lim;
};
class scoped_solver : public solver_scope, public solver {
std::string m_name;
lbool m_last_result = l_undef;
test_record* m_last_finished = nullptr;
public:
scoped_solver(std::string name): solver(lim), m_name(name) {
if (collect_test_records)
std::cout << m_name << std::flush;
else {
std::cout << std::string(78, '#') << "\n\n";
std::cout << "START: " << m_name << "\n";
}
set_max_conflicts(test_max_conflicts);
test_records.begin_batch(name);
}
~scoped_solver() {
test_records.end_batch();
if (collect_test_records)
std::cout << "\n";
}
void set_max_conflicts(unsigned c) {
params_ref p;
p.set_uint("max_conflicts", c);
updt_params(p);
}
void check() {
if (collect_test_records)
std::cout << " ..." << std::flush;
auto* rec = test_records.active_or_new_record();
m_last_finished = nullptr;
SASSERT(rec->m_answer == l_undef);
SASSERT(rec->m_expected == l_undef);
SASSERT(rec->m_result == test_result::undefined);
SASSERT(rec->m_error_message == "");
SASSERT(!rec->m_finished);
{
rec->m_start = test_record::clock_t::now();
on_scope_exit end_timer([rec]() {
rec->m_end = test_record::clock_t::now();
});
m_last_result = check_sat();
}
rec->m_finished = true;
m_last_finished = rec;
if (collect_test_records)
std::cout << " " << m_last_result << std::flush;
else
std::cout << "DONE: " << m_name << ": " << m_last_result << "\n";
statistics st;
collect_statistics(st);
if (!collect_test_records)
std::cout << st << "\n";
rec->m_answer = m_last_result;
rec->m_result = (m_last_result == l_undef) ? test_result::resource_out : test_result::ok;
}
void expect_unsat() {
auto* rec = m_last_finished;
SASSERT(rec);
SASSERT_EQ(rec->m_expected, l_undef);
SASSERT_EQ(rec->m_answer, m_last_result);
rec->m_expected = l_false;
if (rec->m_result == test_result::error)
return;
if (m_last_result != l_false && m_last_result != l_undef) {
rec->m_result = test_result::wrong_answer;
LOG_H1("FAIL: " << m_name << ": expected UNSAT, got " << m_last_result << "!");
if (!collect_test_records)
VERIFY(false);
}
}
void expect_sat(std::vector<std::pair<dd::pdd, unsigned>> const& expected_assignment = {}) {
auto* rec = m_last_finished;
SASSERT(rec);
SASSERT_EQ(rec->m_expected, l_undef);
SASSERT_EQ(rec->m_answer, m_last_result);
rec->m_expected = l_true;
if (rec->m_result == test_result::error)
return;
if (m_last_result == l_true) {
for (auto const& p : expected_assignment) {
auto const& v_pdd = p.first;
auto const expected_value = p.second;
SASSERT(v_pdd.is_var());
auto const v = v_pdd.var();
if (get_value(v) != expected_value) {
rec->m_result = test_result::wrong_model;
LOG_H1("FAIL: " << m_name << ": expected assignment v" << v << " := " << expected_value << ", got value " << get_value(v) << "!");
if (!collect_test_records)
VERIFY(false);
}
}
}
else if (m_last_result == l_false) {
rec->m_result = test_result::wrong_answer;
LOG_H1("FAIL: " << m_name << ": expected SAT, got " << m_last_result << "!");
if (!collect_test_records)
VERIFY(false);
}
}
};
std::string run_filter;
template <typename Test, typename... Args>
void run(char const* name, Test tst, Args... args)
{
if (!run_filter.empty()) {
std::string sname = name;
if (sname.find(run_filter) == std::string::npos)
return;
}
try {
tst(args...);
}
catch(z3_exception const& e) {
test_records.active_or_new_record()->set_error(with_default(e.msg(), "unknown z3_exception"));
if (!collect_test_records)
throw;
}
catch(std::exception const& e) {
test_records.active_or_new_record()->set_error(with_default(e.what(), "unknown std::exception"));
if (!collect_test_records)
throw;
}
catch(...) {
test_records.active_or_new_record()->set_error("unknown throwable");
if (!collect_test_records)
throw;
}
}
#define RUN(tst) run(#tst, []() { tst; })
class test_polysat {
public:
/**
* Testing the solver's internal state.
*/
/// Creates two separate conflicts (from narrowing) before solving loop is started.
static void test_add_conflicts() {
scoped_solver s(__func__);
auto a = s.var(s.add_var(3));
auto b = s.var(s.add_var(3));
s.add_eq(a + 1);
s.add_eq(a + 2);
s.add_eq(b + 1);
s.add_eq(b + 2);
s.check();
s.expect_unsat();
}
/// Has constraints which must be inserted into other watchlist to discover UNSAT
static void test_wlist() {
scoped_solver s(__func__);
auto a = s.var(s.add_var(3));
auto b = s.var(s.add_var(3));
auto c = s.var(s.add_var(3));
auto d = s.var(s.add_var(3));
s.add_eq(d + c + b + a + 1);
s.add_eq(d + c + b + a);
s.add_eq(d + c + b);
s.add_eq(d + c);
s.add_eq(d);
s.check();
s.expect_unsat();
}
/// Has a constraint in cjust[a] where a does not occur.
static void test_cjust() {
scoped_solver s(__func__);
auto a = s.var(s.add_var(3));
auto b = s.var(s.add_var(3));
auto c = s.var(s.add_var(3));
// 1. Decide a = 0.
s.add_eq(a*a + b + 7); // 2. Propagate b = 1
s.add_eq(b*b + c*c*c*(b+7) + c + 5); // 3. Propagate c = 2
s.add_eq(b*b + c*c); // 4. Conflict
// Resolution fails because second constraint has c*c*c
// => cjust[a] += b*b + c*c
s.check();
s.expect_unsat();
}
/**
* most basic linear equation solving.
* they should be solvable.
* they also illustrate some limitations of basic solver even if it solves them.
* Example
* the value to a + 1 = 0 is fixed at 3, there should be no search.
*/
static void test_l1() {
scoped_solver s(__func__);
auto a = s.var(s.add_var(2));
s.add_eq(a + 1);
s.check();
s.expect_sat({{a, 3}});
}
static void test_l2() {
scoped_solver s(__func__);
auto a = s.var(s.add_var(2));
auto b = s.var(s.add_var(2));
s.add_eq(2*a + b + 1);
s.add_eq(2*b + a);
s.check();
s.expect_sat({{a, 2}, {b, 3}});
}
static void test_l3() {
scoped_solver s(__func__);
auto a = s.var(s.add_var(2));
auto b = s.var(s.add_var(2));
s.add_eq(3*b + a + 2);
s.check();
s.expect_sat();
}
static void test_l4() {
scoped_solver s(__func__);
auto a = s.var(s.add_var(3));
s.add_eq(4*a + 2); // always false due to parity
s.check();
s.expect_unsat();
}
static void test_l4b() {
scoped_solver s(__func__);
auto x = s.var(s.add_var(32));
auto y = s.var(s.add_var(32));
s.add_eq(2*x + 4*y + 1); // always false due to parity
s.check();
s.expect_unsat();
}
static void test_l5() {
scoped_solver s(__func__);
auto a = s.var(s.add_var(3));
auto b = s.var(s.add_var(3));
s.add_diseq(b);
s.add_eq(a + 2*b + 4);
s.add_eq(a + 4*b + 4);
s.check();
s.expect_sat({{a, 4}, {b, 4}});
}
static void test_l6() {
scoped_solver s(__func__);
auto x = s.var(s.add_var(6));
s.add_ule(29*x + 3, 29*x + 1);
s.check();
s.expect_sat();
}
/**
* This one is unsat because a*a*(a*a - 1)
* is 0 for all values of a.
*/
static void test_p1() {
scoped_solver s(__func__);
auto a = s.var(s.add_var(2));
auto p = a*a*(a*a - 1) + 1;
s.add_eq(p);
s.check();
s.expect_unsat();
}
/**
* has solutions a = 2 and a = 3
*/
static void test_p2() {
scoped_solver s(__func__);
auto a = s.var(s.add_var(2));
auto p = a*(a-1) + 2;
s.add_eq(p);
s.check();
s.expect_sat();
}
/**
* unsat
* - learns 3*x + 1 == 0 by polynomial resolution
* - this forces x == 5, which means the first constraint is unsatisfiable by parity.
*/
static void test_p3() {
scoped_solver s(__func__);
auto x = s.var(s.add_var(4));
auto y = s.var(s.add_var(4));
auto z = s.var(s.add_var(4));
s.add_eq(x*x*y + 3*y + 7);
s.add_eq(2*y + z + 8);
s.add_eq(3*x + 4*y*z + 2*z*z + 1);
s.check();
s.expect_unsat();
}
// Unique solution: u = 5
static void test_ineq_basic1(unsigned bw = 32) {
scoped_solver s(__func__);
auto u = s.var(s.add_var(bw));
s.add_ule(u, 5);
s.add_ule(5, u);
s.check();
s.expect_sat({{u, 5}});
}
// Unsatisfiable
static void test_ineq_basic2(unsigned bw = 32) {
scoped_solver s(__func__);
auto u = s.var(s.add_var(bw));
s.add_ult(u, 5);
s.add_ule(5, u);
s.check();
s.expect_unsat();
}
// Solutions with u = v = w
static void test_ineq_basic3(unsigned bw = 32) {
scoped_solver s(__func__);
auto u = s.var(s.add_var(bw));
auto v = s.var(s.add_var(bw));
auto w = s.var(s.add_var(bw));
s.add_ule(u, v);
s.add_ule(v, w);
s.add_ule(w, u);
s.check();
s.expect_sat();
}
// Unsatisfiable
static void test_ineq_basic4(unsigned bw = 32) {
scoped_solver s(__func__);
auto u = s.var(s.add_var(bw));
auto v = s.var(s.add_var(bw));
auto w = s.var(s.add_var(bw));
s.add_ule(u, v);
s.add_ult(v, w);
s.add_ule(w, u);
s.check();
s.expect_unsat();
}
// Satisfiable
// Without forbidden intervals, we just try values for u until it works
static void test_ineq_basic5() {
scoped_solver s(__func__);
auto u = s.var(s.add_var(4));
auto v = s.var(s.add_var(4));
s.add_ule(12, u + v);
s.add_ule(v, 2);
s.check();
s.expect_sat(); // e.g., u = 12, v = 0
}
// Like test_ineq_basic5 but the other forbidden interval will be the longest
static void test_ineq_basic6() {
scoped_solver s(__func__);
auto u = s.var(s.add_var(4));
auto v = s.var(s.add_var(4));
s.add_ule(14, u + v);
s.add_ule(v, 2);
s.check();
s.expect_sat();
}
// -43 \/ 3 \/ 4
// -43: v3 + -1 != 0
// 3: v3 == 0
// 4: v3 <= v5
static void test_clause_simplify1() {
scoped_solver s(__func__);
simplify_clause simp(s);
clause_builder cb(s);
auto u = s.var(s.add_var(4));
auto v = s.var(s.add_var(4));
cb.insert(s.eq(u));
cb.insert(~s.eq(u - 1));
cb.insert(s.ule(u, v));
auto cl = cb.build();
simp.apply(*cl);
VERIFY_EQ(cl->size(), 2);
}
// p <= q
// p == q (should be removed)
static void test_clause_simplify2() {
scoped_solver s(__func__);
simplify_clause simp(s);
clause_builder cb(s);
auto u = s.var(s.add_var(32));
auto v = s.var(s.add_var(32));
auto w = s.var(s.add_var(32));
auto p = 2*u*v;
auto q = 7*v*w;
cb.insert(s.ule(p, q));
cb.insert(s.eq(p, q));
auto cl = cb.build();
simp.apply(*cl);
VERIFY_EQ(cl->size(), 1);
VERIFY_EQ((*cl)[0], s.ule(p, q).blit());
}
// p < q
// p == q
// should be merged into p <= q
static void test_clause_simplify3() {
scoped_solver s(__func__);
simplify_clause simp(s);
clause_builder cb(s);
auto u = s.var(s.add_var(32));
auto v = s.var(s.add_var(32));
auto w = s.var(s.add_var(32));
auto p = 2*u*v;
auto q = 7*v*w;
cb.insert(s.ult(p, q));
cb.insert(s.eq(p, q));
auto cl = cb.build();
simp.apply(*cl);
VERIFY_EQ(cl->size(), 1);
VERIFY_EQ((*cl)[0], s.ule(p, q).blit());
}
// 2^1*x + 2^1 == 0 and 2^2*x == 0
static void test_fi_quickcheck1() {
scoped_solver s(__func__);
auto x = s.var(s.add_var(3));
signed_constraint c1 = s.eq(x * 2 + 2, 0);
signed_constraint c2 = s.eq(4 * x, 0);
s.add_clause(c1, false);
s.add_clause(c2, false);
s.m_viable.intersect(x.var(), c1);
s.m_viable.intersect(x.var(), c2);
svector<lbool> fixed;
vector<ptr_vector<viable::entry>> justifications;
VERIFY(!s.m_viable.collect_bit_information(x.var(), false, fixed, justifications));
}
// parity(x) >= 3 and bit_1(x)
static void test_fi_quickcheck2() {
scoped_solver s(__func__);
auto x = s.var(s.add_var(4));
signed_constraint c1 = s.parity_at_least(x, 3);
signed_constraint c2 = s.bit(x, 1);
s.add_clause(c1, false);
s.add_clause(c2, false);
s.m_viable.intersect(x.var(), c1);
s.m_viable.intersect(x.var(), c2);
svector<lbool> fixed;
vector<ptr_vector<viable::entry>> justifications;
VERIFY(!s.m_viable.collect_bit_information(x.var(), false, fixed, justifications));
}
// 8 * x + 3 == 0 or 8 * x + 5 == 0 is unsat
static void test_parity1() {
scoped_solver s(__func__);
auto x = s.var(s.add_var(8));
auto y = s.var(s.add_var(8));
auto z = s.var(s.add_var(8));
s.add_clause({s.eq(x * y + z), s.eq(x * y + 5)}, false);
s.add_eq(y, 8);
s.add_eq(z, 3);
s.check();
s.expect_unsat();
}
// 8 * u * x + 3 == 0 or 8 * u * x + 5 == 0 is unsat
static void test_parity1b() {
scoped_solver s(__func__);
auto u = s.var(s.add_var(8));
auto x = s.var(s.add_var(8));
auto y = s.var(s.add_var(8));
auto z = s.var(s.add_var(8));
s.add_clause({s.eq(u * x * y + z), s.eq(u * x * y + 5)}, false);
s.add_eq(y, 8);
s.add_eq(z, 3);
s.check();
s.expect_unsat();
}
// 8 * x + 2 == 0 or 8 * x + 4 == 0 is unsat
static void test_parity2() {
scoped_solver s(__func__);
auto x = s.var(s.add_var(8));
auto y = s.var(s.add_var(8));
s.add_clause({ s.eq(x * y + 2), s.eq(x * y + 4) }, false);
s.add_eq(y, 8);
s.check();
s.expect_unsat();
}
// (x * y + 4 == 0 or x * y + 2 == 0) & 16 divides y is unsat
static void test_parity3() {
scoped_solver s(__func__);
auto x = s.var(s.add_var(8));
auto y = s.var(s.add_var(8));
s.add_clause({ s.eq(x * y + 2), s.eq(x * y + 4) }, false);
s.add_eq(16 * y);
s.check();
s.expect_unsat();
}
// x*y + 2 == 0
// parity(y) >= 4 || parity(y) >= 8
static void test_parity4(unsigned bw = 8) {
scoped_solver s(concat(__func__, " bw=", bw));
pdd x = s.var(s.add_var(bw));
pdd y = s.var(s.add_var(bw));
s.add_eq(x * y + 2);
s.add_clause({ s.parity_at_least(y, 4), s.parity_at_least(y, 8) }, false);
s.check();
s.expect_unsat();
}
/**
* Check unsat of:
* u = v*q + r
* r < u
* v*q > u
*/
static void test_ineq1() {
scoped_solver s(__func__);
auto u = s.var(s.add_var(5));
auto v = s.var(s.add_var(5));
auto q = s.var(s.add_var(5));
auto r = s.var(s.add_var(5));
s.add_eq(u - (v*q) - r);
s.add_ult(r, u);
s.add_ult(u, v*q);
s.check();
s.expect_unsat();
}
/**
* Check unsat of:
* n*q1 = a - b 3*1 == 3 - 0
* n*q2 + r2 = c*a - c*b 3*0 + 1 == 11*3 - 11*0 (= 33 = 1 mod 32)
* n > r2 > 0 3 > 1 > 0
* It is actually satisfiable, e.g.: { {n, 3}, {q1, 1}, {a, 3}, {b, 0}, {c, 11}, {q2, 0}, {r2, 1} } (not a unique solution)
*/
static void test_ineq2() {
scoped_solver s(__func__);
auto n = s.var(s.add_var(5));
auto q1 = s.var(s.add_var(5));
auto a = s.var(s.add_var(5));
auto b = s.var(s.add_var(5));
auto c = s.var(s.add_var(5));
auto q2 = s.var(s.add_var(5));
auto r2 = s.var(s.add_var(5));
s.add_eq(n*q1 - a + b);
s.add_eq(n*q2 + r2 - c*a + c*b);
s.add_ult(r2, n);
s.add_diseq(r2);
s.check();
s.expect_sat();
}
/**
* Monotonicity example from certora
*
* We do overflow checks by doubling the base bitwidth here.
*/
static void test_monot(unsigned base_bw = 5) {
scoped_solver s(concat(__func__, "(", base_bw, ")"));
auto max_int_const = rational::power_of_two(base_bw) - 1;
unsigned bw = 2 * base_bw;
auto max_int = s.var(s.add_var(bw));
s.add_eq(max_int + (-max_int_const));
auto tb1 = s.var(s.add_var(bw));
s.add_ule(tb1, max_int);
auto tb2 = s.var(s.add_var(bw));
s.add_ule(tb2, max_int);
auto a = s.var(s.add_var(bw));
s.add_ule(a, max_int);
auto v = s.var(s.add_var(bw));
s.add_ule(v, max_int);
auto base1 = s.var(s.add_var(bw));
s.add_ule(base1, max_int);
auto base2 = s.var(s.add_var(bw));
s.add_ule(base2, max_int);
auto elastic1 = s.var(s.add_var(bw));
s.add_ule(elastic1, max_int);
auto elastic2 = s.var(s.add_var(bw));
s.add_ule(elastic2, max_int);
auto err = s.var(s.add_var(bw));
s.add_ule(err, max_int);
auto rem1 = s.var(s.add_var(bw));
auto quot2 = s.var(s.add_var(bw));
s.add_ule(quot2, max_int);
auto rem2 = s.var(s.add_var(bw));
auto rem3 = s.var(s.add_var(bw));
auto quot4 = s.var(s.add_var(bw));
s.add_ule(quot4, max_int);
auto rem4 = s.var(s.add_var(bw));
s.add_diseq(elastic1);
// division: tb1 = (v * base1) / elastic1;
s.add_eq((tb1 * elastic1) + rem1 - (v * base1));
s.add_ult(rem1, elastic1);
s.add_ule((tb1 * elastic1), max_int);
// division: quot2 = (a * base1) / elastic1
s.add_eq((quot2 * elastic1) + rem2 - (a * base1));
s.add_ult(rem2, elastic1);
s.add_ule((quot2 * elastic1), max_int);
s.add_eq(base1 + quot2 - base2);
s.add_eq(elastic1 + a - elastic2);
// division: tb2 = ((v * base2) / elastic2);
s.add_eq((tb2 * elastic2) + rem3 - (v * base2));
s.add_ult(rem3, elastic2);
s.add_ule((tb2 * elastic2), max_int);
// division: quot4 = v / elastic2;
s.add_eq((quot4 * elastic2) + rem4 - v);
s.add_ult(rem4, elastic2);
s.add_ule((quot4 * elastic2), max_int);
s.add_eq(quot4 + 1 - err);
s.push();
s.add_ult(tb1, tb2);
s.check();
s.expect_unsat();
s.pop();
s.push();
s.add_ult(tb2 + err, tb1);
s.check();
s.expect_unsat();
s.pop();
}
/*
* Mul-then-div in fixed point arithmetic is (roughly) neutral.
*
* I.e. we prove "(((a * b) / sf) * sf) / b" to be equal to a, up to some error margin.
*
* sf is the scaling factor (we could leave this unconstrained, but non-zero, to make the benchmark a bit harder)
* em is the error margin
*
* We do overflow checks by doubling the base bitwidth here.
*/
static void test_fixed_point_arith_mul_div_inverse(unsigned base_bw = 5) {
scoped_solver s(__func__);
auto max_int_const = rational::power_of_two(base_bw) - 1;
auto bw = 2 * base_bw;
auto max_int = s.var(s.add_var(bw));
s.add_eq(max_int - max_int_const);
// "input" variables
auto a = s.var(s.add_var(bw));
s.add_ule(a, max_int);
auto b = s.var(s.add_var(bw));
s.add_ule(b, max_int);
s.add_ult(0, b); // b > 0
// scaling factor (setting it, somewhat arbitrarily, to max_int/3)
auto sf_val = div(max_int_const, rational(3));
auto sf = s.var(s.add_var(bw));
s.add_eq(sf - sf_val);
// (a * b) / sf = quot1 <=> quot1 * sf + rem1 - (a * b) = 0
auto quot1 = s.var(s.add_var(bw));
auto rem1 = s.var(s.add_var(bw));
s.add_eq((quot1 * sf) + rem1 - (a * b));
s.add_ult(rem1, sf);
s.add_ule(quot1 * sf, max_int);
// (((a * b) / sf) * sf) / b <=> quot2 * b + rem2 - (((a * b) / sf) * sf) = 0
auto quot2 = s.var(s.add_var(bw));
auto rem2 = s.var(s.add_var(bw));
s.add_eq((quot2 * b) + rem2 - (quot1 * sf));
s.add_ult(rem2, b);
s.add_ule(quot2 * b, max_int);
// sf / b = quot3 <=> quot3 * b + rem3 = sf
auto quot3 = s.var(s.add_var(bw));
auto rem3 = s.var(s.add_var(bw));
s.add_eq((quot3 * b) + rem3 - sf);
s.add_ult(rem3, b);
s.add_ule(quot3 * b, max_int);
// em = sf / b + 1
auto em = s.var(s.add_var(bw));
s.add_eq(quot3 + 1 - em);
// we prove quot3 <= a and quot3 + em >= a
s.push();
s.add_ult(a, quot3);
s.check();
// s.expect_unsat();
s.pop();
s.push();
s.add_ult(quot3 + em, a);
s.check();
// s.expect_unsat();
s.pop();
}
/*
* Div-then-mul in fixed point arithmetic is (roughly) neutral.
*
* I.e. we prove "(b * ((a * sf) / b)) / sf" to be equal to a, up to some error margin.
*
* sf is the scaling factor (we could leave this unconstrained, but non-zero, to make the benchmark a bit harder)
* em is the error margin
*
* We do overflow checks by doubling the base bitwidth here.
*/
static void test_fixed_point_arith_div_mul_inverse(unsigned base_bw = 5) {
scoped_solver s(__func__);
auto max_int_const = rational::power_of_two(base_bw) - 1;
auto bw = 2 * base_bw;
auto max_int = s.var(s.add_var(bw));
s.add_eq(max_int - max_int_const);
// "input" variables
auto a = s.var(s.add_var(bw));
s.add_ule(a, max_int);
auto b = s.var(s.add_var(bw));
s.add_ule(b, max_int);
s.add_ult(0, b); // b > 0
// scaling factor (setting it, somewhat arbitrarily, to max_int/3)
auto sf = s.var(s.add_var(bw));
s.add_eq(sf - floor(max_int_const/3));
// (a * sf) / b = quot1 <=> quot1 * b + rem1 - (a * sf) = 0
auto quot1 = s.var(s.add_var(bw));
auto rem1 = s.var(s.add_var(bw));
s.add_eq((quot1 * b) + rem1 - (a * sf));
s.add_ult(rem1, b);
s.add_ule(quot1 * b, max_int);
// (b * ((a * sf) / b)) / sf = quot2 <=> quot2 * sf + rem2 - (b * ((a * sf) / b)) = 0
auto quot2 = s.var(s.add_var(bw));
auto rem2 = s.var(s.add_var(bw));
s.add_eq((quot2 * sf) + rem2 - (b * quot1));
s.add_ult(rem2, sf);
s.add_ule(quot2 * sf, max_int);
// b / sf = quot3 <=> quot3 * sf + rem3 - b = 0
auto quot3 = s.var(s.add_var(bw));
auto rem3 = s.var(s.add_var(bw));
s.add_eq((quot3 * sf) + rem3 - b);
s.add_ult(rem3, sf);
s.add_ule(quot3 * sf, max_int);
// em = b / sf + 1
auto em = s.var(s.add_var(bw));
s.add_eq(quot3 + 1 - em);
// we prove quot3 <= a and quot3 + em >= a
s.push();
s.add_ult(quot3 + em, a);
s.check();
// s.expect_unsat();
s.pop();
s.push();
s.add_ult(a, quot3);
s.check();
// s.expect_unsat();
s.pop();
}
/** Monotonicity under bounds,
* puzzle extracted from https://github.com/NikolajBjorner/polysat/blob/main/puzzles/bv.smt2
*
* x, y, z \in [0..2^64[
* x, y, z < 2^32
* y <= x
* x*z < 2^32
* y*z >= 2^32
*/
static void test_monot_bounds(unsigned base_bw = 32) {
scoped_solver s(std::string{__func__} + "(" + std::to_string(base_bw) + ")");
unsigned bw = 2 * base_bw;
auto y = s.var(s.add_var(bw));
auto z = s.var(s.add_var(bw));
auto x = s.var(s.add_var(bw));
auto bound = rational::power_of_two(base_bw);
#if 1
s.add_ult(x, bound);
s.add_ult(y, bound);
// s.add_ult(z, bound); // not required
#else
s.add_ule(x, bound - 1);
s.add_ule(y, bound - 1);
// s.add_ule(z, bound - 1); // not required
#endif
unsigned a = 13;
s.add_ule(z, y);
s.add_ult(x*y, a);
s.add_ule(a, x*z);
s.check();
s.expect_unsat();
}
/** Monotonicity under bounds, simplified even more.
*
* x, y, z \in [0..2^64[
* x, y, z < 2^32
* z <= y
* y*x < z*x
*/
static void test_monot_bounds_simple(unsigned base_bw = 32) {
scoped_solver s(__func__);
unsigned bw = 2 * base_bw;
/*
auto z = s.var(s.add_var(bw));
auto x = s.var(s.add_var(bw));
auto y = s.var(s.add_var(bw));
*/
auto y = s.var(s.add_var(bw));
auto x = s.var(s.add_var(bw));
auto z = s.var(s.add_var(bw));
auto bound = rational::power_of_two(base_bw);
s.add_ult(x, bound);
s.add_ult(y, bound);
s.add_ult(z, bound);
s.add_ule(z, y);
s.add_ult(y*x, z*x);
s.check();
s.expect_unsat();
}
/*
* Transcribed from https://github.com/NikolajBjorner/polysat/blob/main/puzzles/bv.smt2
*
* We do overflow checks by doubling the base bitwidth here.
*/
static void test_monot_bounds_full(unsigned base_bw = 5) {
scoped_solver s(__func__);
auto const max_int_const = rational::power_of_two(base_bw) - 1;
auto const bw = 2 * base_bw;
auto const max_int = s.var(s.add_var(bw));
s.add_eq(max_int - max_int_const);
auto const first = s.var(s.add_var(bw));
s.add_ule(first, max_int);
auto const second = s.var(s.add_var(bw));
s.add_ule(second, max_int);
auto const idx = s.var(s.add_var(bw));
s.add_ule(idx, max_int);
auto const q = s.var(s.add_var(bw));
s.add_ule(q, max_int);
auto const r = s.var(s.add_var(bw));
s.add_ule(r, max_int);
// q = max_int / idx <=> q * idx + r - max_int = 0
s.add_eq((q * idx) + r - max_int);
s.add_ult(r, idx);
s.add_ule(q * idx, max_int);
/* last assertion:
(not
(=> (bvugt second first)
(=>
(=> (not (= idx #x00000000))
(bvule (bvsub second first) q))
(bvumul_noovfl (bvsub second first) idx))))
transforming negated boolean skeleton:
(not (=> a (=> (or b c) d))) <=> (and a (not d) (or b c))
*/
// (bvugt second first)
s.add_ult(first, second);
// (not (bvumul_noovfl (bvsub second first) idx))
s.add_ult(max_int, (second - first) * idx);
// s.add_ule((second - first) * idx, max_int);
// resolving disjunction via push/pop
// first disjunct: (= idx #x00000000)
s.push();
s.add_eq(idx);
s.check();
s.expect_unsat();
s.pop();
// second disjunct: (bvule (bvsub second first) q)
s.push();
s.add_ule(second - first, q);
s.check();
s.expect_unsat();
s.pop();
}
static void test_var_minimize(unsigned bw = 32) {
scoped_solver s(__func__);
auto x = s.var(s.add_var(bw));
auto y = s.var(s.add_var(bw));
auto z = s.var(s.add_var(bw));
s.add_eq(x);
s.add_eq(4 * y + 8 * z + x + 2); // should only depend on assignment to x
s.check();
s.expect_unsat();
}
static void expect_lemma_cnt(conflict& cfl, unsigned cnt) {
auto lemmas = cfl.lemmas();
if (lemmas.size() == cnt)
return;
LOG_H1("FAIL: Expected " << cnt << " learned lemmas; got " << lemmas.size() << "!");
SASSERT(false);
if (!collect_test_records)
VERIFY(false);
}
static void expect_lemma(solver& s, conflict& cfl, signed_constraint c1) {
LOG_H1("Looking for constraint: " << c1);
auto lemmas = cfl.lemmas();
for (auto& lemma : lemmas) {
for (unsigned i = 0; i < lemma->size(); i++) {
if (s.lit2cnstr(lemma->operator[](i)) == c1)
return;
LOG_H1("Found different constraint " << s.lit2cnstr(lemma->operator[](i)));
}
}
LOG_H1("FAIL: Lemma " << c1 << " not deduced!");
SASSERT(false);
if (!collect_test_records)
VERIFY(false);
}
static void test_elim1(unsigned bw = 32) {
scoped_solver s(__func__);
free_variable_elimination elim(s);
auto x = s.var(s.add_var(bw));
auto y = s.var(s.add_var(bw));
auto c1 = s.eq(7 * x, 3);
auto c2 = s.ule(x - y, 5);
conflict cfl(s);
s.assign_eh(c1, dependency(0));
cfl.insert(c1);
s.assign_eh(c2, dependency(0));
cfl.insert(c2);
elim.find_lemma(cfl);
rational res;
rational(7).mult_inverse(bw, res);
expect_lemma_cnt(cfl, 1);
expect_lemma(s, cfl, s.ule(s.sz2pdd(bw).mk_val(res * 3) - y, 5));
}
static void test_elim2(unsigned bw = 32) {
scoped_solver s(__func__);
free_variable_elimination elim(s);
auto x = s.var(s.add_var(bw));
auto y = s.var(s.add_var(bw));
auto c1 = s.eq(x * x, x * 3);
auto c2 = s.ule(x + y, 5);
conflict cfl(s);
s.assign_eh(c1, dependency(0));
cfl.insert(c1);
s.assign_eh(c2, dependency(0));
cfl.insert(c2);
elim.find_lemma(cfl);
expect_lemma_cnt(cfl, 0); // Non linear; should be skipped
}
static void test_elim3(unsigned bw = 32) {
scoped_solver s(__func__);
free_variable_elimination elim(s);
auto x = s.var(s.add_var(bw));
auto y = s.var(s.add_var(bw));
auto c1 = s.eq(7 * x, 3);
auto c2 = s.ule(x * x + y, 5);
conflict cfl(s);
s.assign_eh(c1, dependency(0));
cfl.insert(c1);
s.assign_eh(c2, dependency(0));
cfl.insert(c2);
elim.find_lemma(cfl);
expect_lemma_cnt(cfl, 0); // also not linear; should be skipped
}
static void test_elim4(unsigned bw = 32) {
scoped_solver s(__func__);
free_variable_elimination elim(s);
auto x = s.var(s.add_var(bw));
auto y = s.var(s.add_var(bw));
auto c1 = s.eq(7 * x, 3);
auto c2 = s.ule(y * x, 5 + x + y);
conflict cfl(s);
s.assign_eh(c1, dependency(0));
cfl.insert(c1);
s.assign_eh(c2, dependency(0));
cfl.insert(c2);
elim.find_lemma(cfl);
expect_lemma_cnt(cfl, 1);
expect_lemma(s, cfl, s.ule(5 * y, 10 + y));
}
static void test_elim5(unsigned bw = 32) {
scoped_solver s(__func__);
free_variable_elimination elim(s);
auto x = s.var(s.add_var(bw));
auto y = s.var(s.add_var(bw));
auto z = s.var(s.add_var(bw));
auto c1 = s.eq(x * 7 + x * y, 3);
auto c2 = s.ule(y * x * z, 2);
conflict cfl(s);
s.assign_eh(c1, dependency(0));
cfl.insert(c1);
s.assign_eh(c2, dependency(0));
cfl.insert(c2);
elim.find_lemma(cfl);
expect_lemma_cnt(cfl, 1); // Eliminating "x" fails because there is no assignment for "y"; eliminating "y" works
expect_lemma(s, cfl, s.ule(3 * z - 7 * x * z, 2));
s.assign_core(y.var(), rational(2), justification::propagation(0));
conflict cfl2(s);
cfl2.insert(c1);
cfl2.insert_vars(c1);
cfl2.insert(c2);
cfl2.insert_vars(c2);
elim.find_lemma(cfl2);
expect_lemma_cnt(cfl2, 2); // Now it uses the assignment
expect_lemma(s, cfl2, s.ule(3 * z - 7 * x * z, 2));
expect_lemma(s, cfl2, s.ule(6 * z, 2));
}
static void test_elim6(unsigned bw = 32) {
scoped_solver s(__func__);
free_variable_elimination elim(s);
auto x = s.var(s.add_var(bw));
auto y = s.var(s.add_var(bw));
auto z = s.var(s.add_var(bw));
auto c1 = s.eq(2 * x, z);
auto c2 = s.ule(4 * x, y);
conflict cfl(s);
s.assign_eh(c1, dependency(0));
cfl.insert(c1);
s.assign_eh(c2, dependency(0));
cfl.insert(c2);
elim.find_lemma(cfl);
expect_lemma_cnt(cfl, 1); // We have to multiply by 2 so this is an over-approximation (or we would increase bit-width by 1)
expect_lemma(s, cfl, s.ule(2 * z, y));
auto c3 = s.eq(4 * x, z);
auto c4 = s.ule(2 * x, y);
conflict cfl2(s);
s.assign_eh(c3, dependency(0));
cfl2.insert(c3);
s.assign_eh(c4, dependency(0));
cfl2.insert(c4);
elim.find_lemma(cfl2);
expect_lemma_cnt(cfl2, 0); // This does not work because of polarity
}
static void test_elim7(unsigned bw = 32) {
scoped_solver s(__func__);
free_variable_elimination elim(s);
auto x = s.var(s.add_var(bw));
auto y = s.var(s.add_var(bw));
auto z = s.var(s.add_var(bw));
auto c1 = s.eq(x * y, 3);
auto c2 = s.ule(z * x, 2);
conflict cfl(s);
s.assign_eh(c1, dependency(0));
cfl.insert(c1);
s.assign_eh(c2, dependency(0));
cfl.insert(c2);
elim.find_lemma(cfl);
expect_lemma_cnt(cfl, 1); // Should introduce polarity constraints
// TODO: Check if this lemma is really correct
}
/**
* x*x <= z
* (x+1)*(x+1) <= z
* y == x+1
* ¬(y*y <= z)
*
* The original version had signed comparisons but that doesn't matter for the UNSAT result.
* UNSAT can be seen easily by substituting the equality.
*
* Possible ways to solve:
* - Integrate AC congruence closure
* See: Deepak Kapur. A Modular Associative Commutative (AC) Congruence Closure Algorithm, FSCD 2021. https://doi.org/10.4230/LIPIcs.FSCD.2021.15
* - Propagate equalities as substitutions
* x=t /\ p(x) ==> p(t)
* Ackermann-like reduction
* (index, watch lists over boolean literals)
* - Augment explain:
* conflict: y=x+1 /\ y^2 > z
* explain could then derive (x+1)^2 > z
*/
static void test_subst(unsigned bw = 32) {
scoped_solver s(__func__);
auto x = s.var(s.add_var(bw));
auto y = s.var(s.add_var(bw));
auto z = s.var(s.add_var(bw));
s.add_ule(x * x, z); // optional
s.add_ule((x + 1) * (x + 1), z);
s.add_eq(x + 1 - y);
s.add_ult(z, y*y);
s.check();
s.expect_unsat();
}
static void test_subst_signed(unsigned bw = 32) {
scoped_solver s(__func__);
auto x = s.var(s.add_var(bw));
auto y = s.var(s.add_var(bw));
auto z = s.var(s.add_var(bw));
s.add_sle(x * x, z); // optional
s.add_sle((x + 1) * (x + 1), z);
s.add_eq(x + 1 - y);
s.add_slt(z, y*y);
s.check();
s.expect_unsat();
}
// xy < xz and !Omega(x*y) => y < z
static void test_ineq_axiom1(unsigned bw = 32, std::optional<unsigned> perm = std::nullopt) {
if (perm) {
scoped_solver s(concat(__func__, " bw=", bw, " perm=", *perm));
auto const bound = rational::power_of_two(bw/2);
auto x = s.var(s.add_var(bw));
auto y = s.var(s.add_var(bw));
auto z = s.var(s.add_var(bw));
permute_args(*perm, x, y, z);
s.add_ult(x * y, x * z);
s.add_ule(z, y);
s.add_ult(x, bound);
s.add_ult(y, bound);
s.check();
s.expect_unsat();
}
else {
for (unsigned i = 0; i < 6; ++i) {
test_ineq_axiom1(bw, i);
}
}
}
static void test_ineq_non_axiom1(unsigned bw, unsigned i) {
auto const bound = rational::power_of_two(bw - 1);
scoped_solver s(concat(__func__, " bw=", bw, " perm=", i));
auto x = s.var(s.add_var(bw));
auto y = s.var(s.add_var(bw));
auto z = s.var(s.add_var(bw));
permute_args(i, x, y, z);
s.add_ult(x * y, x * z);
s.add_ule(z, y);
//s.add_ult(x, bound);
//s.add_ult(y, bound);
s.check();
s.expect_sat();
}
static void test_ineq_non_axiom1(unsigned bw = 32) {
for (unsigned i = 0; i < 6; ++i)
test_ineq_non_axiom1(32, i);
}
// xy <= xz & !Omega(x*y) => y <= z or x = 0
static void test_ineq_axiom2(unsigned bw = 32) {
auto const bound = rational::power_of_two(bw/2);
for (unsigned i = 0; i < 6; ++i) {
scoped_solver s(concat(__func__, " bw=", bw, " perm=", i));
auto x = s.var(s.add_var(bw));
auto y = s.var(s.add_var(bw));
auto z = s.var(s.add_var(bw));
permute_args(i, x, y, z);
s.add_ult(x * y, x * z);
s.add_ult(z, y);
s.add_ult(x, bound);
s.add_ult(y, bound);
s.add_diseq(x);
s.check();
s.expect_unsat();
}
}
// xy < b & a <= x & !Omega(x*y) => a*y < b
static void test_ineq_axiom3(unsigned bw, unsigned i) {
auto const bound = rational::power_of_two(bw/2);
scoped_solver s(concat(__func__, " bw=", bw, " perm=", i));
auto x = s.var(s.add_var(bw));
auto y = s.var(s.add_var(bw));
auto a = s.var(s.add_var(bw));
auto b = s.var(s.add_var(bw));
permute_args(i, x, y, a, b);
s.add_ult(x * y, b);
s.add_ule(a, x);
s.add_ult(x, bound);
s.add_ult(y, bound);
s.add_ule(b, a * y);
s.check();
s.expect_unsat();
}
static void test_ineq_axiom3(unsigned bw = 32) {
for (unsigned i = 0; i < 24; ++i)
test_ineq_axiom3(bw, i);
}
// x*y <= b & a <= x & !Omega(x*y) => a*y <= b
static void test_ineq_axiom4(unsigned bw = 32, unsigned i = 0) {
auto const bound = rational::power_of_two(bw/2);
for (; i < 24; ++i) {
scoped_solver s(concat(__func__, " bw=", bw, " perm=", i));
auto x = s.var(s.add_var(bw));
auto y = s.var(s.add_var(bw));
auto a = s.var(s.add_var(bw));
auto b = s.var(s.add_var(bw));
permute_args(i, x, y, a, b);
s.add_ule(x * y, b);
s.add_ule(a, x);
s.add_ult(x, bound);
s.add_ult(y, bound);
s.add_ult(b, a * y);
s.check();
s.expect_unsat();
}
}
// x*y <= b & a <= x & !Omega(x*y) => a*y <= b
static void test_ineq_non_axiom4(unsigned bw, unsigned i) {
auto const bound = rational::power_of_two(bw - 1);
scoped_solver s(concat(__func__, " bw=", bw, " perm=", i));
LOG("permutation: " << i);
auto x = s.var(s.add_var(bw));
auto y = s.var(s.add_var(bw));
auto a = s.var(s.add_var(bw));
auto b = s.var(s.add_var(bw));
permute_args(i, x, y, a, b);
s.add_ule(x * y, b);
s.add_ule(a, x);
s.add_ult(x, bound);
s.add_ult(y, bound);
s.add_ult(b, a * y);
s.check();
s.expect_sat();
}
static void test_ineq_non_axiom4(unsigned bw = 32) {
for (unsigned i = 0; i < 24; ++i)
test_ineq_non_axiom4(bw, i);
}
// a < xy & x <= b & !Omega(b*y) => a < b*y
static void test_ineq_axiom5(unsigned bw, unsigned i) {
auto const bound = rational::power_of_two(bw/2);
scoped_solver s(concat(__func__, " bw=", bw, " perm=", i));
auto x = s.var(s.add_var(bw));
auto y = s.var(s.add_var(bw));
auto a = s.var(s.add_var(bw));
auto b = s.var(s.add_var(bw));
permute_args(i, x, y, a, b);
s.add_ult(a, x * y);
s.add_ule(x, b);
s.add_ult(b, bound);
s.add_ult(y, bound);
s.add_ule(b * y, a);
s.check();
s.expect_unsat();
}
static void test_ineq_axiom5(unsigned bw = 32) {
for (unsigned i = 0; i < 24; ++i)
test_ineq_axiom5(bw, i);
}
// a <= xy & x <= b & !Omega(b*y) => a <= b*y
static void test_ineq_axiom6(unsigned bw, unsigned i) {
auto const bound = rational::power_of_two(bw/2);
scoped_solver s(concat(__func__, " bw=", bw, " perm=", i));
auto x = s.var(s.add_var(bw));
auto y = s.var(s.add_var(bw));
auto a = s.var(s.add_var(bw));
auto b = s.var(s.add_var(bw));
permute_args(i, x, y, a, b);
s.add_ule(a, x * y);
s.add_ule(x, b);
s.add_ult(b, bound);
s.add_ult(y, bound);
s.add_ult(b * y, a);
s.check();
s.expect_unsat();
}
static void test_ineq_axiom6(unsigned bw = 32) {
for (unsigned i = 0; i < 24; ++i)
test_ineq_axiom6(bw, i);
}
static void test_quot_rem_incomplete() {
unsigned bw = 4;
scoped_solver s(__func__);
auto quot = s.var(s.add_var(bw));
auto rem = s.var(s.add_var(bw));
auto a = s.value(rational(2), bw);
auto b = s.value(rational(5), bw);
// Incomplete axiomatization of quotient/remainder.
// quot_rem(2, 5) should have single solution (0, 2),
// but with the usual axioms we also get (3, 3).
s.add_eq(b * quot + rem - a);
s.add_umul_noovfl(b, quot);
s.add_ult(rem, b);
// To force a solution that's different from the correct one.
s.add_diseq(quot - 0);
s.check();
s.expect_sat({{quot, 3}, {rem, 3}});
}
static void test_quot_rem_fixed() {
unsigned bw = 4;
scoped_solver s(__func__);
auto a = s.value(rational(2), bw);
auto b = s.value(rational(5), bw);
auto [quot, rem] = s.quot_rem(a, b);
s.add_diseq(quot - 0); // to force a solution that's different from the correct one
s.check();
s.expect_unsat();
}
static void test_quot_rem(unsigned bw = 32) {
scoped_solver s(__func__);
auto a = s.var(s.add_var(bw));
auto quot = s.var(s.add_var(bw));
auto rem = s.var(s.add_var(bw));
auto x = a * 123;
auto y = 123;
// quot = udiv(a*123, 123)
s.add_eq(x, quot * y + rem); // 123*a = q*123 + r
s.add_diseq(a, quot); // a != quot
s.add_umul_noovfl(quot, y); // 123*quot < 2^K
s.add_ult(rem, x); // r < 123*a ???
s.check();
s.expect_sat(); // dubious
}
static void test_quot_rem2(unsigned bw = 32) {
scoped_solver s(__func__);
auto q = s.var(s.add_var(bw));
auto r = s.var(s.add_var(bw));
auto idx = s.var(s.add_var(bw));
auto second = s.var(s.add_var(bw));
auto first = s.var(s.add_var(bw));
s.add_eq(q*idx + r, UINT_MAX);
s.add_ult(r, idx);
s.add_umul_noovfl(q, idx);
s.add_ult(first, second);
s.add_diseq(idx, 0);
s.add_ule(second - first, q);
s.add_umul_noovfl(second - first, idx);
s.check();
}
static void test_band1(unsigned bw = 32) {
scoped_solver s(concat(__func__, " bw=", bw));
auto p = s.var(s.add_var(bw));
auto q = s.var(s.add_var(bw));
s.add_diseq(p - s.band(p, q));
s.add_diseq(p - q);
s.check();
s.expect_sat();
}
static void test_band2(unsigned bw = 32) {
scoped_solver s(concat(__func__, " bw=", bw));
auto p = s.var(s.add_var(bw));
auto q = s.var(s.add_var(bw));
s.add_ult(p, s.band(p, q));
s.check();
s.expect_unsat();
}
static void test_band3(unsigned bw = 32) {
scoped_solver s(concat(__func__, " bw=", bw));
auto p = s.var(s.add_var(bw));
auto q = s.var(s.add_var(bw));
s.add_ult(q, s.band(p, q));
s.check();
s.expect_unsat();
}
static void test_band4(unsigned bw = 32) {
scoped_solver s(concat(__func__, " bw=", bw));
auto p = s.var(s.add_var(bw));
auto q = s.var(s.add_var(bw));
s.add_ule(p, s.band(p, q));
s.check();
s.expect_sat();
}
/**
* p <= p & q
* p != p & q
* is unsatisfiable.
*/
static void test_band5(unsigned bw = 32) {
scoped_solver s(concat(__func__, " bw=", bw));
auto p = s.var(s.add_var(bw));
auto q = s.var(s.add_var(bw));
s.add_ule(p, s.band(p, q));
s.add_diseq(p, s.band(p, q));
// Immediate solution with clause: p <= r /\ r <= p ==> p = r
// s.add_clause({ ~s.ule(p, s.band(p, q)), ~s.ule(s.band(p, q), p), s.eq(p, s.band(p, q)) }, true);
s.check();
s.expect_unsat();
}
/** Like test_band5, but represent disequality as clause of less-than constraints */
static void test_band5_clause(unsigned bw = 32) {
scoped_solver s(concat(__func__, " bw=", bw));
auto p = s.var(s.add_var(bw));
auto q = s.var(s.add_var(bw));
s.add_ule(p, s.band(p, q));
// Rewrite p - q > 0 --> p < q || q < p
s.add_clause({ s.ult(p, s.band(p, q)), s.ult(s.band(p, q), p) }, false);
s.check();
s.expect_unsat();
}
static void test_band6(unsigned bw = 32) {
scoped_solver s(concat(__func__, " bw=", bw));
auto a = s.var(s.add_var(bw));
auto x = s.var(s.add_var(bw));
auto y = s.var(s.add_var(bw));
signed_constraint and1 = s.eq(a, s.band(x, x.manager().mk_val(rational::power_of_two((bw + 1) / 2) - 1)));
signed_constraint and2 = s.eq(a, s.band(x, x.manager().mk_val((rational::power_of_two((bw + 1) / 2) - 1) * rational::power_of_two(bw / 2))));
s.add_clause(and1, false);
s.add_clause(and2, false);
s.add_clause(~s.eq(a, 0), false);
s.check();
s.expect_unsat();
}
static void test_band6_complex_term(unsigned bw = 32) {
scoped_solver s(concat(__func__, " bw=", bw));
auto a = s.var(s.add_var(bw));
auto x = s.var(s.add_var(bw));
auto y = s.var(s.add_var(bw));
signed_constraint and1 = s.eq(a, s.band(x * y, x.manager().mk_val(rational::power_of_two((bw + 1) / 2) - 1)));
signed_constraint and2 = s.eq(a, s.band(x * y, x.manager().mk_val((rational::power_of_two((bw + 1) / 2) - 1) * rational::power_of_two(bw / 2))));
s.add_clause(and1, false);
s.add_clause(and2, false);
s.add_clause(~s.eq(a, 0), false);
s.check();
s.expect_unsat();
}
static void test_band6_eq_order(unsigned bw = 32) {
scoped_solver s(concat(__func__, " bw=", bw));
auto a = s.var(s.add_var(bw));
auto x = s.var(s.add_var(bw));
auto y = s.var(s.add_var(bw));
signed_constraint and1 = s.eq(a, s.band(x, x.manager().mk_val(rational::power_of_two((bw + 1) / 2) - 1)));
signed_constraint and2 = s.eq(a, s.band(x, y));
s.add_clause(and1, false);
s.add_clause(and2, false);
s.add_clause(s.eq(x.manager().mk_val((rational::power_of_two((bw + 1) / 2) - 1) * rational::power_of_two(bw / 2)), y), false);
s.add_clause(~s.eq(a, 0), false);
s.check();
s.expect_unsat();
}
static void test_fi_zero() {
scoped_solver s(__func__);
auto a = s.var(s.add_var(256));
auto b = s.var(s.add_var(256));
auto c = s.var(s.add_var(256));
s.add_eq(a, 0);
s.add_eq(c, 0); // add c to prevent simplification by leading coefficient
s.add_eq(4*a - 123456789*b + c);
s.check();
s.expect_sat({{a, 0}, {b, 0}});
}
static void test_fi_nonzero() {
scoped_solver s(__func__);
auto a = s.var(s.add_var(5));
auto b = s.var(s.add_var(5));
s.add_ult(b*b*b, 7*a + 6);
s.check();
}
static void test_fi_nonmax() {
scoped_solver s(__func__);
auto a = s.var(s.add_var(5));
auto b = s.var(s.add_var(5));
s.add_ult(a + 8, b*b*b);
s.check();
}
static void test_fi_disequal_mild() {
{
// small version
scoped_solver s(__func__);
auto a = s.var(s.add_var(6));
auto b = s.var(s.add_var(6));
// v > -3*v
s.add_eq(a - 3);
s.add_ult(-a*b, b);
s.check();
}
{
// large version
scoped_solver s(__func__);
auto a = s.var(s.add_var(256));
auto b = s.var(s.add_var(256));
// v > -100*v
s.add_eq(a - 100);
s.add_ult(-a*b, b);
s.check();
}
}
static void test_pop_conflict() {
scoped_solver s(__func__);
auto a = s.var(s.add_var(32));
s.add_ule(a, 5);
s.push();
s.add_ult(5, a);
s.push();
s.add_ule(1, a);
s.check();
s.expect_unsat();
s.pop();
s.check();
s.expect_unsat();
s.pop();
s.add_ult(4, a);
// s.add_ule(100, a);
s.check();
s.expect_sat({{a, 5}});
}
/**
* Motivated by weak forbidden intervals lemma in bench23:
* v66 > v41 && v67 == v66 ==> v67 != 0
*
* Cause by omitting v41 from the symbolic bounds of v66 > v41.
*
* The stronger lemma should be:
* v66 > v41 && v67 == v66 ==> v41 + 1 <= v67
*
* The conclusion could also be v67 > v41 (note that -1 > v41 follows from premise v66 > v41, so both conclusions are equivalent in this lemma).
*/
static void test_bench23_fi_lemma() {
scoped_solver s(__func__);
auto x = s.var(s.add_var(256)); // v41
auto y = s.var(s.add_var(256)); // v66
auto z = s.var(s.add_var(256)); // v67
s.add_ult(x, y); // v66 > v41
s.add_eq(y, z); // v66 == v67
s.add_eq(z, 0); // v67 == 0
s.check();
s.expect_unsat();
}
static void test_bench6_viable() {
scoped_solver s(__func__);
rational coeff("12737129816104781496592808350955669863859698313220462044340334240870444260393");
auto a = s.var(s.add_var(256));
auto b = s.var(s.add_var(256));
s.add_eq(4 * b - coeff * a);
s.add_eq(b - 1);
// s.add_eq(a - 100);
s.check();
}
static void test_bench27_viable(const char* coeff) {
scoped_solver s(__func__);
rational a(coeff);
rational b = rational::power_of_two(128) - 1;
auto x = s.var(s.add_var(256));
s.add_ult(0, x);
s.add_ule(a * x + b, b);
s.check();
}
static void test_bench27_viable1() {
test_bench27_viable("2787252867449406954228501409473613420036128"); // 2^5 * a'
}
static void test_bench27_viable2() {
test_bench27_viable("5574846017265734846920466193554658608284160"); // 2^9 * a'
}
// -1*v12 <= -1*v12 + v8 + v7*v1 + 2^128*v7 + 1
// v8 := 0 v12 := 1 v4 := 0 v9 := 1 v17 := 0 v1 := 5574846017265734846920466193554658608283648
//
// Substitute assignment:
// -1 <= (5574846017265734846920466193554658608283648 + 2^128) * v7
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ == 2^142
// this is actually an equation
//
// 2^142 * v7 == -1, unsatisfiable due to parity
static void test_bench27_viable3() {
scoped_solver s(__func__);
rational const a("5574846017265734846920466193554658608283648");
rational const b = rational::power_of_two(128);
auto const v1 = s.var(s.add_var(256));
auto const v7 = s.var(s.add_var(256));
auto const v8 = s.var(s.add_var(256));
auto const v12 = s.var(s.add_var(256));
s.add_ule(-v12, -v12 + v8 + v7*v1 + b*v7 + 1);
s.add_eq(v8, 0);
s.add_eq(v12, 1);
s.add_eq(v1, a);
s.check();
s.expect_unsat();
}
// similar as test_bench27_viable3, but satisfiable (to test the other branch)
static void test_bench27_viable3_sat() {
scoped_solver s(__func__);
rational const a("5574846017265734846920466193554658608283648");
rational const b = rational::power_of_two(128) - 1;
auto const v1 = s.var(s.add_var(256));
auto const v7 = s.var(s.add_var(256));
auto const v8 = s.var(s.add_var(256));
auto const v12 = s.var(s.add_var(256));
s.add_ule(-v12, -v12 + v8 + v7*v1 + b*v7 + 1);
s.add_eq(v8, 0);
s.add_eq(v12, 1);
s.add_eq(v1, a);
s.check();
s.expect_sat();
}
static void test_bench13_mulovfl_ineq() {
scoped_solver s(__func__);
rational const a = rational::power_of_two(128) - 1;
rational const b = rational::power_of_two(128) - 1;
auto const x = s.var(s.add_var(256));
auto const y = s.var(s.add_var(256));
s.add_ule(-x - 1, x * y);
s.add_ule(x, a);
s.add_ule(y, b);
s.add_umul_noovfl(x, y);
s.check();
//s.expect_unsat();
}
}; // class test_polysat
} // namespace polysat
static void STD_CALL polysat_on_ctrl_c(int) {
signal(SIGINT, SIG_DFL);
using namespace polysat;
test_records.display(std::cout);
raise(SIGINT);
}
void tst_polysat() {
using namespace polysat;
test_polysat::test_ineq_axiom4(32, 7);
#if 0 // Enable this block to run a single unit test with detailed output.
collect_test_records = false;
test_max_conflicts = 50;
//test_polysat::test_bench13_mulovfl_ineq();
test_polysat::test_ineq_axiom3(32, 3); // TODO: assertion
// test_polysat::test_ineq_axiom6(32, 0); // TODO: assertion
// test_polysat::test_band5(); // TODO: assertion when clause simplification (merging p>q and p=q) is enabled
// test_polysat::test_bench27_viable1(); // TODO: refinement
// test_polysat::test_bench27_viable2(); // TODO: refinement
return;
#endif
// If non-empty, only run tests whose name contains the run_filter
run_filter = "";
test_max_conflicts = 20;
collect_test_records = true;
if (collect_test_records) {
signal(SIGINT, polysat_on_ctrl_c);
set_default_debug_action(debug_action::throw_exception);
set_log_enabled(false);
}
#if 0
RUN(test_polysat::test_elim1());
RUN(test_polysat::test_elim2());
RUN(test_polysat::test_elim3());
RUN(test_polysat::test_elim4());
RUN(test_polysat::test_elim5());
RUN(test_polysat::test_elim6());
RUN(test_polysat::test_elim7());
#endif
RUN(test_polysat::test_parity1());
RUN(test_polysat::test_parity1b());
RUN(test_polysat::test_parity2());
RUN(test_polysat::test_parity3());
RUN(test_polysat::test_parity4());
RUN(test_polysat::test_clause_simplify1());
RUN(test_polysat::test_clause_simplify2());
// RUN(test_polysat::test_clause_simplify3()); // TODO: corresponding simplification is disabled at the moment
RUN(test_polysat::test_bench23_fi_lemma());
RUN(test_polysat::test_add_conflicts());
RUN(test_polysat::test_wlist());
RUN(test_polysat::test_cjust());
// RUN(test_polysat::test_subst());
// RUN(test_polysat::test_subst_signed());
RUN(test_polysat::test_pop_conflict());
RUN(test_polysat::test_l1());
RUN(test_polysat::test_l2());
RUN(test_polysat::test_l3());
RUN(test_polysat::test_l4());
RUN(test_polysat::test_l4b());
RUN(test_polysat::test_l5());
RUN(test_polysat::test_l6());
RUN(test_polysat::test_p1());
RUN(test_polysat::test_p2());
RUN(test_polysat::test_p3());
RUN(test_polysat::test_ineq_basic1());
RUN(test_polysat::test_ineq_basic2());
RUN(test_polysat::test_ineq_basic3());
RUN(test_polysat::test_ineq_basic4());
RUN(test_polysat::test_ineq_basic5());
RUN(test_polysat::test_ineq_basic6());
RUN(test_polysat::test_var_minimize()); // works but var_minimize isn't used (UNSAT before lemma is created)
RUN(test_polysat::test_ineq1());
RUN(test_polysat::test_ineq2());
RUN(test_polysat::test_monot());
RUN(test_polysat::test_monot_bounds(2));
RUN(test_polysat::test_monot_bounds(8));
RUN(test_polysat::test_monot_bounds());
RUN(test_polysat::test_monot_bounds_full());
RUN(test_polysat::test_monot_bounds_simple(8));
RUN(test_polysat::test_fixed_point_arith_mul_div_inverse());
RUN(test_polysat::test_fixed_point_arith_div_mul_inverse());
RUN(test_polysat::test_quot_rem_incomplete());
RUN(test_polysat::test_quot_rem_fixed());
RUN(test_polysat::test_quot_rem());
RUN(test_polysat::test_quot_rem2());
RUN(test_polysat::test_band1());
RUN(test_polysat::test_band2());
RUN(test_polysat::test_band3());
RUN(test_polysat::test_band4());
RUN(test_polysat::test_band5());
RUN(test_polysat::test_band5_clause());
RUN(test_polysat::test_fi_zero());
RUN(test_polysat::test_fi_nonzero());
RUN(test_polysat::test_fi_nonmax());
RUN(test_polysat::test_fi_disequal_mild());
RUN(test_polysat::test_bench6_viable());
// RUN(test_polysat::test_bench27_viable1()); // stuck in refinement loop + fallback solver
// RUN(test_polysat::test_bench27_viable2()); // stuck in refinement loop + fallback solver
RUN(test_polysat::test_bench27_viable3());
RUN(test_polysat::test_bench27_viable3_sat());
RUN(test_polysat::test_ineq_axiom1());
RUN(test_polysat::test_ineq_axiom2());
RUN(test_polysat::test_ineq_axiom3());
RUN(test_polysat::test_ineq_axiom4());
RUN(test_polysat::test_ineq_axiom5());
RUN(test_polysat::test_ineq_axiom6());
RUN(test_polysat::test_ineq_non_axiom1());
RUN(test_polysat::test_ineq_non_axiom4());
RUN(test_polysat::test_fi_quickcheck1());
RUN(test_polysat::test_fi_quickcheck2());
if (collect_test_records)
test_records.display(std::cout);
}