3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-04-13 12:28:44 +00:00
z3/src/math/grobner/pdd_solver.cpp
Nikolaj Bjorner 316ed778e0 Tune Grobner equations
\brief convert p == 0 into a solved form v == r, such that
   v has bounds [lo, oo) iff r has bounds [lo', oo)
   v has bounds (oo,hi]  iff r has bounds (oo,hi']

   The solved form allows the Grobner solver identify more bounds conflicts.
   A bad leading term can miss bounds conflicts.
   For example for x + y + z == 0 where x, y : [0, oo) and z : (oo,0]
   we prefer to solve z == -x - y instead of x == -z - y
   because the solution -z - y has neither an upper, nor a lower bound.

The Grobner solver is augmented with a notion of a substitution that is applied before the solver is run.
2022-07-11 16:14:26 -07:00

550 lines
18 KiB
C++

/*++
Copyright (c) 2019 Microsoft Corporation
Abstract:
Solver core based on pdd representation of polynomials
Author:
Nikolaj Bjorner (nbjorner)
Lev Nachmanson (levnach)
--*/
#include "util/uint_set.h"
#include "math/grobner/pdd_solver.h"
#include "math/grobner/pdd_simplifier.h"
#include <math.h>
namespace dd {
/***
A simple algorithm maintains two sets (S, A),
where S is m_processed, and A is m_to_simplify.
Initially S is empty and A contains the initial equations.
Each step proceeds as follows:
- pick a in A, and remove a from A
- simplify a using S
- simplify S using a
- for s in S:
b = superpose(a, s)
add b to A
- add a to S
- simplify A using a
Alternate:
- Fix a variable ordering x1 > x2 > x3 > ....
In each step:
- pick a in A with *highest* variable x_i in leading term of *lowest* degree.
- simplify a using S
- simplify S using a
- if a does not contains x_i, put it back into A and pick again (determine whether possible)
- for s in S:
b = superpose(a, s)
add b to A
- add a to S
- simplify A using a
- the variable ordering should be chosen from a candidate model M,
in a way that is compatible with weights that draw on the number of occurrences
in polynomials with violated evaluations and with the maximal slack (degree of freedom).
weight(x) := < count { p in P | x in p, M(p) != 0 }, min_{p in P | x in p} slack(p,x) >
slack is computed from interval assignments to variables, and is an interval in which x can possibly move
(an over-approximation).
*/
solver::solver(reslimit& lim, pdd_manager& m) :
m(m),
m_limit(lim),
m_conflict(nullptr)
{}
solver::~solver() {
reset();
}
void solver::adjust_cfg() {
auto & cfg = m_config;
IF_VERBOSE(3, verbose_stream() << "start saturate\n"; display_statistics(verbose_stream()));
cfg.m_eqs_threshold = static_cast<unsigned>(cfg.m_eqs_growth * ceil(log(1 + m_to_simplify.size()))* m_to_simplify.size());
cfg.m_expr_size_limit = 0;
cfg.m_expr_degree_limit = 0;
for (equation* e: m_to_simplify) {
cfg.m_expr_size_limit = std::max(cfg.m_expr_size_limit, (unsigned)e->poly().tree_size());
cfg.m_expr_degree_limit = std::max(cfg.m_expr_degree_limit, e->poly().degree());
}
cfg.m_expr_size_limit *= cfg.m_expr_size_growth;
cfg.m_expr_degree_limit *= cfg.m_expr_degree_growth;;
IF_VERBOSE(3, verbose_stream() << "set m_config.m_eqs_threshold " << m_config.m_eqs_threshold << "\n";
verbose_stream() << "set m_config.m_expr_size_limit to " << m_config.m_expr_size_limit << "\n";
verbose_stream() << "set m_config.m_expr_degree_limit to " << m_config.m_expr_degree_limit << "\n";
);
}
void solver::saturate() {
simplify();
if (done()) {
return;
}
init_saturate();
TRACE("dd.solver", display(tout););
try {
while (!done() && step()) {
TRACE("dd.solver", display(tout););
DEBUG_CODE(invariant(););
IF_VERBOSE(3, display_statistics(verbose_stream()));
}
DEBUG_CODE(invariant(););
}
catch (pdd_manager::mem_out) {
IF_VERBOSE(2, verbose_stream() << "mem-out\n");
// don't reduce further
}
}
void solver::scoped_process::done() {
pdd p = e->poly();
SASSERT(!p.is_val());
if (p.degree() == 1) {
g.push_equation(solved, e);
}
else {
g.push_equation(processed, e);
}
e = nullptr;
}
solver::scoped_process::~scoped_process() {
if (e) {
pdd p = e->poly();
SASSERT(!p.is_val());
g.push_equation(processed, e);
}
}
void solver::simplify() {
simplifier s(*this);
s();
}
void solver::superpose(equation const & eq) {
for (equation* target : m_processed) {
superpose(eq, *target);
}
}
/*
Use a set of equations to simplify eq
*/
void solver::simplify_using(equation& eq, equation_vector const& eqs) {
bool simplified, changed_leading_term;
do {
simplified = false;
for (equation* p : eqs) {
if (try_simplify_using(eq, *p, changed_leading_term)) {
simplified = true;
}
if (canceled() || eq.poly().is_val()) {
break;
}
}
}
while (simplified && !eq.poly().is_val());
if (eq.poly().is_unary() && eq.poly().hi().val() < 0)
eq = -eq.poly();
TRACE("dd.solver", display(tout << "simplification result: ", eq););
}
/*
Use the given equation to simplify equations in set
*/
void solver::simplify_using(equation_vector& set, equation const& eq) {
struct scoped_update {
equation_vector& set;
unsigned i, j, sz;
scoped_update(equation_vector& set): set(set), i(0), j(0), sz(set.size()) {}
void nextj() {
set[j] = set[i];
set[i]->set_index(j++);
}
~scoped_update() {
for (; i < sz; ++i) {
nextj();
}
set.shrink(j);
}
};
scoped_update sr(set);
for (; sr.i < sr.sz; ++sr.i) {
equation& target = *set[sr.i];
bool changed_leading_term = false;
bool simplified = true;
simplified = !done() && try_simplify_using(target, eq, changed_leading_term);
if (simplified && is_trivial(target)) {
retire(&target);
}
else if (simplified && check_conflict(target)) {
// pushed to solved
}
else if (simplified && changed_leading_term) {
SASSERT(target.state() == processed);
push_equation(to_simplify, target);
if (!m_var2level.empty()) {
m_levelp1 = std::max(m_var2level[target.poly().var()]+1, m_levelp1);
}
}
else {
sr.nextj();
}
}
}
/*
simplify target using source.
return true if the target was simplified.
set changed_leading_term if the target is in the m_processed set and the leading term changed.
*/
bool solver::try_simplify_using(equation& dst, equation const& src, bool& changed_leading_term) {
if (&src == &dst) {
return false;
}
m_stats.incr_simplified();
pdd t = src.poly();
pdd r = dst.poly().reduce(t);
if (r == dst.poly()){
return false;
}
if (is_too_complex(r)) {
m_too_complex = true;
return false;
}
TRACE("dd.solver",
tout << "reduce: " << dst.poly() << "\n";
tout << "using: " << t << "\n";
tout << "to: " << r << "\n";);
changed_leading_term = dst.state() == processed && m.different_leading_term(r, dst.poly());
dst = r;
dst = m_dep_manager.mk_join(dst.dep(), src.dep());
update_stats_max_degree_and_size(dst);
return true;
}
void solver::simplify_using(equation & dst, equation const& src, bool& changed_leading_term) {
if (&src == &dst) return;
m_stats.incr_simplified();
pdd t = src.poly();
pdd r = dst.poly().reduce(t);
changed_leading_term = dst.state() == processed && m.different_leading_term(r, dst.poly());
TRACE("dd.solver",
tout << "reduce: " << dst.poly() << "\n";
tout << "using: " << t << "\n";
tout << "to: " << r << "\n";);
if (r == dst.poly()) return;
dst = r;
dst = m_dep_manager.mk_join(dst.dep(), src.dep());
update_stats_max_degree_and_size(dst);
}
/*
let eq1: ab+q=0, and eq2: ac+e=0, then qc - eb = 0
*/
void solver::superpose(equation const& eq1, equation const& eq2) {
TRACE("dd.solver_d", display(tout << "eq1=", eq1); display(tout << "eq2=", eq2););
pdd r(m);
if (m.try_spoly(eq1.poly(), eq2.poly(), r) && !r.is_zero()) {
if (is_too_complex(r)) {
m_too_complex = true;
}
else {
m_stats.m_superposed++;
add(r, m_dep_manager.mk_join(eq1.dep(), eq2.dep()));
}
}
}
bool solver::step() {
m_stats.m_compute_steps++;
IF_VERBOSE(3, if (m_stats.m_compute_steps % 100 == 0) verbose_stream() << "compute steps = " << m_stats.m_compute_steps << "\n";);
equation* e = pick_next();
if (!e) return false;
scoped_process sd(*this, e);
equation& eq = *e;
SASSERT(eq.state() == to_simplify);
simplify_using(eq, m_processed);
if (is_trivial(eq)) { sd.e = nullptr; retire(e); return true; }
if (check_conflict(eq)) { sd.e = nullptr; return false; }
m_too_complex = false;
simplify_using(m_processed, eq);
if (done()) return false;
TRACE("dd.solver", display(tout << "eq = ", eq););
superpose(eq);
simplify_using(m_to_simplify, eq);
if (done()) return false;
if (!m_too_complex) sd.done();
return true;
}
void solver::init_saturate() {
unsigned_vector const& l2v = m.get_level2var();
m_level2var.resize(l2v.size());
m_var2level.resize(l2v.size());
for (unsigned i = 0; i < l2v.size(); ++i) {
m_level2var[i] = l2v[i];
m_var2level[l2v[i]] = i;
}
m_levelp1 = m_level2var.size();
}
solver::equation* solver::pick_next() {
while (m_levelp1 > 0) {
unsigned v = m_level2var[m_levelp1-1];
equation* eq = nullptr;
for (equation* curr : m_to_simplify) {
SASSERT(curr->idx() != UINT_MAX);
pdd const& p = curr->poly();
if (curr->state() == to_simplify && p.var() == v) {
if (!eq || is_simpler(*curr, *eq))
eq = curr;
}
}
if (eq) {
pop_equation(eq);
return eq;
}
--m_levelp1;
}
return nullptr;
}
solver::equation_vector const& solver::equations() {
m_all_eqs.reset();
for (equation* eq : m_solved) m_all_eqs.push_back(eq);
for (equation* eq : m_to_simplify) m_all_eqs.push_back(eq);
for (equation* eq : m_processed) m_all_eqs.push_back(eq);
return m_all_eqs;
}
void solver::reset() {
for (equation* e : m_solved) dealloc(e);
for (equation* e : m_to_simplify) dealloc(e);
for (equation* e : m_processed) dealloc(e);
m_subst.reset();
m_solved.reset();
m_processed.reset();
m_to_simplify.reset();
m_stats.reset();
m_level2var.reset();
m_var2level.reset();
m_conflict = nullptr;
}
void solver::add(pdd const& p, u_dependency * dep) {
if (p.is_zero()) return;
equation * eq = alloc(equation, p, dep);
if (check_conflict(*eq))
return;
push_equation(to_simplify, eq);
if (!m_var2level.empty())
m_levelp1 = std::max(m_var2level[p.var()]+1, m_levelp1);
update_stats_max_degree_and_size(*eq);
}
void solver::add_subst(unsigned v, pdd const& p, u_dependency* dep) {
SASSERT(m_processed.empty());
SASSERT(m_solved.empty());
m_subst.push_back({v, p, dep});
for (auto* e : m_to_simplify) {
auto r = e->poly().subst_pdd(v, p);
if (r == e->poly())
continue;
*e = m_dep_manager.mk_join(dep, e->dep());
*e = r;
}
}
void solver::simplify(pdd& p, u_dependency*& d) {
for (auto const& [v, q, d2] : m_subst) {
pdd r = p.subst_pdd(v, q);
if (r != p) {
p = r;
d = m_dep_manager.mk_join(d, d2);
}
}
}
bool solver::canceled() {
return m_limit.is_canceled();
}
bool solver::done() {
return
m_to_simplify.size() + m_processed.size() >= m_config.m_eqs_threshold ||
m_stats.simplified() >= m_config.m_max_simplified ||
canceled() ||
m_stats.m_compute_steps > m_config.m_max_steps ||
m_conflict != nullptr;
}
solver::equation_vector& solver::get_queue(equation const& eq) {
switch (eq.state()) {
case processed: return m_processed;
case to_simplify: return m_to_simplify;
case solved: return m_solved;
}
UNREACHABLE();
return m_to_simplify;
}
void solver::del_equation(equation* eq) {
pop_equation(eq);
retire(eq);
}
void solver::retire(equation* eq) {
#if 0
// way to check if retired equations are ever accessed.
eq->set_index(UINT_MAX);
#else
dealloc(eq);
#endif
}
void solver::pop_equation(equation& eq) {
equation_vector& v = get_queue(eq);
unsigned idx = eq.idx();
if (idx != v.size() - 1) {
equation* eq2 = v.back();
eq2->set_index(idx);
v[idx] = eq2;
}
v.pop_back();
}
void solver::push_equation(eq_state st, equation& eq) {
SASSERT(st != to_simplify || !eq.poly().is_val());
SASSERT(st != processed || !eq.poly().is_val());
eq.set_state(st);
equation_vector& v = get_queue(eq);
eq.set_index(v.size());
v.push_back(&eq);
}
void solver::update_stats_max_degree_and_size(const equation& e) {
m_stats.m_max_expr_size = std::max(m_stats.m_max_expr_size, e.poly().tree_size());
m_stats.m_max_expr_degree = std::max(m_stats.m_max_expr_degree, e.poly().degree());
}
void solver::collect_statistics(statistics& st) const {
st.update("dd.solver.steps", m_stats.m_compute_steps);
st.update("dd.solver.simplified", m_stats.simplified());
st.update("dd.solver.superposed", m_stats.m_superposed);
st.update("dd.solver.processed", m_processed.size());
st.update("dd.solver.solved", m_solved.size());
st.update("dd.solver.to_simplify", m_to_simplify.size());
st.update("dd.solver.degree", m_stats.m_max_expr_degree);
st.update("dd.solver.size", m_stats.m_max_expr_size);
}
std::ostream& solver::display(std::ostream & out, const equation & eq) const {
out << eq.poly() << "\n";
if (m_print_dep) m_print_dep(eq.dep(), out);
return out;
}
std::ostream& solver::display(std::ostream& out) const {
if (!m_solved.empty()) {
out << "solved\n"; for (auto e : m_solved) display(out, *e);
}
if (!m_processed.empty()) {
out << "processed\n"; for (auto e : m_processed) display(out, *e);
}
if (!m_to_simplify.empty()) {
out << "to_simplify\n"; for (auto e : m_to_simplify) display(out, *e);
}
if (!m_subst.empty()) {
out << "subst\n";
for (auto const& [v, p, d] : m_subst) {
out << "v" << v << " := " << p;
if (m_print_dep)
m_print_dep(d, out);
out << "\n";
}
}
return display_statistics(out);
}
std::ostream& solver::display_statistics(std::ostream& out) const {
statistics st;
collect_statistics(st);
st.display(out);
out << "\n----\n";
return out;
}
void solver::invariant() const {
return; // disabling the check that seem incorrect after changing in the order of pdd expressions
// equations in processed have correct indices
// they are labled as processed
unsigned i = 0;
for (auto* e : m_processed) {
VERIFY(e->state() == processed);
VERIFY(e->idx() == i);
VERIFY(!e->poly().is_val());
++i;
}
i = 0;
uint_set head_vars;
for (auto* e : m_solved) {
VERIFY(e->state() == solved);
VERIFY(e->idx() == i);
++i;
pdd p = e->poly();
if (p.degree() == 1) {
unsigned v = p.var();
SASSERT(!head_vars.contains(v));
head_vars.insert(v);
}
}
if (!head_vars.empty()) {
for (auto * e : m_to_simplify) {
for (auto v : m.free_vars(e->poly())) VERIFY(!head_vars.contains(v));
}
for (auto * e : m_processed) {
for (auto v : m.free_vars(e->poly())) VERIFY(!head_vars.contains(v));
}
}
// equations in to_simplify have correct indices
// they are labeled as non-processed
i = 0;
for (auto* e : m_to_simplify) {
VERIFY(e->idx() == i);
VERIFY(e->state() == to_simplify);
pdd const& p = e->poly();
VERIFY(!p.is_val());
++i;
}
}
}