mirror of
https://github.com/Z3Prover/z3
synced 2025-05-11 09:44:43 +00:00
* Use scoped_ptr for condition * Check solver result in unit tests * Add test for unusual cjust * Add solver::get_value * Broken assertion * Support forbidden interval for coefficient -1
215 lines
6.3 KiB
C++
215 lines
6.3 KiB
C++
/*++
|
|
Copyright (c) 2021 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
polysat eq_constraints
|
|
|
|
Author:
|
|
|
|
Nikolaj Bjorner (nbjorner) 2021-03-19
|
|
Jakob Rath 2021-04-6
|
|
|
|
--*/
|
|
|
|
#include "math/polysat/constraint.h"
|
|
#include "math/polysat/solver.h"
|
|
#include "math/polysat/log.h"
|
|
|
|
namespace polysat {
|
|
|
|
std::ostream& eq_constraint::display(std::ostream& out) const {
|
|
return out << p() << (sign() == pos_t ? " == 0" : " != 0") << " [" << m_status << "]";
|
|
}
|
|
|
|
constraint* eq_constraint::resolve(solver& s, pvar v) {
|
|
if (is_positive())
|
|
return eq_resolve(s, v);
|
|
if (is_negative())
|
|
return diseq_resolve(s, v);
|
|
UNREACHABLE();
|
|
return nullptr;
|
|
}
|
|
|
|
void eq_constraint::narrow(solver& s) {
|
|
SASSERT(!is_undef());
|
|
LOG("Assignment: " << s.m_search);
|
|
auto q = p().subst_val(s.m_search);
|
|
LOG("Substituted: " << p() << " := " << q);
|
|
if (q.is_zero()) {
|
|
if (is_positive())
|
|
return;
|
|
if (is_negative()) {
|
|
LOG("Conflict (zero under current assignment)");
|
|
s.set_conflict(*this);
|
|
return;
|
|
}
|
|
}
|
|
if (q.is_never_zero()) {
|
|
if (is_positive()) {
|
|
LOG("Conflict (never zero under current assignment)");
|
|
s.set_conflict(*this);
|
|
return;
|
|
}
|
|
if (is_negative())
|
|
return;
|
|
}
|
|
|
|
if (q.is_unilinear()) {
|
|
// a*x + b == 0
|
|
pvar v = q.var();
|
|
rational a = q.hi().val();
|
|
rational b = q.lo().val();
|
|
bddv const& x = s.var2bits(v).var();
|
|
bddv lhs = a * x + b;
|
|
rational zero = rational::zero();
|
|
bdd xs = is_positive() ? (lhs == zero) : (lhs != zero);
|
|
s.push_cjust(v, this);
|
|
s.intersect_viable(v, xs);
|
|
|
|
rational val;
|
|
if (s.find_viable(v, val) == dd::find_t::singleton) {
|
|
s.propagate(v, val, *this);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// TODO: what other constraints can be extracted cheaply?
|
|
}
|
|
|
|
bool eq_constraint::is_always_false() {
|
|
if (is_positive())
|
|
return p().is_never_zero();
|
|
if (is_negative())
|
|
return p().is_zero();
|
|
UNREACHABLE();
|
|
return false;
|
|
}
|
|
|
|
bool eq_constraint::is_currently_false(solver& s) {
|
|
pdd r = p().subst_val(s.m_search);
|
|
if (is_positive())
|
|
return r.is_never_zero();
|
|
if (is_negative())
|
|
return r.is_zero();
|
|
UNREACHABLE();
|
|
return false;
|
|
}
|
|
|
|
bool eq_constraint::is_currently_true(solver& s) {
|
|
pdd r = p().subst_val(s.m_search);
|
|
if (is_positive())
|
|
return r.is_zero();
|
|
if (is_negative())
|
|
return r.is_never_zero();
|
|
UNREACHABLE();
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Equality constraints
|
|
*/
|
|
|
|
constraint* eq_constraint::eq_resolve(solver& s, pvar v) {
|
|
LOG("Resolve " << *this << " upon v" << v);
|
|
if (s.m_conflict.size() != 1)
|
|
return nullptr;
|
|
constraint* c = s.m_conflict[0];
|
|
SASSERT(c->is_currently_false(s));
|
|
// 'c == this' can happen if propagation was from decide() with only one value left
|
|
// (e.g., if there's an unsatisfiable clause and we try all values).
|
|
// Resolution would give us '0 == 0' in this case, which is useless.
|
|
if (c == this)
|
|
return nullptr;
|
|
SASSERT(is_currently_true(s)); // TODO: might not always hold (due to similar case as in comment above?)
|
|
if (c->is_eq()) {
|
|
pdd a = c->to_eq().p();
|
|
pdd b = p();
|
|
pdd r = a;
|
|
if (!a.resolve(v, b, r))
|
|
return nullptr;
|
|
p_dependency_ref d(s.m_dm.mk_join(c->dep(), dep()), s.m_dm);
|
|
unsigned lvl = std::max(c->level(), level());
|
|
constraint* lemma = constraint::eq(lvl, s.m_next_bvar++, pos_t, r, d);
|
|
lemma->assign_eh(true);
|
|
return lemma;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
/**
|
|
* Disequality constraints
|
|
*/
|
|
|
|
constraint* eq_constraint::diseq_resolve(solver& s, pvar v) {
|
|
NOT_IMPLEMENTED_YET();
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
|
|
/// Compute forbidden interval for equality constraint by considering it as p <=u 0 (or p >u 0 for disequality)
|
|
bool eq_constraint::forbidden_interval(solver& s, pvar v, eval_interval& out_interval, scoped_ptr<constraint>& out_neg_cond)
|
|
{
|
|
SASSERT(!is_undef());
|
|
|
|
// Current only works when degree(v) is at most one
|
|
unsigned const deg = p().degree(v);
|
|
if (deg > 1)
|
|
return false;
|
|
|
|
if (deg == 0) {
|
|
UNREACHABLE(); // this case is not useful for conflict resolution (but it could be handled in principle)
|
|
// i is empty or full, condition would be this constraint itself?
|
|
return true;
|
|
}
|
|
|
|
unsigned const sz = s.size(v);
|
|
dd::pdd_manager& m = s.sz2pdd(sz);
|
|
|
|
pdd p1 = m.zero();
|
|
pdd e1 = m.zero();
|
|
p().factor(v, 1, p1, e1);
|
|
|
|
pdd e2 = m.zero();
|
|
|
|
// Currently only works if coefficient is a power of two
|
|
if (!p1.is_val())
|
|
return false;
|
|
rational a1 = p1.val();
|
|
// TODO: to express the interval for coefficient 2^i symbolically, we need right-shift/upper-bits-extract in the language.
|
|
// So currently we can only do it if the coefficient is 1.
|
|
if (!a1.is_zero() && !a1.is_one())
|
|
return false;
|
|
/*
|
|
unsigned j1 = 0;
|
|
if (!a1.is_zero() && !a1.is_power_of_two(j1))
|
|
return false;
|
|
*/
|
|
|
|
// Concrete values of evaluable terms
|
|
auto e1s = e1.subst_val(s.m_search);
|
|
auto e2s = m.zero();
|
|
SASSERT(e1s.is_val());
|
|
SASSERT(e2s.is_val());
|
|
|
|
// e1 + t <= 0, with t = 2^j1*y
|
|
// condition for empty/full: 0 == -1, never satisfied, so we always have a proper interval!
|
|
SASSERT(!a1.is_zero());
|
|
pdd lo = 1 - e1;
|
|
rational lo_val = (1 - e1s).val();
|
|
pdd hi = -e1;
|
|
rational hi_val = (-e1s).val();
|
|
if (is_negative()) {
|
|
swap(lo, hi);
|
|
lo_val.swap(hi_val);
|
|
}
|
|
out_interval = eval_interval::proper(lo, lo_val, hi, hi_val);
|
|
out_neg_cond = nullptr;
|
|
return true;
|
|
}
|
|
|
|
}
|