3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-10-16 20:40:27 +00:00

Propagate assignment if all bits are assigned and use better justification if any found

This commit is contained in:
Clemens Eisenhofer 2022-12-27 08:44:55 +01:00
parent 52eefb6e85
commit 39a4bb025b
7 changed files with 460 additions and 313 deletions

View file

@ -12,7 +12,8 @@ Abstract:
--*/
#pragma once
#include "types.h"
#include "math/polysat/constraint.h"
#include "math/polysat/types.h"
#include "util/hash.h"
#include "util/optional.h"
#include "util/tbv.h"
@ -23,7 +24,8 @@ namespace polysat {
class solver;
class constraint;
class fixed_bits;
class bitvec_info;
struct bvpos {
optional<pdd> m_pdd;
unsigned m_bit_idx;
@ -58,15 +60,19 @@ namespace polysat {
using bit_dependencies = vector<bvpos>;
class bit_justication {
class bit_justification {
protected:
static bit_justication* get_other_justification(const fixed_bits& fixed, const pdd& p, unsigned idx);
static bit_justification* get_justification(fixed_bits& fixed, const pdd& p, unsigned idx);
static unsigned get_level(fixed_bits& fixed, const pdd& p, unsigned idx);
static const tbv_ref* get_tbv(fixed_bits& fixed, const pdd& p);
static bool fix_bit(solver& s, fixed_bits& fixed, const pdd& p, tbv_ref& tbv, unsigned idx, tbit val, bit_justication** j);
static bool fix_bit(solver& s, fixed_bits& fixed, const pdd& p, tbv_ref& tbv, unsigned idx, tbit val, bit_justication* j);
bit_justification() = default;
public:
unsigned m_decision_level;
// if we reduce this value we would have to reduce some decision levels of justifications depending on it.
// However, we don't do this for now. (Should be still correct but generate weaker justifications)
// This value is used for comparing which of two justifications is better. Does not have to be 100% correct
unsigned m_decision_level = UINT32_MAX; // maybe better "weight"?
virtual bool can_dealloc() { return true; } // we may not dealloc if the justification is used for multiple bits
virtual bool has_constraint(constraint*& constr) { return false; }
@ -75,14 +81,16 @@ namespace polysat {
// if multiple bits are justified by the same justification
// All elements have to be in the same decision-level
class bit_justication_shared : public bit_justication {
bit_justication* m_justification;
class bit_justification_shared : public bit_justification {
bit_justification* m_justification;
unsigned m_references = 0;
public:
bit_justication_shared() = default;
bit_justication_shared(bit_justication* j) : m_justification(j), m_references(1) {}
bit_justification_shared(bit_justification* j) : m_justification(j), m_references(1) {
SASSERT(j);
m_decision_level = j->m_decision_level;
}
bit_justication* get_justification() { return m_justification; }
bit_justification* get_justification() { return m_justification; }
virtual bool can_dealloc() {
m_references = m_references == 0 ? 0 : m_references - 1;
@ -103,52 +111,50 @@ namespace polysat {
void inc_ref() { m_references++; }
};
class bit_justication_constraint : public bit_justication {
class bit_justification_constraint : public bit_justification {
constraint* m_constraint = nullptr;
// A pdd might occur multiple times if more bits of it are relevant
bit_dependencies m_dependencies;
bit_justication_constraint(constraint* c) : m_constraint(c) {}
bit_justication_constraint(constraint* c, const bit_dependencies& dep) : m_constraint(c), m_dependencies(dep) {}
bit_justication_constraint(constraint* c, bit_dependencies&& dep) : m_constraint(c), m_dependencies(dep) {}
bit_justification_constraint(solver& s, constraint* c, bit_dependencies&& dep);
bit_justification_constraint(solver& s, constraint* c, const bit_dependencies& dep) : bit_justification_constraint(s, c, bit_dependencies(dep)) {}
bit_justification_constraint(solver& s, constraint* c) : bit_justification_constraint(s, c, bit_dependencies()) {}
public:
bit_justication_constraint() = default;
bool has_constraint(constraint*& constr) {
constr = m_constraint;
return true;
}
void get_dependencies(fixed_bits& fixed, bit_dependencies& to_process) override;
static bit_justication_constraint* mk_assignment(constraint* c) { return alloc(bit_justication_constraint, c ); }
static bit_justication_constraint* mk_unary(constraint* c, bvpos v) {
static bit_justification_constraint* mk_assignment(solver& s, constraint* c) { return alloc(bit_justification_constraint, s, c); }
static bit_justification_constraint* mk_unary(solver& s, constraint* c, bvpos v) {
bit_dependencies dep;
dep.push_back(std::move(v));
return alloc(bit_justication_constraint, c, std::move(dep));
return alloc(bit_justification_constraint, s, c, std::move(dep));
}
static bit_justication_constraint* mk_binary(constraint* c, bvpos v1, bvpos v2) {
static bit_justification_constraint* mk_binary(solver& s, constraint* c, bvpos v1, bvpos v2) {
bit_dependencies dep;
dep.push_back(std::move(v1));
dep.push_back(std::move(v2));
return alloc(bit_justication_constraint, c, std::move(dep));
return alloc(bit_justification_constraint, s, c, std::move(dep));
}
static bit_justication_constraint* mk(constraint* c, const bit_dependencies& dep) { return alloc(bit_justication_constraint, c, dep); }
static bit_justification_constraint* mk(solver& s, constraint* c, const bit_dependencies& dep) { return alloc(bit_justification_constraint, s, c, dep); }
// gives the highest bits such that they already enforce a value of "tbv" that is at least "val"
static bit_justication_constraint* mk_justify_at_least(constraint *c, const pdd& v, const tbv_ref& tbv, const rational& least);
static bit_justification_constraint* mk_justify_at_least(solver& s, constraint *c, const pdd& v, const tbv_ref& tbv, const rational& least);
// similar to mk_justify_at_least: gives the highest bits such that they already enforce a value of "tbv" that is at most "val"
static bit_justication_constraint* mk_justify_at_most(constraint *c, const pdd& v, const tbv_ref& tbv, const rational& most);
static bit_justification_constraint* mk_justify_at_most(solver& s, constraint *c, const pdd& v, const tbv_ref& tbv, const rational& most);
// a combination of mk_justify_at_least and mk_justify_at_most
static bit_justication_constraint* mk_justify_between(constraint *c, const pdd& v, const tbv_ref& tbv, rational least, rational most);
static bit_justification_constraint* mk_justify_between(solver& s, constraint *c, const pdd& v, const tbv_ref& tbv, rational least, rational most);
};
// lazy generation of the justifications. Generating them eagerly can generate a huge overhead
class bit_justication_mul : public bit_justication {
class bit_justification_mul : public bit_justification {
unsigned m_idx;
optional<pdd> m_p, m_q, m_r;
@ -156,10 +162,15 @@ namespace polysat {
public:
bit_justication_mul() = default;
bit_justication_mul(unsigned idx, const pdd& p, const pdd& q) : m_idx(idx), m_p(p), m_q(q) {}
bit_justication_mul(unsigned idx, const pdd& p, const pdd& q, const pdd& r) : m_idx(idx), m_p(p), m_q(q), m_r(r) {}
bit_justification_mul(unsigned idx, const pdd& p, const pdd& q, const pdd& r) : m_idx(idx), m_p(p), m_q(q), m_r(r) {
// we can also track the decision level but multiplications are unpleasant anyway.
// We prefer any other justification (Othw. take the max of all coefficients that influence the result)
m_decision_level = UINT32_MAX;
}
bit_justification_mul(unsigned idx, const pdd& p, const pdd& q) : m_idx(idx), m_p(p), m_q(q) {
m_decision_level = UINT32_MAX;
}
// propagates from p, q => r (forward) and r, p/q => q/p (backward)
static void propagate(solver& s, fixed_bits& fixed, const pdd& r, const pdd &p, const pdd &q);
void get_dependencies(fixed_bits& fixed, bit_dependencies& to_process) override;
@ -167,91 +178,172 @@ namespace polysat {
void get_dependencies_backward(fixed_bits& fixed, bit_dependencies& to_process, const tbv_ref& p_tbv, const tbv_ref& q_tbv, unsigned relevant_range);
};
class bit_justication_add : public bit_justication {
class bit_justification_add : public bit_justification {
unsigned m_idx;
optional<pdd> m_c1, m_c2;
public:
bit_justication_add() = default;
bit_justication_add(unsigned idx, const pdd& c1, const pdd& c2) : m_idx(idx), m_c1(c1), m_c2(c2) {}
bit_justification_add(unsigned lvl, unsigned idx, const pdd& c1, const pdd& c2) : m_idx(idx), m_c1(c1), m_c2(c2) {
m_decision_level = lvl;
}
static void propagate(solver& s, fixed_bits& fixed, const pdd& r, const pdd& p, const pdd& q);
void get_dependencies(fixed_bits& fixed, bit_dependencies& to_process) override;
};
struct justified_bvpos : public bvpos {
bit_justication* m_justification;
unsigned m_trail_pos;
bit_justification* m_justification;
justified_bvpos() = default;
justified_bvpos(const pdd & pdd, unsigned idx, bit_justication* jstfc, unsigned int trail_pos) :
bvpos(pdd, idx), m_justification(jstfc), m_trail_pos(trail_pos) {}
justified_bvpos(const pdd & pdd, unsigned idx, bit_justification* jstfc) :
bvpos(pdd, idx), m_justification(jstfc) {}
justified_bvpos(const bvpos& pos, bit_justication* jstfc, unsigned int trail_pos) :
bvpos(pos), m_justification(jstfc), m_trail_pos(trail_pos) {}
justified_bvpos(const bvpos& pos, bit_justification* jstfc) :
bvpos(pos), m_justification(jstfc) {}
void set(bit_justification* new_justification){
if (m_justification && m_justification->can_dealloc())
dealloc(m_justification);
m_justification = new_justification;
}
};
class bitvec_info {
tbv_ref* m_bits = nullptr;
justified_bvpos* m_justifications = nullptr; // do not alter this pointer (except setting it 0 for moving - we refer to it in trail)
unsigned m_unset = 0; // For constant pdds this value is completely ignored/not maintained
public:
bitvec_info() = default;
bitvec_info(tbv_ref* bits) : m_bits(bits), m_justifications(alloc_vect<justified_bvpos>(bits->num_tbits())), m_unset(bits->num_tbits()) {}
bitvec_info(bitvec_info&& other) : m_bits(other.m_bits), m_justifications(other.m_justifications), m_unset(other.m_unset) {
other.m_bits = nullptr;
other.m_justifications = nullptr;
}
bitvec_info& operator=(bitvec_info&& other) {
m_bits = other.m_bits;
m_justifications = other.m_justifications;
m_unset = other.m_unset;
other.m_bits = nullptr;
other.m_justifications = nullptr;
return *this;
}
~bitvec_info() {
SASSERT((bool)m_bits == (bool)m_justifications);
if (!m_bits)
return;
unsigned cnt = num_tbits();
for (unsigned i = 0; i < cnt; i++) {
if (m_justifications[i].m_justification->can_dealloc())
dealloc(m_justifications[i].m_justification);
}
dealloc_svect(m_justifications);
dealloc(m_bits);
}
bool is_determined() const {
return m_unset == 0;
};
unsigned num_tbits() const {
return m_bits->num_tbits();
}
void inc_unset() {
SASSERT(m_unset < num_tbits());
m_unset++;
}
void dec_unset() {
SASSERT(m_unset > 0);
m_unset--;
}
rational get_value() const {
SASSERT(is_determined());
rational value(0);
unsigned cnt = num_tbits();
for (unsigned i = cnt; i > 0; i--) {
SASSERT((*m_bits)[i - 1] == BIT_0 || (*m_bits)[i - 1] == BIT_1);
value *= 2;
value += (*m_bits)[i - 1] == BIT_1;
}
return value;
}
void set_bit(unsigned idx, tbit v) {
SASSERT(v != BIT_x); // We don't use don't-cares
m_bits->manager().set(**m_bits, idx, v);
}
tbit get_bit(unsigned idx) const {
return (*m_bits)[idx];
}
const tbv_ref* get_tbv() const {
return m_bits;
}
justified_bvpos& justification(unsigned idx) {
return m_justifications[idx];
}
const justified_bvpos& justification(unsigned idx) const {
return m_justifications[idx];
}
};
class fixed_bits final {
friend bit_justication;
friend bit_justification;
solver& m_solver;
scoped_ptr_vector<tbv_manager> m_tbv_managers;
using pdd_to_tbv_key = optional<pdd>;
using pdd_to_tbv_eq = default_eq<pdd_to_tbv_key>;
struct pdd_to_tbv_hash {
unsigned operator()(pdd_to_tbv_key const& args) const {
return args ? args->hash() : 0;
using pdd_to_info_eq = default_eq<optional<pdd>>;
struct pdd_to_info_hash {
unsigned operator()(optional<pdd> const& args) const {
return args->hash();
}
};
using pdd_to_tbv_map = map<pdd_to_tbv_key, tbv_ref*, pdd_to_tbv_hash, pdd_to_tbv_eq>;
using bvpos_to_justification_eq = default_eq<bvpos>;
struct bvpos_to_justification_hash {
unsigned operator()(bvpos const& args) const {
return combine_hash(args.get_pdd().hash(), args.get_idx());
}
};
using bvpos_to_justification_map = map<bvpos, justified_bvpos, bvpos_to_justification_hash, bvpos_to_justification_eq>;
using pdd_to_info_map = map<optional<pdd>, bitvec_info, pdd_to_info_hash, pdd_to_info_eq>;
//vector<optional<tbv_ref>> m_var_to_tbv;
pdd_to_tbv_map m_var_to_tbv;
bvpos_to_justification_map m_bvpos_to_justification;
pdd_to_info_map m_pdd_to_info;
svector<justified_bvpos> m_trail;
svector<justified_bvpos*> m_trail;
unsigned_vector m_trail_size;
bool m_consistent = true; // in case evaluating results in a bit-conflict
tbv_manager& get_manager(const pdd& v);
tbv_manager& get_manager(const pdd& p);
tbv_manager& get_manager(unsigned sz);
clause_ref get_explanation(solver& s, bit_justication* j1, bit_justication* j2);
bool fix_value_core(const pdd& p, tbv_ref& tbv, unsigned idx, tbit val, bit_justication* j);
bool fix_bit(solver& s, const pdd& p, tbv_ref& tbv, unsigned idx, tbit val, bit_justication* j);
bitvec_info& get_bit_info(const pdd& p);
clause_ref get_explanation(solver& s, bit_justification** js, unsigned cnt, bool free, signed_constraint* consequence);
clause_ref get_explanation_assignment(solver& s, const pdd& p);
clause_ref get_explanation_conflict(solver& s, bit_justification* j1, bit_justification* j2);
clause_ref get_explanation_conflict(solver& s, bit_justification* j); // Conflict with constant
bool fix_value_core(const pdd& p, bitvec_info& info, unsigned idx, tbit val, bit_justification* j);
void clear_value(const pdd& p, unsigned idx);
void replace_justification(const justified_bvpos& old_j, bit_justication* new_j);
void replace_justification(justified_bvpos& jstfc, bit_justification* new_j);
void propagate_to_subterm(solver& s, const pdd& p);
public:
fixed_bits(solver& s) : m_solver(s) {}
~fixed_bits() {
for (auto& tbv : m_var_to_tbv)
dealloc(tbv.m_value);
for (justified_bvpos& just : m_trail) {
if (just.m_justification->can_dealloc())
dealloc(just.m_justification);
}
}
tbv_ref* get_tbv(const pdd& p);
fixed_bits(solver& s) : m_solver(s) {}
const tbv_ref* get_tbv(const pdd& p);
// #count [min; max]
static std::pair<unsigned, unsigned> leading_zeros(const tbv_ref& ref);
@ -262,11 +354,12 @@ namespace polysat {
tbit get_value(const pdd& p, unsigned idx); // More efficient than calling "eval" and accessing the returned tbv elements
// call this function also if we already know that the correct value is written there. We might decrease the decision level (for "replay")
bool fix_value(solver& s, const pdd& p, unsigned idx, tbit val, bit_justication* j);
bool fix_bit(solver& s, const pdd& p, unsigned idx, tbit val, bit_justification** j, bool recursive);
bool fix_bit(solver& s, const pdd& p, unsigned idx, tbit val, bit_justification* j, bool recursive);
void push();
void pop(unsigned pop_cnt = 1);
tbv_ref* eval(solver& s, const pdd& p);
const tbv_ref* eval(solver& s, const pdd& p);
};
}