mirror of
https://github.com/Z3Prover/z3
synced 2025-04-12 20:18:18 +00:00
441 lines
15 KiB
C++
441 lines
15 KiB
C++
/*++
|
|
Copyright (c) 2020 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
sat_anf_simplifier.cpp
|
|
|
|
Abstract:
|
|
|
|
Simplification based on ANF format.
|
|
|
|
Author:
|
|
|
|
Nikolaj Bjorner 2020-01-02
|
|
|
|
--*/
|
|
|
|
#include "util/union_find.h"
|
|
#include "sat/sat_anf_simplifier.h"
|
|
#include "sat/sat_solver.h"
|
|
#include "sat/sat_elim_eqs.h"
|
|
#include "sat/sat_xor_finder.h"
|
|
#include "sat/sat_aig_finder.h"
|
|
#include "math/grobner/pdd_solver.h"
|
|
|
|
namespace sat {
|
|
|
|
struct anf_simplifier::report {
|
|
anf_simplifier& s;
|
|
stopwatch m_watch;
|
|
report(anf_simplifier& s): s(s) { m_watch.start(); }
|
|
~report() {
|
|
m_watch.stop();
|
|
IF_VERBOSE(2,
|
|
verbose_stream() << " (sat.anf.simplifier"
|
|
<< " :num-units " << s.m_stats.m_num_units
|
|
<< " :num-eqs " << s.m_stats.m_num_eqs
|
|
<< " :mb " << mem_stat()
|
|
<< m_watch << ")\n");
|
|
}
|
|
};
|
|
|
|
void anf_simplifier::operator()() {
|
|
dd::pdd_manager m(20, dd::pdd_manager::semantics::mod2_e);
|
|
pdd_solver solver(s.rlimit(), m);
|
|
report _report(*this);
|
|
configure_solver(solver);
|
|
clauses2anf(solver);
|
|
TRACE("anf_simplifier", solver.display(tout); s.display(tout););
|
|
solver.simplify();
|
|
TRACE("anf_simplifier", solver.display(tout););
|
|
anf2clauses(solver);
|
|
anf2phase(solver);
|
|
save_statistics(solver);
|
|
IF_VERBOSE(10, m_st.display(verbose_stream() << "(sat.anf.simplifier\n"); verbose_stream() << ")\n");
|
|
}
|
|
|
|
/**
|
|
\brief extract learned units and equivalences from processed anf.
|
|
|
|
TBD: could learn binary clauses
|
|
TBD: could try simplify equations using BIG subsumption similar to asymm_branch
|
|
*/
|
|
void anf_simplifier::anf2clauses(pdd_solver& solver) {
|
|
|
|
union_find_default_ctx ctx;
|
|
union_find<> uf(ctx);
|
|
for (unsigned i = 2*s.num_vars(); i--> 0; ) uf.mk_var();
|
|
auto add_eq = [&](literal l1, literal l2) {
|
|
uf.merge(l1.index(), l2.index());
|
|
uf.merge((~l1).index(), (~l2).index());
|
|
};
|
|
|
|
unsigned old_num_eqs = m_stats.m_num_eqs;
|
|
for (auto* e : solver.equations()) {
|
|
auto const& p = e->poly();
|
|
if (p.is_one()) {
|
|
s.set_conflict();
|
|
break;
|
|
}
|
|
else if (p.is_unary()) {
|
|
// unit
|
|
SASSERT(!p.is_val() && p.lo().is_val() && p.hi().is_val());
|
|
literal lit(p.var(), p.lo().is_zero());
|
|
s.assign_unit(lit);
|
|
++m_stats.m_num_units;
|
|
TRACE("anf_simplifier", tout << "unit " << p << " : " << lit << "\n";);
|
|
}
|
|
else if (p.is_binary()) {
|
|
// equivalence
|
|
// x + y + c = 0
|
|
SASSERT(!p.is_val() && p.hi().is_one() && !p.lo().is_val() && p.lo().hi().is_one() && p.lo().lo().is_val());
|
|
literal x(p.var(), false);
|
|
literal y(p.lo().var(), p.lo().lo().is_one());
|
|
add_eq(x, y);
|
|
++m_stats.m_num_eqs;
|
|
TRACE("anf_simplifier", tout << "equivalence " << p << " : " << x << " == " << y << "\n";);
|
|
}
|
|
}
|
|
|
|
if (old_num_eqs < m_stats.m_num_eqs) {
|
|
elim_eqs elim(s);
|
|
elim(uf);
|
|
}
|
|
}
|
|
|
|
/**
|
|
\brief update best phase using solved equations
|
|
polynomials that are not satisfied evaluate to true.
|
|
In a satisfying assignment, all polynomials should evaluate to false.
|
|
assume that solutions are provided in reverse order.
|
|
|
|
As a simplifying assumption it relies on the property
|
|
that if an equation is of the form v + p, where v does not occur in p,
|
|
then all equations that come after it do not contain p.
|
|
In this way we can flip the assignment to v without
|
|
invalidating the evaluation cache.
|
|
*/
|
|
void anf_simplifier::anf2phase(pdd_solver& solver) {
|
|
if (!m_config.m_anf2phase)
|
|
return;
|
|
reset_eval();
|
|
auto const& eqs = solver.equations();
|
|
for (unsigned i = eqs.size(); i-- > 0; ) {
|
|
dd::pdd const& p = eqs[i]->poly();
|
|
if (!p.is_val() && p.hi().is_one() && s.m_best_phase[p.var()] != eval(p.lo())) {
|
|
s.m_best_phase[p.var()] = !s.m_best_phase[p.var()];
|
|
++m_stats.m_num_phase_flips;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool anf_simplifier::eval(dd::pdd const& p) {
|
|
if (p.is_one()) return true;
|
|
if (p.is_zero()) return false;
|
|
unsigned index = p.index();
|
|
if (index < m_eval_cache.size()) {
|
|
if (m_eval_cache[index] == m_eval_ts) return false;
|
|
if (m_eval_cache[index] == m_eval_ts + 1) return true;
|
|
}
|
|
SASSERT(!p.is_val());
|
|
bool hi = eval(p.hi());
|
|
bool lo = eval(p.lo());
|
|
bool v = (hi && s.m_best_phase[p.var()]) ^ lo;
|
|
m_eval_cache.reserve(index + 1, 0);
|
|
m_eval_cache[index] = m_eval_ts + v;
|
|
return v;
|
|
}
|
|
|
|
void anf_simplifier::reset_eval() {
|
|
if (m_eval_ts + 2 < m_eval_ts) {
|
|
m_eval_cache.reset();
|
|
m_eval_ts = 0;
|
|
}
|
|
m_eval_ts += 2;
|
|
}
|
|
|
|
void anf_simplifier::clauses2anf(pdd_solver& solver) {
|
|
svector<solver::bin_clause> bins;
|
|
m_relevant.reset();
|
|
m_relevant.resize(s.num_vars(), false);
|
|
clause_vector clauses(s.clauses());
|
|
s.collect_bin_clauses(bins, false, false);
|
|
collect_clauses(clauses, bins);
|
|
try {
|
|
compile_xors(clauses, solver);
|
|
compile_aigs(clauses, bins, solver);
|
|
|
|
for (auto const& b : bins) {
|
|
add_bin(b, solver);
|
|
}
|
|
for (clause* cp : clauses) {
|
|
add_clause(*cp, solver);
|
|
}
|
|
}
|
|
catch (dd::pdd_manager::mem_out) {
|
|
IF_VERBOSE(1, verbose_stream() << "(sat.anf memout)\n");
|
|
}
|
|
}
|
|
|
|
void anf_simplifier::collect_clauses(clause_vector & clauses, svector<solver::bin_clause>& bins) {
|
|
clause_vector oclauses;
|
|
svector<solver::bin_clause> obins;
|
|
|
|
unsigned j = 0;
|
|
for (clause* cp : clauses) {
|
|
clause const& c = *cp;
|
|
if (is_too_large(c))
|
|
continue;
|
|
else if (is_pre_satisfied(c)) {
|
|
oclauses.push_back(cp);
|
|
}
|
|
else {
|
|
clauses[j++] = cp;
|
|
}
|
|
}
|
|
clauses.shrink(j);
|
|
|
|
j = 0;
|
|
for (auto const& b : bins) {
|
|
if (is_pre_satisfied(b)) {
|
|
obins.push_back(b);
|
|
}
|
|
else {
|
|
bins[j++] = b;
|
|
}
|
|
}
|
|
bins.shrink(j);
|
|
|
|
unsigned rounds = 0, max_rounds = 3;
|
|
bool added = true;
|
|
while (bins.size() + clauses.size() < m_config.m_max_clauses &&
|
|
(!obins.empty() || !oclauses.empty()) &&
|
|
added &&
|
|
rounds < max_rounds) {
|
|
|
|
added = false;
|
|
for (auto const& b : bins) set_relevant(b);
|
|
for (clause* cp : clauses) set_relevant(*cp);
|
|
|
|
j = 0;
|
|
for (auto const& b : obins) {
|
|
if (has_relevant_var(b)) {
|
|
added = true;
|
|
bins.push_back(b);
|
|
}
|
|
else {
|
|
obins[j++] = b;
|
|
}
|
|
}
|
|
obins.shrink(j);
|
|
|
|
if (bins.size() + clauses.size() >= m_config.m_max_clauses) {
|
|
break;
|
|
}
|
|
|
|
j = 0;
|
|
for (clause* cp : oclauses) {
|
|
if (has_relevant_var(*cp)) {
|
|
added = true;
|
|
clauses.push_back(cp);
|
|
}
|
|
else {
|
|
oclauses[j++] = cp;
|
|
}
|
|
}
|
|
oclauses.shrink(j);
|
|
}
|
|
|
|
TRACE("anf_simplifier",
|
|
tout << "kept:\n";
|
|
for (clause* cp : clauses) tout << *cp << "\n";
|
|
for (auto b : bins) tout << b.first << " " << b.second << "\n";
|
|
tout << "removed:\n";
|
|
for (clause* cp : oclauses) tout << *cp << "\n";
|
|
for (auto b : obins) tout << b.first << " " << b.second << "\n";);
|
|
}
|
|
|
|
void anf_simplifier::set_relevant(solver::bin_clause const& b) {
|
|
set_relevant(b.first);
|
|
set_relevant(b.second);
|
|
}
|
|
|
|
void anf_simplifier::set_relevant(clause const& c) {
|
|
for (literal l : c) set_relevant(l);
|
|
}
|
|
|
|
bool anf_simplifier::is_pre_satisfied(clause const& c) {
|
|
for (literal l : c) if (phase_is_true(l)) return true;
|
|
return false;
|
|
}
|
|
|
|
bool anf_simplifier::is_pre_satisfied(solver::bin_clause const& b) {
|
|
return phase_is_true(b.first) || phase_is_true(b.second);
|
|
}
|
|
|
|
bool anf_simplifier::phase_is_true(literal l) {
|
|
bool ph = (s.m_best_phase_size > 0) ? s.m_best_phase[l.var()] : s.m_phase[l.var()];
|
|
return l.sign() ? !ph : ph;
|
|
}
|
|
|
|
bool anf_simplifier::has_relevant_var(clause const& c) {
|
|
for (literal l : c) if (is_relevant(l)) return true;
|
|
return false;
|
|
}
|
|
|
|
bool anf_simplifier::has_relevant_var(solver::bin_clause const& b) {
|
|
return is_relevant(b.first) || is_relevant(b.second);
|
|
}
|
|
|
|
/**
|
|
\brief extract xors from all s.clauses()
|
|
(could be just filtered clauses, or clauses with relevant variables).
|
|
Add the extracted xors to pdd_solver.
|
|
Remove clauses from list that correspond to extracted xors
|
|
*/
|
|
void anf_simplifier::compile_xors(clause_vector& clauses, pdd_solver& ps) {
|
|
if (!m_config.m_compile_xor) {
|
|
return;
|
|
}
|
|
std::function<void(literal_vector const&)> f =
|
|
[&,this](literal_vector const& x) {
|
|
add_xor(x, ps);
|
|
m_stats.m_num_xors++;
|
|
};
|
|
xor_finder xf(s);
|
|
xf.set(f);
|
|
xf(clauses);
|
|
}
|
|
|
|
static solver::bin_clause normalize(solver::bin_clause const& b) {
|
|
if (b.first.index() > b.second.index()) {
|
|
return solver::bin_clause(b.second, b.first);
|
|
}
|
|
else {
|
|
return b;
|
|
}
|
|
}
|
|
/**
|
|
\brief extract AIGs from clauses.
|
|
Add the extracted AIGs to pdd_solver.
|
|
Remove clauses from list that correspond to extracted AIGs
|
|
Remove binary clauses that correspond to extracted AIGs.
|
|
*/
|
|
void anf_simplifier::compile_aigs(clause_vector& clauses, svector<solver::bin_clause>& bins, pdd_solver& ps) {
|
|
if (!m_config.m_compile_aig) {
|
|
return;
|
|
}
|
|
hashtable<solver::bin_clause, solver::bin_clause_hash, default_eq<solver::bin_clause>> seen_bin;
|
|
|
|
std::function<void(literal head, literal_vector const& tail)> on_aig =
|
|
[&,this](literal head, literal_vector const& tail) {
|
|
add_aig(head, tail, ps);
|
|
for (literal l : tail) {
|
|
seen_bin.insert(normalize(solver::bin_clause(~l, head)));
|
|
}
|
|
m_stats.m_num_aigs++;
|
|
};
|
|
std::function<void(literal head, literal c, literal th, literal el)> on_if =
|
|
[&,this](literal head, literal c, literal th, literal el) {
|
|
add_if(head, c, th, el, ps);
|
|
m_stats.m_num_ifs++;
|
|
};
|
|
aig_finder af(s);
|
|
af.set(on_aig);
|
|
af.set(on_if);
|
|
af(clauses);
|
|
|
|
std::function<bool(solver::bin_clause b)> not_seen =
|
|
[&](solver::bin_clause b) { return !seen_bin.contains(normalize(b)); };
|
|
bins.filter_update(not_seen);
|
|
}
|
|
|
|
/**
|
|
assign levels to variables.
|
|
use variable id as a primary source for the level of a variable.
|
|
secondarily, sort variables randomly (each variable is assigned
|
|
a random, unique, id).
|
|
*/
|
|
void anf_simplifier::configure_solver(pdd_solver& ps) {
|
|
unsigned nv = s.num_vars();
|
|
unsigned_vector l2v(nv), var2id(nv), id2var(nv);
|
|
svector<std::pair<unsigned, unsigned>> vl(nv);
|
|
|
|
for (unsigned i = 0; i < nv; ++i) var2id[i] = i;
|
|
shuffle(var2id.size(), var2id.data(), s.rand());
|
|
for (unsigned i = 0; i < nv; ++i) id2var[var2id[i]] = i;
|
|
for (unsigned i = 0; i < nv; ++i) vl[i] = std::make_pair(i, var2id[i]);
|
|
std::sort(vl.begin(), vl.end());
|
|
for (unsigned i = 0; i < nv; ++i) l2v[i] = id2var[vl[i].second];
|
|
|
|
ps.get_manager().reset(l2v);
|
|
|
|
// set configuration parameters.
|
|
dd::solver::config cfg;
|
|
cfg.m_expr_size_limit = 1000;
|
|
cfg.m_max_steps = 1000;
|
|
cfg.m_random_seed = s.rand()();
|
|
cfg.m_enable_exlin = m_config.m_enable_exlin;
|
|
|
|
unsigned max_num_nodes = 1 << 18;
|
|
ps.get_manager().set_max_num_nodes(max_num_nodes);
|
|
ps.set(cfg);
|
|
}
|
|
|
|
#define lit2pdd(_l_) (_l_.sign() ? ~m.mk_var(_l_.var()) : m.mk_var(_l_.var()))
|
|
|
|
void anf_simplifier::add_bin(solver::bin_clause const& b, pdd_solver& ps) {
|
|
auto& m = ps.get_manager();
|
|
dd::pdd p = (lit2pdd(b.first) | lit2pdd(b.second)) ^ true;
|
|
ps.add(p);
|
|
TRACE("anf_simplifier", tout << "bin: " << b.first << " " << b.second << " : " << p << "\n";);
|
|
}
|
|
|
|
void anf_simplifier::add_clause(clause const& c, pdd_solver& ps) {
|
|
if (c.size() > m_config.m_max_clause_size) return;
|
|
auto& m = ps.get_manager();
|
|
dd::pdd p = m.zero();
|
|
for (literal l : c) p |= lit2pdd(l);
|
|
p = p ^ true;
|
|
ps.add(p);
|
|
TRACE("anf_simplifier", tout << "clause: " << c << " : " << p << "\n";);
|
|
}
|
|
|
|
void anf_simplifier::add_xor(literal_vector const& x, pdd_solver& ps) {
|
|
auto& m = ps.get_manager();
|
|
dd::pdd p = m.one();
|
|
for (literal l : x) p ^= lit2pdd(l);
|
|
ps.add(p);
|
|
TRACE("anf_simplifier", tout << "xor: " << x << " : " << p << "\n";);
|
|
}
|
|
|
|
void anf_simplifier::add_aig(literal head, literal_vector const& ands, pdd_solver& ps) {
|
|
auto& m = ps.get_manager();
|
|
dd::pdd q = m.one();
|
|
for (literal l : ands) q &= lit2pdd(l);
|
|
dd::pdd p = lit2pdd(head) ^ q;
|
|
ps.add(p);
|
|
TRACE("anf_simplifier", tout << "aig: " << head << " == " << ands << " poly : " << p << "\n";);
|
|
}
|
|
|
|
void anf_simplifier::add_if(literal head, literal c, literal th, literal el, pdd_solver& ps) {
|
|
auto& m = ps.get_manager();
|
|
dd::pdd cond = lit2pdd(c);
|
|
dd::pdd p = lit2pdd(head) ^ (cond & lit2pdd(th)) ^ (~cond & lit2pdd(el));
|
|
ps.add(p);
|
|
TRACE("anf_simplifier", tout << "ite: " << head << " == " << c << "?" << th << ":" << el << " poly : " << p << "\n";);
|
|
}
|
|
|
|
void anf_simplifier::save_statistics(pdd_solver& solver) {
|
|
solver.collect_statistics(m_st);
|
|
m_st.update("sat-anf.units", m_stats.m_num_units);
|
|
m_st.update("sat-anf.eqs", m_stats.m_num_eqs);
|
|
m_st.update("sat-anf.ands", m_stats.m_num_aigs);
|
|
m_st.update("sat-anf.ites", m_stats.m_num_ifs);
|
|
m_st.update("sat-anf.xors", m_stats.m_num_xors);
|
|
m_st.update("sat-anf.phase_flips", m_stats.m_num_phase_flips);
|
|
}
|
|
|
|
}
|