mirror of
https://github.com/Z3Prover/z3
synced 2026-08-02 12:13:25 +00:00
### What it does
Fixes an inverted precondition assertion in `grobner::pop_scope`
(`src/math/grobner/grobner.cpp`).
```cpp
void grobner::pop_scope(unsigned num_scopes) {
SASSERT(num_scopes >= get_scope_level()); // was reversed
unsigned new_lvl = get_scope_level() - num_scopes; // requires num_scopes <= level
...
m_scopes.shrink(new_lvl);
}
```
`get_scope_level()` returns `m_scopes.size()` (grobner.h). The body
computes
`new_lvl = get_scope_level() - num_scopes` and calls
`m_scopes.shrink(new_lvl)`,
which is only well-defined when `num_scopes <= get_scope_level()`. The
assertion
stated the opposite (`>=`), so in debug builds it would fire on any
valid partial
pop (e.g. popping one of several scopes) while failing to catch the
unsigned
underflow it was meant to guard against. Changed to `<=`.
### Evidence
- `get_scope_level() const { return m_scopes.size(); }`
(`src/math/grobner/grobner.h`).
- Compiled the translation unit with MSVC and `Z3DEBUG` defined (so the
`SASSERT`
is active): builds cleanly.
### What we did not verify
This is a debug-only (`SASSERT`) correctness fix and has no effect on
release
builds. No behavioral test was added.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ad2c295d-7523-48cf-b786-b435b833e3af
958 lines
30 KiB
C++
958 lines
30 KiB
C++
/*++
|
|
Copyright (c) 2006 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
grobner.cpp
|
|
|
|
Abstract:
|
|
|
|
<abstract>
|
|
|
|
Author:
|
|
|
|
Leonardo de Moura (leonardo) 2008-12-04.
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
#include "math/grobner/grobner.h"
|
|
#include "ast/ast_pp.h"
|
|
#include "ast/ast_ll_pp.h"
|
|
#include "util/ref_util.h"
|
|
|
|
// #define PROFILE_GB
|
|
|
|
grobner::grobner(ast_manager & m, v_dependency_manager & d):
|
|
m_manager(m),
|
|
m_dep_manager(d),
|
|
m_util(m),
|
|
m_var_lt(m_var2weight),
|
|
m_monomial_lt(m_var_lt),
|
|
m_changed_leading_term(false),
|
|
m_unsat(nullptr) {
|
|
}
|
|
|
|
grobner::~grobner() {
|
|
flush();
|
|
}
|
|
|
|
void grobner::flush() {
|
|
dec_ref_map_keys(m_manager, m_var2weight);
|
|
del_equations(0);
|
|
}
|
|
|
|
void grobner::del_equations(unsigned old_size) {
|
|
SASSERT(m_equations_to_delete.size() >= old_size);
|
|
equation_vector::iterator it = m_equations_to_delete.begin();
|
|
equation_vector::iterator end = m_equations_to_delete.end();
|
|
it += old_size;
|
|
for (; it != end; ++it) {
|
|
equation * eq = *it;
|
|
if (eq)
|
|
del_equation(eq);
|
|
}
|
|
m_equations_to_delete.shrink(old_size);
|
|
}
|
|
|
|
void grobner::del_equation(equation * eq) {
|
|
m_processed.erase(eq);
|
|
m_to_process.erase(eq);
|
|
SASSERT(m_equations_to_delete[eq->m_bidx] == eq);
|
|
m_equations_to_delete[eq->m_bidx] = 0;
|
|
del_monomials(eq->m_monomials);
|
|
dealloc(eq);
|
|
}
|
|
|
|
void grobner::del_monomials(ptr_vector<monomial>& ms) {
|
|
for (auto& m : ms) {
|
|
del_monomial(m);
|
|
}
|
|
ms.reset();
|
|
}
|
|
|
|
void grobner::del_monomial(monomial * m) {
|
|
for (expr * v : m->m_vars) {
|
|
m_manager.dec_ref(v);
|
|
}
|
|
dealloc(m);
|
|
}
|
|
|
|
void grobner::unfreeze_equations(unsigned old_size) {
|
|
SASSERT(m_equations_to_unfreeze.size() >= old_size);
|
|
equation_vector::iterator it = m_equations_to_unfreeze.begin();
|
|
equation_vector::iterator end = m_equations_to_unfreeze.end();
|
|
it += old_size;
|
|
for (; it != end; ++it) {
|
|
equation * eq = *it;
|
|
m_to_process.insert(eq);
|
|
}
|
|
m_equations_to_unfreeze.shrink(old_size);
|
|
}
|
|
|
|
void grobner::push_scope() {
|
|
m_scopes.push_back(scope());
|
|
scope & s = m_scopes.back();
|
|
s.m_equations_to_unfreeze_lim = m_equations_to_unfreeze.size();
|
|
s.m_equations_to_delete_lim = m_equations_to_delete.size();
|
|
}
|
|
|
|
void grobner::pop_scope(unsigned num_scopes) {
|
|
SASSERT(num_scopes <= get_scope_level());
|
|
unsigned new_lvl = get_scope_level() - num_scopes;
|
|
scope & s = m_scopes[new_lvl];
|
|
unfreeze_equations(s.m_equations_to_unfreeze_lim);
|
|
del_equations(s.m_equations_to_delete_lim);
|
|
m_scopes.shrink(new_lvl);
|
|
}
|
|
|
|
void grobner::reset() {
|
|
flush();
|
|
m_processed.reset();
|
|
m_to_process.reset();
|
|
m_equations_to_unfreeze.reset();
|
|
m_equations_to_delete.reset();
|
|
m_unsat = nullptr;
|
|
}
|
|
|
|
void grobner::display_var(std::ostream & out, expr * var) const {
|
|
out << "#" << var->get_id();
|
|
#if 0
|
|
if (is_app(var) && to_app(var)->get_num_args() > 0)
|
|
out << mk_bounded_pp(var, m_manager);
|
|
else
|
|
out << mk_pp(var, m_manager);
|
|
#endif
|
|
}
|
|
|
|
void grobner::display_vars(std::ostream & out, unsigned num_vars, expr * const * vars) const {
|
|
for (unsigned i = 0; i < num_vars; ++i) {
|
|
display_var(out, vars[i]);
|
|
out << " ";
|
|
}
|
|
}
|
|
|
|
void grobner::display_monomial(std::ostream & out, monomial const & m, std::function<void(std::ostream&, expr*)>& display_var) const {
|
|
if (!m.m_coeff.is_one() || m.m_vars.empty()) {
|
|
out << m.m_coeff;
|
|
if (!m.m_vars.empty())
|
|
out << "*";
|
|
}
|
|
|
|
if (!m.m_vars.empty()) {
|
|
ptr_vector<expr>::const_iterator it = m.m_vars.begin();
|
|
ptr_vector<expr>::const_iterator end = m.m_vars.end();
|
|
unsigned power = 1;
|
|
expr * prev = *it;
|
|
it++;
|
|
for (; it != end; ++it) {
|
|
expr * curr = *it;
|
|
if (curr == prev) {
|
|
power++;
|
|
}
|
|
else {
|
|
display_var(out, prev);
|
|
if (power > 1)
|
|
out << "^" << power;
|
|
power = 1;
|
|
prev = curr;
|
|
out << "*";
|
|
}
|
|
}
|
|
display_var(out, prev);
|
|
if (power > 1)
|
|
out << "^" << power;
|
|
}
|
|
}
|
|
|
|
void grobner::display_monomials(std::ostream & out, unsigned num_monomials, monomial * const * monomials, std::function<void(std::ostream&, expr*)>& display_var) const {
|
|
bool first = true;
|
|
for (unsigned i = 0; i < num_monomials; ++i) {
|
|
monomial const * m = monomials[i];
|
|
if (first)
|
|
first = false;
|
|
else
|
|
out << " + ";
|
|
display_monomial(out, *m, display_var);
|
|
}
|
|
}
|
|
|
|
void grobner::display_equation(std::ostream & out, equation const & eq, std::function<void(std::ostream&, expr*)>& display_var) const {
|
|
display_monomials(out, eq.m_monomials.size(), eq.m_monomials.data(), display_var);
|
|
out << " = 0\n";
|
|
}
|
|
|
|
void grobner::display_equations(std::ostream & out, equation_set const & v, char const * header, std::function<void(std::ostream&, expr*)>& display_var) const {
|
|
if (v.empty())
|
|
return;
|
|
out << header << "\n";
|
|
for (equation const* eq : v)
|
|
display_equation(out, *eq, display_var);
|
|
}
|
|
|
|
void grobner::display(std::ostream & out, std::function<void(std::ostream&, expr*)>& display_var) const {
|
|
display_equations(out, m_processed, "processed:", display_var);
|
|
display_equations(out, m_to_process, "to process:", display_var);
|
|
}
|
|
|
|
void grobner::set_weight(expr * n, int weight) {
|
|
if (weight == 0)
|
|
return;
|
|
if (!m_var2weight.contains(n))
|
|
m_manager.inc_ref(n);
|
|
m_var2weight.insert(n, weight);
|
|
}
|
|
|
|
/**
|
|
\brief Update equation using the new variable order.
|
|
Return true if the leading term was modified.
|
|
*/
|
|
bool grobner::update_order(equation * eq) {
|
|
if (eq->get_num_monomials() == 0)
|
|
return false;
|
|
monomial * first = eq->m_monomials[0];
|
|
for (monomial* m : eq->m_monomials) {
|
|
std::stable_sort(m->m_vars.begin(), m->m_vars.end(), m_var_lt);
|
|
}
|
|
std::stable_sort(eq->m_monomials.begin(), eq->m_monomials.end(), m_monomial_lt);
|
|
return eq->m_monomials[0] != first;
|
|
}
|
|
|
|
void grobner::update_order(equation_set & s, bool processed) {
|
|
ptr_buffer<equation> to_remove;
|
|
for (equation * eq : s) {
|
|
if (update_order(eq)) {
|
|
if (processed) {
|
|
to_remove.push_back(eq);
|
|
m_to_process.insert(eq);
|
|
}
|
|
}
|
|
}
|
|
for (equation* e : to_remove)
|
|
s.erase(e);
|
|
}
|
|
|
|
void grobner::update_order() {
|
|
update_order(m_to_process, false);
|
|
update_order(m_processed, true);
|
|
}
|
|
|
|
bool grobner::var_lt::operator()(expr * v1, expr * v2) const {
|
|
if (v1 == v2) return false;
|
|
int w1 = 0;
|
|
int w2 = 0;
|
|
m_var2weight.find(v1, w1);
|
|
m_var2weight.find(v2, w2);
|
|
return (w1 > w2) || (w1 == w2 && v1->get_id() < v2->get_id());
|
|
}
|
|
|
|
bool grobner::monomial_lt::operator()(monomial * m1, monomial * m2) const {
|
|
// Using graded lex order.
|
|
if (m1->get_degree() > m2->get_degree())
|
|
return true;
|
|
if (m1->get_degree() < m2->get_degree())
|
|
return false;
|
|
ptr_vector<expr>::iterator it1 = m1->m_vars.begin();
|
|
ptr_vector<expr>::iterator it2 = m2->m_vars.begin();
|
|
ptr_vector<expr>::iterator end1 = m1->m_vars.end();
|
|
for (; it1 != end1; ++it1, ++it2) {
|
|
expr * v1 = *it1;
|
|
expr * v2 = *it2;
|
|
if (v1 == v2)
|
|
continue;
|
|
if (m_var_lt(v1, v2))
|
|
return true;
|
|
if (v1 != v2)
|
|
return false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
inline void grobner::add_var(monomial * m, expr * v) {
|
|
SASSERT(!m_util.is_numeral(v));
|
|
m_manager.inc_ref(v);
|
|
m->m_vars.push_back(v);
|
|
}
|
|
|
|
grobner::monomial * grobner::mk_monomial(rational const & coeff, unsigned num_vars, expr * const * vars) {
|
|
monomial * r = alloc(monomial);
|
|
r->m_coeff = coeff;
|
|
for (unsigned i = 0; i < num_vars; ++i)
|
|
add_var(r, vars[i]);
|
|
std::stable_sort(r->m_vars.begin(), r->m_vars.end(), m_var_lt);
|
|
return r;
|
|
}
|
|
|
|
grobner::monomial * grobner::mk_monomial(rational const & coeff, expr * m) {
|
|
monomial * r = alloc(monomial);
|
|
if (m_util.is_numeral(m, r->m_coeff)) {
|
|
r->m_coeff *= coeff;
|
|
return r;
|
|
}
|
|
if (m_util.is_mul(m)) {
|
|
expr * body = m;
|
|
SASSERT(!m_util.is_numeral(to_app(m)->get_arg(1))); // monomial is in normal form
|
|
if (m_util.is_numeral(to_app(m)->get_arg(0), r->m_coeff)) {
|
|
r->m_coeff *= coeff;
|
|
body = to_app(m)->get_arg(1);
|
|
}
|
|
else {
|
|
r->m_coeff = coeff;
|
|
}
|
|
while (m_util.is_mul(body)) {
|
|
add_var(r, to_app(body)->get_arg(0));
|
|
body = to_app(body)->get_arg(1);
|
|
}
|
|
add_var(r, body);
|
|
std::stable_sort(r->m_vars.begin(), r->m_vars.end(), m_var_lt);
|
|
}
|
|
else {
|
|
r->m_coeff = coeff;
|
|
r->m_vars.push_back(m);
|
|
m_manager.inc_ref(m);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
void grobner::init_equation(equation * eq, v_dependency * d) {
|
|
eq->m_scope_lvl = get_scope_level();
|
|
unsigned bidx = m_equations_to_delete.size();
|
|
eq->m_bidx = bidx;
|
|
eq->m_dep = d;
|
|
eq->m_lc = true;
|
|
m_equations_to_delete.push_back(eq);
|
|
SASSERT(m_equations_to_delete[eq->m_bidx] == eq);
|
|
}
|
|
|
|
void grobner::assert_eq_0(unsigned num_monomials, monomial * const * monomials, v_dependency * ex) {
|
|
ptr_vector<monomial> ms;
|
|
ms.append(num_monomials, monomials);
|
|
std::stable_sort(ms.begin(), ms.end(), m_monomial_lt);
|
|
merge_monomials(ms);
|
|
if (!ms.empty()) {
|
|
normalize_coeff(ms);
|
|
equation * eq = alloc(equation);
|
|
eq->m_monomials.swap(ms);
|
|
init_equation(eq, ex);
|
|
m_to_process.insert(eq);
|
|
}
|
|
}
|
|
|
|
void grobner::assert_eq_0(unsigned num_monomials, rational const * coeffs, expr * const * monomials, v_dependency * ex) {
|
|
#define MK_EQ(COEFF) \
|
|
ptr_vector<monomial> ms; \
|
|
for (unsigned i = 0; i < num_monomials; ++i) \
|
|
ms.push_back(mk_monomial(COEFF, monomials[i])); \
|
|
std::stable_sort(ms.begin(), ms.end(), m_monomial_lt); \
|
|
merge_monomials(ms); \
|
|
if (!ms.empty()) { \
|
|
equation * eq = alloc(equation); \
|
|
normalize_coeff(ms); \
|
|
eq->m_monomials.swap(ms); \
|
|
init_equation(eq, ex); \
|
|
m_to_process.insert(eq); \
|
|
}
|
|
|
|
MK_EQ(coeffs[i]);
|
|
}
|
|
|
|
void grobner::assert_eq_0(unsigned num_monomials, expr * const * monomials, v_dependency * ex) {
|
|
rational one(1);
|
|
MK_EQ(one);
|
|
}
|
|
|
|
void grobner::extract_monomials(expr * lhs, ptr_buffer<expr> & monomials) {
|
|
while (m_util.is_add(lhs)) {
|
|
SASSERT(!m_util.is_add(to_app(lhs)->get_arg(0)));
|
|
monomials.push_back(to_app(lhs)->get_arg(0));
|
|
lhs = to_app(lhs)->get_arg(1);
|
|
}
|
|
monomials.push_back(lhs);
|
|
}
|
|
|
|
void grobner::assert_eq(expr * eq, v_dependency * ex) {
|
|
SASSERT(m_manager.is_eq(eq));
|
|
expr * lhs = to_app(eq)->get_arg(0);
|
|
expr * rhs = to_app(eq)->get_arg(1);
|
|
SASSERT(m_util.is_numeral(rhs));
|
|
ptr_buffer<expr> monomials;
|
|
extract_monomials(lhs, monomials);
|
|
rational c;
|
|
bool is_int = false;
|
|
m_util.is_numeral(rhs, c, is_int);
|
|
expr_ref new_c(m_manager);
|
|
if (!c.is_zero()) {
|
|
c.neg();
|
|
new_c = m_util.mk_numeral(c, is_int);
|
|
monomials.push_back(new_c);
|
|
}
|
|
assert_eq_0(monomials.size(), monomials.data(), ex);
|
|
}
|
|
|
|
void grobner::assert_monomial_tautology(expr * m) {
|
|
equation * eq = alloc(equation);
|
|
eq->m_monomials.push_back(mk_monomial(rational(1), m));
|
|
// create (quote m)
|
|
monomial * m1 = alloc(monomial);
|
|
m1->m_coeff = rational(-1);
|
|
m_manager.inc_ref(m);
|
|
m1->m_vars.push_back(m);
|
|
eq->m_monomials.push_back(m1);
|
|
normalize_coeff(eq->m_monomials);
|
|
init_equation(eq, static_cast<v_dependency*>(nullptr)); \
|
|
m_to_process.insert(eq);
|
|
}
|
|
|
|
/**
|
|
\brief Return true if the body of m1 and m2 are equal
|
|
*/
|
|
bool grobner::is_eq_monomial_body(monomial const * m1, monomial const * m2) {
|
|
if (m1->get_degree() != m2->get_degree())
|
|
return false;
|
|
ptr_vector<expr>::const_iterator it1 = m1->m_vars.begin();
|
|
ptr_vector<expr>::const_iterator it2 = m2->m_vars.begin();
|
|
ptr_vector<expr>::const_iterator end1 = m1->m_vars.end();
|
|
for (; it1 != end1; ++it1, ++it2) {
|
|
expr * v1 = *it1;
|
|
expr * v2 = *it2;
|
|
if (v1 != v2)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
\brief Merge monomials (* c1 m) (* c2 m).
|
|
|
|
\remark This method assumes the monomials are sorted.
|
|
*/
|
|
void grobner::merge_monomials(ptr_vector<monomial> & monomials) {
|
|
TRACE(grobner, tout << "before merging monomials:\n"; display_monomials(tout, monomials.size(), monomials.data()); tout << "\n";);
|
|
unsigned j = 0;
|
|
unsigned sz = monomials.size();
|
|
if (sz == 0)
|
|
return;
|
|
SASSERT(&m_del_monomials != &monomials);
|
|
ptr_vector<monomial>& to_delete = m_del_monomials;
|
|
to_delete.reset();
|
|
m_manager.limit().inc(sz);
|
|
for (unsigned i = 1; i < sz; ++i) {
|
|
monomial * m1 = monomials[j];
|
|
monomial * m2 = monomials[i];
|
|
if (is_eq_monomial_body(m1, m2)) {
|
|
m1->m_coeff += m2->m_coeff;
|
|
to_delete.push_back(m2);
|
|
}
|
|
else {
|
|
if (m1->m_coeff.is_zero())
|
|
to_delete.push_back(m1);
|
|
else
|
|
j++;
|
|
monomials[j] = m2;
|
|
}
|
|
}
|
|
SASSERT(j < sz);
|
|
monomial * m1 = monomials[j];
|
|
if (m1->m_coeff.is_zero())
|
|
to_delete.push_back(m1);
|
|
else
|
|
j++;
|
|
monomials.shrink(j);
|
|
del_monomials(to_delete);
|
|
TRACE(grobner, tout << "after merging monomials:\n"; display_monomials(tout, monomials.size(), monomials.data()); tout << "\n";);
|
|
}
|
|
|
|
/**
|
|
\brief Divide the coefficients by the coefficient of the leading term.
|
|
*/
|
|
void grobner::normalize_coeff(ptr_vector<monomial> & monomials) {
|
|
if (monomials.empty())
|
|
return;
|
|
unsigned sz = monomials.size();
|
|
rational c = monomials[0]->m_coeff;
|
|
if (c.is_one())
|
|
return;
|
|
if (c.is_minus_one()) {
|
|
for (unsigned i = 0; i < sz && m_manager.inc(); ++i)
|
|
monomials[i]->m_coeff.neg();
|
|
return;
|
|
}
|
|
if (c.bitsize() > 1000)
|
|
return;
|
|
|
|
for (unsigned i = 0; i < sz && m_manager.inc(); ++i) {
|
|
if (monomials[i]->m_coeff.bitsize() > 1000)
|
|
continue;
|
|
monomials[i]->m_coeff /= c;
|
|
}
|
|
}
|
|
|
|
/**
|
|
\brief Simplify the given monomials
|
|
*/
|
|
void grobner::simplify(ptr_vector<monomial> & monomials) {
|
|
std::stable_sort(monomials.begin(), monomials.end(), m_monomial_lt);
|
|
merge_monomials(monomials);
|
|
normalize_coeff(monomials);
|
|
}
|
|
|
|
/**
|
|
\brief Return true if the equation is of the form k = 0, where k is a numeral different from zero.
|
|
|
|
\remark This method assumes the equation is simplified.
|
|
*/
|
|
inline bool grobner::is_inconsistent(equation * eq) const {
|
|
SASSERT(!(eq->m_monomials.size() == 1 && eq->m_monomials[0]->get_degree() == 0) || !eq->m_monomials[0]->m_coeff.is_zero());
|
|
return eq->m_monomials.size() == 1 && eq->m_monomials[0]->get_degree() == 0;
|
|
}
|
|
|
|
/**
|
|
\brief Return true if the equation is of the form 0 = 0.
|
|
*/
|
|
inline bool grobner::is_trivial(equation * eq) const {
|
|
return eq->m_monomials.empty();
|
|
}
|
|
|
|
/**
|
|
\brief Sort monomials, and merge monomials with the same body.
|
|
*/
|
|
void grobner::simplify(equation * eq) {
|
|
simplify(eq->m_monomials);
|
|
if (is_inconsistent(eq) && !m_unsat)
|
|
m_unsat = eq;
|
|
}
|
|
|
|
/**
|
|
\brief Return true if monomial m1 is (* c1 M) and m2 is (* c2 M M').
|
|
Store M' in rest.
|
|
|
|
\remark This method assumes the variables of m1 and m2 are sorted.
|
|
*/
|
|
bool grobner::is_subset(monomial const * m1, monomial const * m2, ptr_vector<expr> & rest) const {
|
|
unsigned i1 = 0;
|
|
unsigned i2 = 0;
|
|
unsigned sz1 = m1->m_vars.size();
|
|
unsigned sz2 = m2->m_vars.size();
|
|
if (sz1 <= sz2) {
|
|
while (true) {
|
|
if (i1 >= sz1) {
|
|
for (; i2 < sz2; ++i2)
|
|
rest.push_back(m2->m_vars[i2]);
|
|
TRACE(grobner,
|
|
tout << "monomial: "; display_monomial(tout, *m1); tout << " is a subset of ";
|
|
display_monomial(tout, *m2); tout << "\n";
|
|
tout << "rest: "; display_vars(tout, rest.size(), rest.data()); tout << "\n";);
|
|
return true;
|
|
}
|
|
if (i2 >= sz2)
|
|
break;
|
|
expr * var1 = m1->m_vars[i1];
|
|
expr * var2 = m2->m_vars[i2];
|
|
if (var1 == var2) {
|
|
i1++;
|
|
i2++;
|
|
continue;
|
|
}
|
|
if (m_var_lt(var2, var1)) {
|
|
i2++;
|
|
rest.push_back(var2);
|
|
continue;
|
|
}
|
|
SASSERT(m_var_lt(var1, var2));
|
|
break;
|
|
}
|
|
}
|
|
// is not subset
|
|
TRACE(grobner, tout << "monomial: "; display_monomial(tout, *m1); tout << " is not a subset of ";
|
|
display_monomial(tout, *m2); tout << "\n";);
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
\brief Multiply the monomials of source starting at position start_idx by (coeff * vars), and store the resultant monomials
|
|
at result.
|
|
*/
|
|
void grobner::mul_append(unsigned start_idx, equation const * source, rational const & coeff, ptr_vector<expr> const & vars, ptr_vector<monomial> & result) {
|
|
unsigned sz = source->get_num_monomials();
|
|
for (unsigned i = start_idx; i < sz; ++i) {
|
|
monomial const * m = source->get_monomial(i);
|
|
monomial * new_m = alloc(monomial);
|
|
new_m->m_coeff = m->m_coeff;
|
|
new_m->m_coeff *= coeff;
|
|
new_m->m_vars.append(m->m_vars.size(), m->m_vars.data());
|
|
new_m->m_vars.append(vars.size(), vars.data());
|
|
for (expr* e : new_m->m_vars)
|
|
m_manager.inc_ref(e);
|
|
std::stable_sort(new_m->m_vars.begin(), new_m->m_vars.end(), m_var_lt);
|
|
result.push_back(new_m);
|
|
}
|
|
}
|
|
|
|
/**
|
|
\brief Copy the given monomial.
|
|
*/
|
|
grobner::monomial * grobner::copy_monomial(monomial const * m) {
|
|
monomial * r = alloc(monomial);
|
|
r->m_coeff = m->m_coeff;
|
|
for (expr* e : m->m_vars)
|
|
add_var(r, e);
|
|
return r;
|
|
}
|
|
|
|
/**
|
|
\brief Copy the given equation.
|
|
*/
|
|
grobner::equation * grobner::copy_equation(equation const * eq) {
|
|
equation * r = alloc(equation);
|
|
unsigned sz = eq->get_num_monomials();
|
|
for (unsigned i = 0; i < sz; ++i)
|
|
r->m_monomials.push_back(copy_monomial(eq->get_monomial(i)));
|
|
init_equation(r, eq->m_dep);
|
|
r->m_lc = eq->m_lc;
|
|
return r;
|
|
}
|
|
|
|
/**
|
|
\brief Simplify the target equation using the source as a rewrite rule.
|
|
Return 0 if target was not simplified.
|
|
Return target if target was simplified but source->m_scope_lvl <= target->m_scope_lvl.
|
|
Return new_equation if source->m_scope_lvl > target->m_scope_lvl, moreover target is freezed, and new_equation contains the result.
|
|
*/
|
|
grobner::equation * grobner::simplify(equation const * source, equation * target) {
|
|
TRACE(grobner, tout << "simplifying: "; display_equation(tout, *target); tout << "using: "; display_equation(tout, *source););
|
|
if (source->get_num_monomials() == 0)
|
|
return nullptr;
|
|
if (!m_manager.inc())
|
|
return target;
|
|
m_stats.m_simplify++;
|
|
bool result = false;
|
|
bool simplified;
|
|
do {
|
|
simplified = false;
|
|
unsigned i = 0;
|
|
unsigned j = 0;
|
|
unsigned sz = target->m_monomials.size();
|
|
monomial const * LT = source->get_monomial(0);
|
|
ptr_vector<monomial> & new_monomials = m_tmp_monomials;
|
|
new_monomials.reset();
|
|
ptr_vector<expr> & rest = m_tmp_vars1;
|
|
for (; i < sz; ++i) {
|
|
monomial * curr = target->m_monomials[i];
|
|
rest.reset();
|
|
if (is_subset(LT, curr, rest)) {
|
|
if (i == 0)
|
|
m_changed_leading_term = true;
|
|
if (source->m_scope_lvl > target->m_scope_lvl) {
|
|
target = copy_equation(target);
|
|
SASSERT(target->m_scope_lvl >= source->m_scope_lvl);
|
|
}
|
|
if (!result) {
|
|
// first time that source is being applied.
|
|
target->m_dep = m_dep_manager.mk_join(target->m_dep, source->m_dep);
|
|
}
|
|
simplified = true;
|
|
result = true;
|
|
rational coeff = curr->m_coeff;
|
|
coeff /= LT->m_coeff;
|
|
coeff.neg();
|
|
if (!rest.empty())
|
|
target->m_lc = false;
|
|
mul_append(1, source, coeff, rest, new_monomials);
|
|
del_monomial(curr);
|
|
target->m_monomials[i] = 0;
|
|
}
|
|
else {
|
|
target->m_monomials[j] = curr;
|
|
j++;
|
|
}
|
|
}
|
|
if (simplified) {
|
|
target->m_monomials.shrink(j);
|
|
target->m_monomials.append(new_monomials.size(), new_monomials.data());
|
|
simplify(target);
|
|
}
|
|
}
|
|
while (simplified && m_manager.inc());
|
|
TRACE(grobner, tout << "result: "; display_equation(tout, *target););
|
|
return result ? target : nullptr;
|
|
}
|
|
|
|
/**
|
|
\brief Simplify given equation using processed equalities.
|
|
Return 0, if the equation was not simplified.
|
|
Return eq, if the equation was simplified using destructive updates.
|
|
Return new_eq otherwise.
|
|
*/
|
|
grobner::equation * grobner::simplify_using_processed(equation * eq) {
|
|
bool result = false;
|
|
bool simplified;
|
|
TRACE(grobner, tout << "simplifying: "; display_equation(tout, *eq); tout << "using already processed equalities\n";);
|
|
do {
|
|
simplified = false;
|
|
for (equation const* p : m_processed) {
|
|
equation * new_eq = simplify(p, eq);
|
|
if (new_eq) {
|
|
result = true;
|
|
simplified = true;
|
|
eq = new_eq;
|
|
}
|
|
if (!m_manager.inc()) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
}
|
|
while (simplified);
|
|
TRACE(grobner, tout << "simplification result: "; display_equation(tout, *eq););
|
|
return result ? eq : nullptr;
|
|
}
|
|
|
|
/**
|
|
\brief Return true if eq1 is a better than e2, for being the next equation to be processed.
|
|
*/
|
|
bool grobner::is_better_choice(equation * eq1, equation * eq2) {
|
|
if (!eq2)
|
|
return true;
|
|
if (eq1->m_monomials.empty())
|
|
return true;
|
|
if (eq2->m_monomials.empty())
|
|
return false;
|
|
if (eq1->m_monomials[0]->get_degree() < eq2->m_monomials[0]->get_degree())
|
|
return true;
|
|
if (eq1->m_monomials[0]->get_degree() > eq2->m_monomials[0]->get_degree())
|
|
return false;
|
|
return eq1->m_monomials.size() < eq2->m_monomials.size();
|
|
}
|
|
|
|
/**
|
|
\brief Pick next unprocessed equation
|
|
*/
|
|
grobner::equation * grobner::pick_next() {
|
|
equation * r = nullptr;
|
|
ptr_buffer<equation> to_delete;
|
|
for (equation * curr : m_to_process) {
|
|
if (is_trivial(curr))
|
|
to_delete.push_back(curr);
|
|
else if (is_better_choice(curr, r))
|
|
r = curr;
|
|
}
|
|
for (equation * e : to_delete)
|
|
del_equation(e);
|
|
if (r)
|
|
m_to_process.erase(r);
|
|
TRACE(grobner, tout << "selected equation: "; if (!r) tout << "<null>\n"; else display_equation(tout, *r););
|
|
return r;
|
|
}
|
|
|
|
/**
|
|
\brief Use the given equation to simplify processed terms.
|
|
*/
|
|
bool grobner::simplify_processed(equation * eq) {
|
|
ptr_buffer<equation> to_insert;
|
|
ptr_buffer<equation> to_remove;
|
|
ptr_buffer<equation> to_delete;
|
|
equation_set::iterator it = m_processed.begin();
|
|
equation_set::iterator end = m_processed.end();
|
|
for (; it != end && m_manager.inc(); ++it) {
|
|
equation * curr = *it;
|
|
m_changed_leading_term = false;
|
|
// if the leading term is simplified, then the equation has to be moved to m_to_process
|
|
equation * new_curr = simplify(eq, curr);
|
|
if (new_curr != nullptr) {
|
|
if (new_curr != curr) {
|
|
m_equations_to_unfreeze.push_back(curr);
|
|
to_remove.push_back(curr);
|
|
if (m_changed_leading_term) {
|
|
m_to_process.insert(new_curr);
|
|
to_remove.push_back(curr);
|
|
}
|
|
else {
|
|
to_insert.push_back(new_curr);
|
|
}
|
|
curr = new_curr;
|
|
}
|
|
else {
|
|
if (m_changed_leading_term) {
|
|
m_to_process.insert(curr);
|
|
to_remove.push_back(curr);
|
|
}
|
|
}
|
|
}
|
|
if (is_trivial(curr))
|
|
to_delete.push_back(curr);
|
|
}
|
|
for (equation* eq : to_insert)
|
|
m_processed.insert(eq);
|
|
for (equation* eq : to_remove)
|
|
m_processed.erase(eq);
|
|
for (equation* eq : to_delete)
|
|
del_equation(eq);
|
|
return m_manager.inc();
|
|
}
|
|
|
|
/**
|
|
\brief Use the given equation to simplify to-process terms.
|
|
*/
|
|
void grobner::simplify_to_process(equation * eq) {
|
|
ptr_buffer<equation> to_insert;
|
|
ptr_buffer<equation> to_remove;
|
|
ptr_buffer<equation> to_delete;
|
|
for (equation* curr : m_to_process) {
|
|
equation * new_curr = simplify(eq, curr);
|
|
if (new_curr != nullptr && new_curr != curr) {
|
|
m_equations_to_unfreeze.push_back(curr);
|
|
to_insert.push_back(new_curr);
|
|
to_remove.push_back(curr);
|
|
curr = new_curr;
|
|
}
|
|
if (is_trivial(curr))
|
|
to_delete.push_back(curr);
|
|
}
|
|
for (equation* eq : to_insert)
|
|
m_to_process.insert(eq);
|
|
for (equation* eq : to_remove)
|
|
m_to_process.erase(eq);
|
|
for (equation* eq : to_delete)
|
|
del_equation(eq);
|
|
}
|
|
|
|
/**
|
|
\brief If m1 = (* c M M1) and m2 = (* d M M2) and M is non empty, then return true and store M1 in rest1 and M2 in rest2.
|
|
*/
|
|
bool grobner::unify(monomial const * m1, monomial const * m2, ptr_vector<expr> & rest1, ptr_vector<expr> & rest2) {
|
|
TRACE(grobner, tout << "unifying: "; display_monomial(tout, *m1); tout << " "; display_monomial(tout, *m2); tout << "\n";);
|
|
bool found_M = false;
|
|
unsigned i1 = 0;
|
|
unsigned i2 = 0;
|
|
unsigned sz1 = m1->m_vars.size();
|
|
unsigned sz2 = m2->m_vars.size();
|
|
while (true) {
|
|
if (i1 >= sz1) {
|
|
if (found_M) {
|
|
for (; i2 < sz2; ++i2)
|
|
rest2.push_back(m2->m_vars[i2]);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
if (i2 >= sz2) {
|
|
if (found_M) {
|
|
for (; i1 < sz1; ++i1)
|
|
rest1.push_back(m1->m_vars[i1]);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
expr * var1 = m1->m_vars[i1];
|
|
expr * var2 = m2->m_vars[i2];
|
|
if (var1 == var2) {
|
|
found_M = true;
|
|
i1++;
|
|
i2++;
|
|
}
|
|
else if (m_var_lt(var2, var1)) {
|
|
i2++;
|
|
rest2.push_back(var2);
|
|
}
|
|
else {
|
|
i1++;
|
|
rest1.push_back(var1);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
\brief Superpose the given two equations.
|
|
*/
|
|
void grobner::superpose(equation * eq1, equation * eq2) {
|
|
if (eq1->m_monomials.empty() || eq2->m_monomials.empty())
|
|
return;
|
|
m_stats.m_superpose++;
|
|
ptr_vector<expr> & rest1 = m_tmp_vars1;
|
|
rest1.reset();
|
|
ptr_vector<expr> & rest2 = m_tmp_vars2;
|
|
rest2.reset();
|
|
if (unify(eq1->m_monomials[0], eq2->m_monomials[0], rest1, rest2)) {
|
|
TRACE(grobner, tout << "superposing:\n"; display_equation(tout, *eq1); display_equation(tout, *eq2);
|
|
tout << "rest1: "; display_vars(tout, rest1.size(), rest1.data()); tout << "\n";
|
|
tout << "rest2: "; display_vars(tout, rest2.size(), rest2.data()); tout << "\n";);
|
|
ptr_vector<monomial> & new_monomials = m_tmp_monomials;
|
|
new_monomials.reset();
|
|
mul_append(1, eq1, eq2->m_monomials[0]->m_coeff, rest2, new_monomials);
|
|
rational c = eq1->m_monomials[0]->m_coeff;
|
|
c.neg();
|
|
mul_append(1, eq2, c, rest1, new_monomials);
|
|
simplify(new_monomials);
|
|
TRACE(grobner, tout << "resulting monomials: "; display_monomials(tout, new_monomials.size(), new_monomials.data()); tout << "\n";);
|
|
if (new_monomials.empty())
|
|
return;
|
|
m_num_new_equations++;
|
|
equation * new_eq = alloc(equation);
|
|
new_eq->m_monomials.swap(new_monomials);
|
|
init_equation(new_eq, m_dep_manager.mk_join(eq1->m_dep, eq2->m_dep));
|
|
new_eq->m_lc = false;
|
|
m_to_process.insert(new_eq);
|
|
}
|
|
}
|
|
|
|
/**
|
|
\brief Superpose the given equations with the equations in m_processed.
|
|
*/
|
|
void grobner::superpose(equation * eq) {
|
|
for (equation * curr : m_processed) {
|
|
superpose(eq, curr);
|
|
}
|
|
}
|
|
|
|
void grobner::compute_basis_init() {
|
|
m_stats.m_compute_basis++;
|
|
m_num_new_equations = 0;
|
|
}
|
|
|
|
bool grobner::compute_basis_step() {
|
|
equation * eq = pick_next();
|
|
if (!eq)
|
|
return true;
|
|
m_stats.m_num_processed++;
|
|
#ifdef PROFILE_GB
|
|
if (m_stats.m_num_processed % 100 == 0) {
|
|
verbose_stream() << "[grobner] " << m_processed.size() << " " << m_to_process.size() << "\n";
|
|
}
|
|
#endif
|
|
equation * new_eq = simplify_using_processed(eq);
|
|
if (new_eq != nullptr && eq != new_eq) {
|
|
// equation was updated using non destructive updates
|
|
m_equations_to_unfreeze.push_back(eq);
|
|
eq = new_eq;
|
|
}
|
|
if (!m_manager.inc()) return false;
|
|
if (!simplify_processed(eq)) return false;
|
|
superpose(eq);
|
|
m_processed.insert(eq);
|
|
simplify_to_process(eq);
|
|
TRACE(grobner, tout << "end of iteration:\n"; display(tout););
|
|
return false;
|
|
}
|
|
|
|
bool grobner::compute_basis(unsigned threshold) {
|
|
compute_basis_init();
|
|
while (m_num_new_equations < threshold && m_manager.inc()) {
|
|
if (compute_basis_step()) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void grobner::copy_to(equation_set const & s, ptr_vector<equation> & result) const {
|
|
for (equation * e : s)
|
|
result.push_back(e);
|
|
}
|
|
|
|
/**
|
|
\brief Copy the equations in m_processed and m_to_process to result.
|
|
|
|
\warning This equations can be deleted when compute_basis is invoked.
|
|
*/
|
|
void grobner::get_equations(ptr_vector<equation> & result) const {
|
|
copy_to(m_processed, result);
|
|
copy_to(m_to_process, result);
|
|
}
|
|
|