3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-04-12 04:03:39 +00:00
z3/src/sat/tactic/goal2sat.cpp
Nikolaj Bjorner ed27ce5526 fix regression in goal2sat
Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
2021-07-31 11:41:55 -07:00

1350 lines
43 KiB
C++

/*++
Copyright (c) 2011 Microsoft Corporation
Module Name:
goal2sat.cpp
Abstract:
"Compile" a goal into the SAT engine.
Atoms are "abstracted" into boolean variables.
The mapping between boolean variables and atoms
can be used to convert back the state of the
SAT engine into a goal.
The idea is to support scenarios such as:
1) simplify, blast, convert into SAT, and solve
2) convert into SAT, apply SAT for a while, learn new units, and translate back into a goal.
3) convert into SAT, apply SAT preprocessor (failed literal propagation, resolution, etc) and translate back into a goal.
4) Convert boolean structure into SAT, convert atoms into another engine, combine engines using lazy combination, solve.
Author:
Leonardo (leonardo) 2011-10-26
Notes:
--*/
#include "util/ref_util.h"
#include "ast/ast_smt2_pp.h"
#include "ast/ast_pp.h"
#include "ast/ast_smt2_pp.h"
#include "ast/ast_ll_pp.h"
#include "ast/pb_decl_plugin.h"
#include "ast/ast_util.h"
#include "ast/for_each_expr.h"
#include "model/model_evaluator.h"
#include "model/model_v2_pp.h"
#include "tactic/tactic.h"
#include "tactic/generic_model_converter.h"
#include "sat/sat_cut_simplifier.h"
#include "sat/sat_drat.h"
#include "sat/tactic/goal2sat.h"
#include "sat/smt/pb_solver.h"
#include "sat/smt/euf_solver.h"
#include "sat/smt/sat_th.h"
#include "sat/sat_params.hpp"
#include<sstream>
struct goal2sat::imp : public sat::sat_internalizer {
struct frame {
app * m_t;
unsigned m_root:1;
unsigned m_sign:1;
unsigned m_idx;
frame(app * t, bool r, bool s, unsigned idx):
m_t(t), m_root(r), m_sign(s), m_idx(idx) {}
};
ast_manager & m;
pb_util pb;
svector<frame> m_frame_stack;
svector<sat::literal> m_result_stack;
obj_map<app, sat::literal> m_app2lit;
u_map<app*> m_lit2app;
unsigned_vector m_cache_lim;
app_ref_vector m_cache_trail;
obj_hashtable<expr> m_interface_vars;
sat::solver_core & m_solver;
atom2bool_var & m_map;
dep2asm_map & m_dep2asm;
obj_map<expr, sat::bool_var>* m_expr2var_replay { nullptr };
bool m_ite_extra;
unsigned long long m_max_memory;
expr_ref_vector m_trail;
func_decl_ref_vector m_unhandled_funs;
bool m_default_external;
bool m_euf { false };
bool m_drat { false };
bool m_is_redundant { false };
bool m_top_level { false };
sat::literal_vector aig_lits;
imp(ast_manager & _m, params_ref const & p, sat::solver_core & s, atom2bool_var & map, dep2asm_map& dep2asm, bool default_external):
m(_m),
pb(m),
m_cache_trail(m),
m_solver(s),
m_map(map),
m_dep2asm(dep2asm),
m_trail(m),
m_unhandled_funs(m),
m_default_external(default_external) {
updt_params(p);
}
~imp() override {
}
sat::cut_simplifier* aig() {
return m_solver.get_cut_simplifier();
}
void updt_params(params_ref const & p) {
sat_params sp(p);
m_ite_extra = p.get_bool("ite_extra", true);
m_max_memory = megabytes_to_bytes(p.get_uint("max_memory", UINT_MAX));
m_euf = sp.euf();
m_drat = sp.drat_file().is_non_empty_string();
}
void throw_op_not_handled(std::string const& s) {
std::string s0 = "operator " + s + " not supported, apply simplifier before invoking translator";
throw tactic_exception(std::move(s0));
}
sat::status mk_status() const {
return sat::status::th(m_is_redundant, m.get_basic_family_id());
}
bool relevancy_enabled() {
return m_euf && ensure_euf()->relevancy_enabled();
}
bool top_level_relevant() {
return m_top_level && relevancy_enabled();
}
void add_dual_def(unsigned n, sat::literal const* lits) {
if (relevancy_enabled())
ensure_euf()->add_aux(n, lits);
}
void add_dual_root(unsigned n, sat::literal const* lits) {
if (relevancy_enabled())
ensure_euf()->add_root(n, lits);
}
void add_dual_root(sat::literal lit) {
add_dual_root(1, &lit);
}
void mk_clause(sat::literal l) {
mk_clause(1, &l);
}
void mk_clause(sat::literal l1, sat::literal l2) {
sat::literal lits[2] = { l1, l2 };
mk_clause(2, lits);
}
void mk_clause(sat::literal l1, sat::literal l2, sat::literal l3) {
sat::literal lits[3] = { l1, l2, l3 };
mk_clause(3, lits);
}
void mk_clause(unsigned n, sat::literal * lits) {
TRACE("goal2sat", tout << "mk_clause: "; for (unsigned i = 0; i < n; i++) tout << lits[i] << " "; tout << "\n";);
add_dual_def(n, lits);
m_solver.add_clause(n, lits, mk_status());
}
void mk_root_clause(sat::literal l) {
mk_root_clause(1, &l);
}
void mk_root_clause(sat::literal l1, sat::literal l2) {
sat::literal lits[2] = { l1, l2 };
mk_root_clause(2, lits);
}
void mk_root_clause(sat::literal l1, sat::literal l2, sat::literal l3) {
sat::literal lits[3] = { l1, l2, l3 };
mk_root_clause(3, lits);
}
void mk_root_clause(unsigned n, sat::literal * lits) {
TRACE("goal2sat", tout << "mk_root_clause: "; for (unsigned i = 0; i < n; i++) tout << lits[i] << " "; tout << "\n";);
add_dual_root(n, lits);
m_solver.add_clause(n, lits, m_is_redundant ? mk_status() : sat::status::input());
}
sat::bool_var add_var(bool is_ext, expr* n) {
sat::bool_var v;
if (m_expr2var_replay && m_expr2var_replay->find(n, v))
return v;
v = m_solver.add_var(is_ext);
log_def(v, n);
if (top_level_relevant() && !is_bool_op(n))
ensure_euf()->track_relevancy(v);
return v;
}
void log_def(sat::bool_var v, expr* n) {
if (m_drat && m_euf)
ensure_euf()->drat_bool_def(v, n);
}
sat::bool_var to_bool_var(expr* e) override {
sat::literal l;
sat::bool_var v = m_map.to_bool_var(e);
if (v != sat::null_bool_var)
return v;
if (is_app(e) && m_app2lit.find(to_app(e), l) && !l.sign())
return l.var();
return sat::null_bool_var;
}
void set_expr2var_replay(obj_map<expr, sat::bool_var>* r) override {
m_expr2var_replay = r;
}
sat::bool_var mk_bool_var(expr* t) {
force_push();
sat::bool_var v;
if (!m_expr2var_replay || !m_expr2var_replay->find(t, v))
v = add_var(true, t);
m_map.insert(t, v);
if (relevancy_enabled() && (m.is_true(t) || m.is_false(t)))
add_dual_root(sat::literal(v, m.is_false(t)));
return v;
}
sat::bool_var add_bool_var(expr* t) override {
sat::bool_var v = m_map.to_bool_var(t);
if (v == sat::null_bool_var)
v = mk_bool_var(t);
else
m_solver.set_external(v);
return v;
}
unsigned m_num_scopes{ 0 };
void force_push() {
for (; m_num_scopes > 0; --m_num_scopes) {
m_map.push();
m_cache_lim.push_back(m_cache_trail.size());
}
}
void push() override {
++m_num_scopes;
}
void pop(unsigned n) override {
if (n <= m_num_scopes) {
m_num_scopes -= n;
return;
}
n -= m_num_scopes;
m_num_scopes = 0;
m_map.pop(n);
unsigned k = m_cache_lim[m_cache_lim.size() - n];
for (unsigned i = m_cache_trail.size(); i-- > k; ) {
app* t = m_cache_trail.get(i);
sat::literal lit;
if (m_app2lit.find(t, lit)) {
m_app2lit.remove(t);
m_lit2app.remove(lit.index());
}
}
m_cache_trail.shrink(k);
m_cache_lim.shrink(m_cache_lim.size() - n);
}
// remove non-external literals from cache.
void uncache(sat::literal lit) override {
app* t = nullptr;
if (m_lit2app.find(lit.index(), t)) {
m_lit2app.remove(lit.index());
m_app2lit.remove(t);
}
}
void cache(app* t, sat::literal l) override {
force_push();
SASSERT(!m_app2lit.contains(t));
SASSERT(!m_lit2app.contains(l.index()));
m_app2lit.insert(t, l);
m_lit2app.insert(l.index(), t);
m_cache_trail.push_back(t);
}
void convert_atom(expr * t, bool root, bool sign) {
SASSERT(m.is_bool(t));
sat::literal l;
sat::bool_var v = m_map.to_bool_var(t);
if (v == sat::null_bool_var) {
if (m.is_true(t)) {
sat::literal tt = sat::literal(add_var(false, m.mk_true()), false);
mk_clause(tt);
add_dual_root(1, &tt);
l = sign ? ~tt : tt;
}
else if (m.is_false(t)) {
sat::literal tt = sat::literal(add_var(false, m.mk_false()), true);
mk_clause(tt);
add_dual_root(1, &tt);
l = sign ? tt : ~tt;
}
else if (m_euf) {
convert_euf(t, root, sign);
return;
}
else {
if (!is_uninterp_const(t)) {
if (!is_app(t)) {
std::ostringstream strm;
strm << mk_ismt2_pp(t, m);
throw_op_not_handled(strm.str());
}
m_unhandled_funs.push_back(to_app(t)->get_decl());
}
v = mk_bool_var(t);
l = sat::literal(v, sign);
bool ext = m_default_external || !is_uninterp_const(t) || m_interface_vars.contains(t);
if (ext)
m_solver.set_external(v);
TRACE("sat", tout << "new_var: " << v << ": " << mk_bounded_pp(t, m, 2) << " " << is_uninterp_const(t) << "\n";);
}
}
else {
SASSERT(v != sat::null_bool_var);
l = sat::literal(v, sign);
m_solver.set_eliminated(v, false);
}
SASSERT(l != sat::null_literal);
if (root)
mk_root_clause(l);
else
m_result_stack.push_back(l);
}
bool convert_app(app* t, bool root, bool sign) {
if (!m_euf && pb.is_pb(t)) {
m_frame_stack.push_back(frame(to_app(t), root, sign, 0));
return false;
}
else {
convert_atom(t, root, sign);
return true;
}
}
bool process_cached(app* t, bool root, bool sign) {
sat::literal l = sat::null_literal;
if (!m_app2lit.find(t, l))
return false;
if (sign)
l.neg();
if (root)
mk_root_clause(l);
else
m_result_stack.push_back(l);
return true;
}
bool visit(expr * t, bool root, bool sign) {
SASSERT(m.is_bool(t));
if (!is_app(t)) {
convert_atom(t, root, sign);
return true;
}
if (process_cached(to_app(t), root, sign))
return true;
if (to_app(t)->get_family_id() != m.get_basic_family_id())
return convert_app(to_app(t), root, sign);
switch (to_app(t)->get_decl_kind()) {
case OP_NOT:
case OP_OR:
case OP_AND:
case OP_ITE:
case OP_XOR:
case OP_IMPLIES:
m_frame_stack.push_back(frame(to_app(t), root, sign, 0));
return false;
case OP_EQ:
if (m.is_bool(to_app(t)->get_arg(1))) {
m_frame_stack.push_back(frame(to_app(t), root, sign, 0));
return false;
}
else {
convert_atom(t, root, sign);
return true;
}
case OP_DISTINCT: {
if (m_euf) {
convert_euf(t, root, sign);
return true;
}
TRACE("goal2sat_not_handled", tout << mk_ismt2_pp(t, m) << "\n";);
std::ostringstream strm;
strm << mk_ismt2_pp(t, m);
throw_op_not_handled(strm.str());
}
default:
convert_atom(t, root, sign);
return true;
}
}
void convert_or(app * t, bool root, bool sign) {
TRACE("goal2sat", tout << "convert_or:\n" << mk_bounded_pp(t, m, 2) << " root " << root << " stack " << m_result_stack.size() << "\n";);
unsigned num = t->get_num_args();
SASSERT(num <= m_result_stack.size());
unsigned old_sz = m_result_stack.size() - num;
if (root) {
SASSERT(num == m_result_stack.size());
if (sign) {
// this case should not really happen.
for (unsigned i = 0; i < num; i++) {
sat::literal l = m_result_stack[i];
l.neg();
mk_root_clause(l);
}
}
else {
mk_root_clause(m_result_stack.size(), m_result_stack.data());
}
m_result_stack.shrink(old_sz);
}
else {
SASSERT(num <= m_result_stack.size());
sat::bool_var k = add_var(false, t);
sat::literal l(k, false);
cache(t, l);
sat::literal * lits = m_result_stack.end() - num;
for (unsigned i = 0; i < num; i++)
mk_clause(~lits[i], l);
m_result_stack.push_back(~l);
lits = m_result_stack.end() - num - 1;
if (aig()) {
aig_lits.reset();
aig_lits.append(num, lits);
}
// remark: mk_clause may perform destructive updated to lits.
// I have to execute it after the binary mk_clause above.
mk_clause(num+1, lits);
if (aig())
aig()->add_or(l, num, aig_lits.data());
m_solver.set_phase(~l);
m_result_stack.shrink(old_sz);
if (sign)
l.neg();
m_result_stack.push_back(l);
}
}
void convert_and(app * t, bool root, bool sign) {
TRACE("goal2sat", tout << "convert_and:\n" << mk_bounded_pp(t, m, 2) << " root: " << root << " result stack: " << m_result_stack.size() << "\n";);
unsigned num = t->get_num_args();
unsigned old_sz = m_result_stack.size() - num;
SASSERT(num <= m_result_stack.size());
if (root) {
if (sign) {
for (unsigned i = 0; i < num; ++i) {
m_result_stack[i].neg();
}
mk_root_clause(m_result_stack.size(), m_result_stack.data());
}
else {
for (unsigned i = 0; i < num; ++i) {
mk_root_clause(m_result_stack[i]);
}
}
m_result_stack.shrink(old_sz);
}
else {
SASSERT(num <= m_result_stack.size());
sat::bool_var k = add_var(false, t);
sat::literal l(k, false);
cache(t, l);
sat::literal * lits = m_result_stack.end() - num;
// l => /\ lits
for (unsigned i = 0; i < num; i++) {
mk_clause(~l, lits[i]);
}
// /\ lits => l
for (unsigned i = 0; i < num; ++i) {
m_result_stack[m_result_stack.size() - num + i].neg();
}
m_result_stack.push_back(l);
lits = m_result_stack.end() - num - 1;
if (aig()) {
aig_lits.reset();
aig_lits.append(num, lits);
}
mk_clause(num+1, lits);
if (aig()) {
aig()->add_and(l, num, aig_lits.data());
}
m_solver.set_phase(l);
if (sign)
l.neg();
m_result_stack.shrink(old_sz);
m_result_stack.push_back(l);
TRACE("goal2sat", tout << m_result_stack << "\n";);
}
}
void convert_ite(app * n, bool root, bool sign) {
unsigned sz = m_result_stack.size();
SASSERT(sz >= 3);
sat::literal c = m_result_stack[sz-3];
sat::literal t = m_result_stack[sz-2];
sat::literal e = m_result_stack[sz-1];
m_result_stack.shrink(sz - 3);
if (root) {
SASSERT(sz == 3);
if (sign) {
mk_root_clause(~c, ~t);
mk_root_clause(c, ~e);
}
else {
mk_root_clause(~c, t);
mk_root_clause(c, e);
}
}
else {
sat::bool_var k = add_var(false, n);
sat::literal l(k, false);
cache(n, l);
mk_clause(~l, ~c, t);
mk_clause(~l, c, e);
mk_clause(l, ~c, ~t);
mk_clause(l, c, ~e);
if (m_ite_extra) {
mk_clause(~t, ~e, l);
mk_clause(t, e, ~l);
}
if (aig()) aig()->add_ite(l, c, t, e);
if (sign)
l.neg();
m_result_stack.push_back(l);
}
}
void convert_not(app* t, bool root, bool sign) {
SASSERT(t->get_num_args() == 1);
unsigned sz = m_result_stack.size();
SASSERT(sz >= 1);
sat::literal lit = m_result_stack[sz - 1];
m_result_stack.shrink(sz - 1);
if (root) {
SASSERT(sz == 1);
mk_root_clause(sign ? lit : ~lit);
}
else {
sat::bool_var k = add_var(false, t);
sat::literal l(k, false);
cache(t, l);
// l <=> ~lit
mk_clause(lit, l);
mk_clause(~lit, ~l);
if (sign)
l.neg();
m_result_stack.push_back(l);
}
}
void convert_implies(app* t, bool root, bool sign) {
SASSERT(t->get_num_args() == 2);
unsigned sz = m_result_stack.size();
SASSERT(sz >= 2);
sat::literal l2 = m_result_stack[sz - 1];
sat::literal l1 = m_result_stack[sz - 2];
m_result_stack.shrink(sz - 2);
if (root) {
SASSERT(sz == 2);
if (sign) {
mk_root_clause(l1);
mk_root_clause(~l2);
}
else {
mk_root_clause(~l1, l2);
}
}
else {
sat::bool_var k = add_var(false, t);
sat::literal l(k, false);
cache(t, l);
// l <=> (l1 => l2)
mk_clause(~l, ~l1, l2);
mk_clause(l1, l);
mk_clause(~l2, l);
if (sign)
l.neg();
m_result_stack.push_back(l);
}
}
void convert_iff(app * t, bool root, bool sign) {
if (t->get_num_args() != 2)
throw default_exception("unexpected number of arguments to " + mk_pp(t, m));
SASSERT(t->get_num_args() == 2);
unsigned sz = m_result_stack.size();
SASSERT(sz >= 2);
sat::literal l1 = m_result_stack[sz-1];
sat::literal l2 = m_result_stack[sz-2];
m_result_stack.shrink(sz - 2);
if (root) {
if (m.is_xor(t))
sign = !sign;
SASSERT(sz == 2);
if (sign) {
mk_root_clause(l1, l2);
mk_root_clause(~l1, ~l2);
}
else {
mk_root_clause(l1, ~l2);
mk_root_clause(~l1, l2);
}
}
else {
sat::bool_var k = add_var(false, t);
sat::literal l(k, false);
if (m.is_xor(t))
l1.neg();
mk_clause(~l, l1, ~l2);
mk_clause(~l, ~l1, l2);
mk_clause(l, l1, l2);
mk_clause(l, ~l1, ~l2);
if (aig()) aig()->add_iff(l, l1, l2);
cache(t, l);
if (sign)
l.neg();
m_result_stack.push_back(l);
}
}
func_decl_ref_vector const& interpreted_funs() {
auto* ext = dynamic_cast<euf::solver*>(m_solver.get_extension());
if (ext)
return ext->unhandled_functions();
return m_unhandled_funs;
}
euf::solver* ensure_euf() {
SASSERT(m_euf);
sat::extension* ext = m_solver.get_extension();
euf::solver* euf = nullptr;
if (!ext) {
euf = alloc(euf::solver, m, *this);
m_solver.set_extension(euf);
#if 0
std::function<solver*(void)> mk_solver = [&]() {
return mk_inc_sat_solver(m, m_params, true);
};
euf->set_mk_solver(mk_solver);
#endif
}
else {
euf = dynamic_cast<euf::solver*>(ext);
}
if (!euf)
throw default_exception("cannot convert to euf");
return euf;
}
void convert_euf(expr* e, bool root, bool sign) {
SASSERT(m_euf);
TRACE("goal2sat", tout << "convert-euf " << mk_bounded_pp(e, m, 2) << " root " << root << "\n";);
euf::solver* euf = ensure_euf();
sat::literal lit;
{
flet<bool> _top(m_top_level, false);
lit = euf->internalize(e, sign, root, m_is_redundant);
}
if (lit == sat::null_literal)
return;
if (top_level_relevant())
euf->track_relevancy(lit.var());
if (root)
mk_root_clause(lit);
else
m_result_stack.push_back(lit);
}
void convert_ba(app* t, bool root, bool sign) {
SASSERT(!m_euf);
sat::extension* ext = dynamic_cast<pb::solver*>(m_solver.get_extension());
euf::th_solver* th = nullptr;
if (!ext) {
th = alloc(pb::solver, m, *this, pb.get_family_id());
m_solver.set_extension(th);
th->push_scopes(m_solver.num_scopes());
}
else {
th = dynamic_cast<euf::th_solver*>(ext);
SASSERT(th);
}
auto lit = th->internalize(t, sign, root, m_is_redundant);
m_result_stack.shrink(m_result_stack.size() - t->get_num_args());
if (lit == sat::null_literal)
return;
if (root)
mk_root_clause(lit);
else
m_result_stack.push_back(lit);
}
void convert(app * t, bool root, bool sign) {
if (t->get_family_id() == m.get_basic_family_id()) {
switch (to_app(t)->get_decl_kind()) {
case OP_OR:
convert_or(t, root, sign);
break;
case OP_AND:
convert_and(t, root, sign);
break;
case OP_ITE:
convert_ite(t, root, sign);
break;
case OP_EQ:
convert_iff(t, root, sign);
break;
case OP_XOR:
convert_iff(t, root, sign);
break;
case OP_IMPLIES:
convert_implies(t, root, sign);
break;
case OP_NOT:
convert_not(t, root, sign);
break;
default:
UNREACHABLE();
}
SASSERT(!root || m_result_stack.empty());
}
else if (!m_euf && pb.is_pb(t)) {
convert_ba(t, root, sign);
}
else {
UNREACHABLE();
}
}
struct scoped_stack {
imp& i;
sat::literal_vector& r;
unsigned rsz;
svector<frame>& frames;
unsigned fsz;
bool is_root;
scoped_stack(imp& x, bool is_root) :
i(x), r(i.m_result_stack), rsz(r.size()), frames(x.m_frame_stack), fsz(frames.size()), is_root(is_root)
{}
~scoped_stack() {
if (frames.size() > fsz) {
frames.shrink(fsz);
r.shrink(rsz);
return;
}
SASSERT(i.m.limit().is_canceled() || frames.size() == fsz);
SASSERT(i.m.limit().is_canceled() || !is_root || rsz == r.size());
SASSERT(i.m.limit().is_canceled() || is_root || rsz + 1 == r.size());
}
};
void process(expr* n, bool is_root, bool redundant) {
TRACE("goal2sat", tout << "process-begin " << mk_bounded_pp(n, m, 2)
<< " root: " << is_root
<< " result-stack: " << m_result_stack.size()
<< " frame-stack: " << m_frame_stack.size() << "\n";);
flet<bool> _is_redundant(m_is_redundant, redundant);
scoped_stack _sc(*this, is_root);
unsigned sz = m_frame_stack.size();
if (visit(n, is_root, false))
return;
while (m_frame_stack.size() > sz) {
loop:
if (!m.inc())
throw tactic_exception(m.limit().get_cancel_msg());
if (memory::get_allocation_size() > m_max_memory)
throw tactic_exception(TACTIC_MAX_MEMORY_MSG);
unsigned fsz = m_frame_stack.size();
frame const& _fr = m_frame_stack[fsz-1];
app * t = _fr.m_t;
bool root = _fr.m_root;
bool sign = _fr.m_sign;
TRACE("goal2sat_bug", tout << "result stack\n";
tout << "ref-count: " << t->get_ref_count() << "\n";
tout << mk_bounded_pp(t, m, 3) << " root: " << root << " sign: " << sign << "\n";
tout << m_result_stack << "\n";);
if (_fr.m_idx == 0 && process_cached(t, root, sign)) {
m_frame_stack.pop_back();
continue;
}
if (m.is_not(t) && !m.is_not(t->get_arg(0))) {
m_frame_stack.pop_back();
visit(t->get_arg(0), root, !sign);
continue;
}
unsigned num = t->get_num_args();
while (m_frame_stack[fsz-1].m_idx < num) {
expr * arg = t->get_arg(m_frame_stack[fsz-1].m_idx);
m_frame_stack[fsz - 1].m_idx++;
if (!visit(arg, false, false))
goto loop;
TRACE("goal2sat_bug", tout << "visit " << mk_bounded_pp(arg, m, 2) << " result stack: " << m_result_stack.size() << "\n";);
}
TRACE("goal2sat_bug", tout << "converting\n";
tout << mk_bounded_pp(t, m, 2) << " root: " << root << " sign: " << sign << "\n";
tout << m_result_stack << "\n";);
SASSERT(m_frame_stack.size() > sz);
convert(t, root, sign);
m_frame_stack.pop_back();
}
TRACE("goal2sat", tout
<< "done process: " << mk_bounded_pp(n, m, 3)
<< " frame-stack: " << m_frame_stack.size()
<< " result-stack: " << m_result_stack.size() << "\n";);
}
sat::literal internalize(expr* n, bool redundant) override {
flet<bool> _top(m_top_level, false);
unsigned sz = m_result_stack.size();
(void)sz;
SASSERT(n->get_ref_count() > 0);
TRACE("goal2sat", tout << "internalize " << mk_bounded_pp(n, m, 2) << "\n";);
process(n, false, redundant);
SASSERT(m_result_stack.size() == sz + 1);
sat::literal result = m_result_stack.back();
TRACE("goal2sat", tout << "done internalize " << result << " " << mk_bounded_pp(n, m, 2) << "\n";);
m_result_stack.pop_back();
if (!result.sign() && m_map.to_bool_var(n) == sat::null_bool_var) {
force_push();
m_map.insert(n, result.var());
m_solver.set_external(result.var());
}
return result;
}
bool is_bool_op(expr* t) const override {
if (!is_app(t))
return false;
if (to_app(t)->get_family_id() == m.get_basic_family_id()) {
switch (to_app(t)->get_decl_kind()) {
case OP_OR:
case OP_AND:
case OP_TRUE:
case OP_FALSE:
case OP_NOT:
case OP_IMPLIES:
case OP_XOR:
return true;
case OP_ITE:
case OP_EQ:
return m.is_bool(to_app(t)->get_arg(1));
default:
return false;
}
}
else if (!m_euf && to_app(t)->get_family_id() == pb.get_family_id())
return true;
else
return false;
}
void process(expr * n) {
flet<bool> _top(m_top_level, true);
VERIFY(m_result_stack.empty());
TRACE("goal2sat", tout << "assert: " << mk_bounded_pp(n, m, 3) << "\n";);
process(n, true, m_is_redundant);
CTRACE("goal2sat", !m_result_stack.empty(), tout << m_result_stack << "\n";);
SASSERT(m_result_stack.empty());
}
void insert_dep(expr* dep0, expr* dep, bool sign) {
SASSERT(sign || dep0 == dep); // !sign || (not dep0) == dep.
SASSERT(!sign || m.is_not(dep0));
expr_ref new_dep(m), fml(m);
if (is_uninterp_const(dep)) {
new_dep = dep;
}
else {
new_dep = m.mk_fresh_const("dep", m.mk_bool_sort());
m_trail.push_back(new_dep);
m_interface_vars.insert(new_dep);
fml = m.mk_iff(new_dep, dep);
process(fml);
}
convert_atom(new_dep, false, false);
sat::literal lit = m_result_stack.back();
m_dep2asm.insert(dep0, sign?~lit:lit);
m_result_stack.pop_back();
}
void operator()(goal const & g) {
struct scoped_reset {
imp& i;
scoped_reset(imp& i) :i(i) {}
~scoped_reset() {
i.m_interface_vars.reset();
i.m_app2lit.reset();
i.m_lit2app.reset();
}
};
scoped_reset _reset(*this);
collect_boolean_interface(g, m_interface_vars);
unsigned size = g.size();
expr_ref f(m), d_new(m);
ptr_vector<expr> deps;
expr_ref_vector fmls(m);
for (unsigned idx = 0; idx < size; idx++) {
f = g.form(idx);
// Add assumptions.
if (g.dep(idx)) {
deps.reset();
fmls.reset();
m.linearize(g.dep(idx), deps);
fmls.push_back(f);
for (expr * d : deps) {
expr * d1 = d;
SASSERT(m.is_bool(d));
bool sign = m.is_not(d, d1);
insert_dep(d, d1, sign);
if (d == f) {
goto skip_dep;
}
if (sign) {
d_new = d1;
}
else {
d_new = m.mk_not(d);
}
fmls.push_back(d_new);
}
f = m.mk_or(fmls);
}
TRACE("goal2sat", tout << mk_bounded_pp(f, m, 2) << "\n";);
process(f);
skip_dep:
;
}
}
void update_model(model_ref& mdl) {
auto* ext = dynamic_cast<euf::solver*>(m_solver.get_extension());
if (ext)
ext->update_model(mdl);
}
void user_push() {
}
void user_pop(unsigned n) {
}
};
struct unsupported_bool_proc {
struct found {};
ast_manager & m;
unsupported_bool_proc(ast_manager & _m):m(_m) {}
void operator()(var *) {}
void operator()(quantifier *) {}
void operator()(app * n) {
if (n->get_family_id() == m.get_basic_family_id()) {
switch (n->get_decl_kind()) {
case OP_DISTINCT:
throw found();
default:
break;
}
}
}
};
/**
\brief Return true if s contains an unsupported Boolean operator.
goal_rewriter (with the following configuration) can be used to
eliminate unsupported operators.
:elim-and true
:blast-distinct true
*/
bool goal2sat::has_unsupported_bool(goal const & g) {
return false && test<unsupported_bool_proc>(g);
}
goal2sat::goal2sat():
m_imp(nullptr) {
}
goal2sat::~goal2sat() {
dealloc(m_imp);
}
void goal2sat::collect_param_descrs(param_descrs & r) {
insert_max_memory(r);
r.insert("ite_extra", CPK_BOOL, "(default: true) add redundant clauses (that improve unit propagation) when encoding if-then-else formulas");
}
void goal2sat::operator()(goal const & g, params_ref const & p, sat::solver_core & t, atom2bool_var & m, dep2asm_map& dep2asm, bool default_external) {
if (!m_imp) {
m_imp = alloc(imp, g.m(), p, t, m, dep2asm, default_external);
for (unsigned i = 0; i < m_scopes; ++i)
m_imp->user_push();
}
(*m_imp)(g);
if (!t.get_extension() && m_imp->interpreted_funs().empty()) {
dealloc(m_imp);
m_imp = nullptr;
}
else
m_scopes = 0;
}
void goal2sat::get_interpreted_funs(func_decl_ref_vector& funs) {
if (m_imp)
funs.append(m_imp->interpreted_funs());
}
bool goal2sat::has_interpreted_funs() const {
return m_imp && !m_imp->interpreted_funs().empty();
}
bool goal2sat::has_euf() const {
return m_imp && m_imp->m_euf;
}
void goal2sat::update_model(model_ref& mdl) {
if (m_imp)
m_imp->update_model(mdl);
}
void goal2sat::user_push() {
if (m_imp)
m_imp->user_push();
else
m_scopes++;
}
void goal2sat::user_pop(unsigned n) {
if (m_imp)
m_imp->user_pop(n);
else
m_scopes -= n;
}
sat::sat_internalizer& goal2sat::si(ast_manager& m, params_ref const& p, sat::solver_core& t, atom2bool_var& a2b, dep2asm_map& dep2asm, bool default_external) {
if (!m_imp)
m_imp = alloc(imp, m, p, t, a2b, dep2asm, default_external);
return *m_imp;
}
sat2goal::mc::mc(ast_manager& m): m(m), m_var2expr(m) {}
void sat2goal::mc::flush_smc(sat::solver_core& s, atom2bool_var const& map) {
s.flush(m_smc);
m_var2expr.resize(s.num_vars());
map.mk_var_inv(m_var2expr);
flush_gmc();
}
void sat2goal::mc::flush_gmc() {
sat::literal_vector updates;
m_smc.expand(updates);
if (!m_gmc) m_gmc = alloc(generic_model_converter, m, "sat2goal");
// now gmc owns the model converter
sat::literal_vector clause;
expr_ref_vector tail(m);
expr_ref def(m);
auto is_literal = [&](expr* e) { expr* r; return is_uninterp_const(e) || (m.is_not(e, r) && is_uninterp_const(r)); };
for (unsigned i = 0; i < updates.size(); ++i) {
sat::literal l = updates[i];
if (l == sat::null_literal) {
sat::literal lit0 = clause[0];
for (unsigned i = 1; i < clause.size(); ++i) {
tail.push_back(lit2expr(~clause[i]));
}
def = m.mk_or(lit2expr(lit0), mk_and(tail));
if (lit0.sign()) {
lit0.neg();
def = m.mk_not(def);
}
expr_ref e = lit2expr(lit0);
if (is_literal(e))
m_gmc->add(e, def);
clause.reset();
tail.reset();
}
// short circuit for equivalences:
else if (clause.empty() && tail.empty() &&
i + 5 < updates.size() &&
updates[i] == ~updates[i + 3] &&
updates[i + 1] == ~updates[i + 4] &&
updates[i + 2] == sat::null_literal &&
updates[i + 5] == sat::null_literal) {
sat::literal r = ~updates[i+1];
if (l.sign()) {
l.neg();
r.neg();
}
expr* a = lit2expr(l);
if (is_literal(a))
m_gmc->add(a, lit2expr(r));
i += 5;
}
else {
clause.push_back(l);
}
}
}
model_converter* sat2goal::mc::translate(ast_translation& translator) {
mc* result = alloc(mc, translator.to());
result->m_smc.copy(m_smc);
result->m_gmc = m_gmc ? dynamic_cast<generic_model_converter*>(m_gmc->translate(translator)) : nullptr;
for (expr* e : m_var2expr) {
result->m_var2expr.push_back(translator(e));
}
return result;
}
void sat2goal::mc::set_env(ast_pp_util* visitor) {
flush_gmc();
if (m_gmc) m_gmc->set_env(visitor);
}
void sat2goal::mc::display(std::ostream& out) {
flush_gmc();
if (m_gmc) m_gmc->display(out);
}
void sat2goal::mc::get_units(obj_map<expr, bool>& units) {
flush_gmc();
if (m_gmc) m_gmc->get_units(units);
}
void sat2goal::mc::operator()(sat::model& md) {
m_smc(md);
}
void sat2goal::mc::operator()(model_ref & md) {
// apply externalized model converter
CTRACE("sat_mc", m_gmc, m_gmc->display(tout << "before sat_mc\n"); model_v2_pp(tout, *md););
if (m_gmc) (*m_gmc)(md);
CTRACE("sat_mc", m_gmc, m_gmc->display(tout << "after sat_mc\n"); model_v2_pp(tout, *md););
}
void sat2goal::mc::operator()(expr_ref& fml) {
flush_gmc();
if (m_gmc) (*m_gmc)(fml);
}
void sat2goal::mc::insert(sat::bool_var v, expr * atom, bool aux) {
SASSERT(!m_var2expr.get(v, nullptr));
m_var2expr.reserve(v + 1);
m_var2expr.set(v, atom);
if (aux) {
SASSERT(m.is_bool(atom));
if (!m_gmc) m_gmc = alloc(generic_model_converter, m, "sat2goal");
if (is_uninterp_const(atom))
m_gmc->hide(to_app(atom)->get_decl());
}
TRACE("sat_mc", tout << "insert " << v << "\n";);
}
expr_ref sat2goal::mc::lit2expr(sat::literal l) {
sat::bool_var v = l.var();
if (!m_var2expr.get(v)) {
app* aux = m.mk_fresh_const(nullptr, m.mk_bool_sort());
m_var2expr.set(v, aux);
if (!m_gmc) m_gmc = alloc(generic_model_converter, m, "sat2goal");
m_gmc->hide(aux->get_decl());
}
VERIFY(m_var2expr.get(v));
expr_ref result(m_var2expr.get(v), m);
if (l.sign()) {
result = m.mk_not(result);
}
return result;
}
struct sat2goal::imp {
typedef mc sat_model_converter;
ast_manager & m;
expr_ref_vector m_lit2expr;
unsigned long long m_max_memory;
bool m_learned;
imp(ast_manager & _m, params_ref const & p):m(_m), m_lit2expr(m) {
updt_params(p);
}
void updt_params(params_ref const & p) {
m_learned = p.get_bool("learned", false);
m_max_memory = megabytes_to_bytes(p.get_uint("max_memory", UINT_MAX));
}
void checkpoint() {
if (!m.inc())
throw tactic_exception(m.limit().get_cancel_msg());
if (memory::get_allocation_size() > m_max_memory)
throw tactic_exception(TACTIC_MAX_MEMORY_MSG);
}
expr * lit2expr(ref<mc>& mc, sat::literal l) {
if (!m_lit2expr.get(l.index())) {
SASSERT(m_lit2expr.get((~l).index()) == 0);
expr* aux = mc ? mc->var2expr(l.var()) : nullptr;
if (!aux) {
aux = m.mk_fresh_const(nullptr, m.mk_bool_sort());
if (mc) {
mc->insert(l.var(), aux, true);
}
}
sat::literal lit(l.var(), false);
m_lit2expr.set(lit.index(), aux);
m_lit2expr.set((~lit).index(), m.mk_not(aux));
}
return m_lit2expr.get(l.index());
}
void assert_clauses(ref<mc>& mc, sat::solver_core const & s, sat::clause_vector const& clauses, goal & r, bool asserted) {
ptr_buffer<expr> lits;
unsigned small_lbd = 3; // s.get_config().m_gc_small_lbd;
for (sat::clause* cp : clauses) {
checkpoint();
lits.reset();
sat::clause const & c = *cp;
if (asserted || m_learned || c.glue() <= small_lbd) {
for (sat::literal l : c) {
lits.push_back(lit2expr(mc, l));
}
r.assert_expr(m.mk_or(lits));
}
}
}
void operator()(sat::solver_core & s, atom2bool_var const & map, goal & r, ref<mc> & mc) {
if (s.at_base_lvl() && s.inconsistent()) {
r.assert_expr(m.mk_false());
return;
}
if (r.models_enabled() && !mc) {
mc = alloc(sat_model_converter, m);
}
if (mc) mc->flush_smc(s, map);
m_lit2expr.resize(s.num_vars() * 2);
map.mk_inv(m_lit2expr);
// collect units
unsigned trail_sz = s.init_trail_size();
for (unsigned i = 0; i < trail_sz; ++i) {
checkpoint();
r.assert_expr(lit2expr(mc, s.trail_literal(i)));
}
// collect binary clauses
svector<sat::solver::bin_clause> bin_clauses;
s.collect_bin_clauses(bin_clauses, m_learned, false);
for (sat::solver::bin_clause const& bc : bin_clauses) {
checkpoint();
r.assert_expr(m.mk_or(lit2expr(mc, bc.first), lit2expr(mc, bc.second)));
}
// collect clauses
assert_clauses(mc, s, s.clauses(), r, true);
auto* ext = s.get_extension();
if (ext) {
std::function<expr_ref(sat::literal)> l2e = [&](sat::literal lit) {
return expr_ref(lit2expr(mc, lit), m);
};
expr_ref_vector fmls(m);
pb::solver* ba = dynamic_cast<pb::solver*>(ext);
if (ba) {
ba->to_formulas(l2e, fmls);
}
else
dynamic_cast<euf::solver*>(ext)->to_formulas(l2e, fmls);
for (expr* f : fmls)
r.assert_expr(f);
}
}
void add_clause(ref<mc>& mc, sat::literal_vector const& lits, expr_ref_vector& lemmas) {
expr_ref_vector lemma(m);
for (sat::literal l : lits) {
expr* e = lit2expr(mc, l);
if (!e) return;
lemma.push_back(e);
}
lemmas.push_back(mk_or(lemma));
}
void add_clause(ref<mc>& mc, sat::clause const& c, expr_ref_vector& lemmas) {
expr_ref_vector lemma(m);
for (sat::literal l : c) {
expr* e = lit2expr(mc, l);
if (!e) return;
lemma.push_back(e);
}
lemmas.push_back(mk_or(lemma));
}
};
sat2goal::sat2goal():m_imp(nullptr) {
}
void sat2goal::collect_param_descrs(param_descrs & r) {
insert_max_memory(r);
r.insert("learned", CPK_BOOL, "(default: false) collect also learned clauses.");
}
struct sat2goal::scoped_set_imp {
sat2goal * m_owner;
scoped_set_imp(sat2goal * o, sat2goal::imp * i):m_owner(o) {
m_owner->m_imp = i;
}
~scoped_set_imp() {
m_owner->m_imp = nullptr;
}
};
void sat2goal::operator()(sat::solver_core & t, atom2bool_var const & m, params_ref const & p,
goal & g, ref<mc> & mc) {
imp proc(g.m(), p);
scoped_set_imp set(this, &proc);
proc(t, m, g, mc);
}