3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-10-31 03:32:28 +00:00
z3/src/smt/theory_finite_set.cpp
Nikolaj Bjorner 43d40ac142 revise axiom instantiation scheme for finite-sets
Instead of asserting theory axioms lazily we create them on the fly and allow propagation eagerly.
The approach uses a waterfall model as follows:
- terms are created: they are inserted into an index for (set.in x S) axiom creation.
- two terms are merged by an equality.
  Loop over all new opportunities for axiom instantiation
  New axioms are added to a queue of recently created axioms.
- an atomic formula was asserted by the SAT solver.
  Update the watch list to find new propagations.

During propagation recently created axioms are either inserted into a propagation queue, or inserted into a watch list.
They are inserted into a propagation queue all or all but one literal is assigned to false.
They are inserted into a watch list if at least two literals are unassigned
They are dropped if the axiom contains a literal that is assigned to true

The propagation queue is processed by by asserting the theory axiom to the core.

Also add some elementary statistics.

A breaking change is to change the datatype for undo-trail in smt_context to not use a custom data-structure.
This can likely cause regressions. For example, the region allocator now comes from the stack_trail instead of being
owned within smt_context with a different declaration order. smt_context could crash during destruction or maybe even pop.
We take the risk as the change is overdue.

Add swap method to ref_vector.
2025-10-18 12:08:39 +02:00

759 lines
29 KiB
C++

/*++
Copyright (c) 2025 Microsoft Corporation
Module Name:
theory_finite_set.cpp
Abstract:
Theory solver for finite sets.
Implements axiom schemas for finite set operations.
--*/
#include "smt/theory_finite_set.h"
#include "smt/smt_context.h"
#include "smt/smt_model_generator.h"
#include "ast/ast_pp.h"
namespace smt {
/**
* Constructor.
* Set up callback that adds axiom instantiations as clauses.
**/
theory_finite_set::theory_finite_set(context& ctx):
theory(ctx, ctx.get_manager().mk_family_id("finite_set")),
u(m),
m_axioms(m), m_find(*this)
{
// Setup the add_clause callback for axioms
std::function<void(expr_ref_vector const &)> add_clause_fn =
[this](expr_ref_vector const& clause) {
this->add_clause(clause);
};
m_axioms.set_add_clause(add_clause_fn);
}
theory_finite_set::~theory_finite_set() {
reset_set_members();
}
void theory_finite_set::reset_set_members() {
for (auto [k, s] : m_set_members)
dealloc(s);
m_set_members.reset();
}
/**
* When creating a theory variable, we associate extra data structures with it.
* if n := (set.in x S)
* then for every T in the equivalence class of S (including S), assert theory axioms for x in T.
*
* if n := (setop T U)
* then for every (set.in x S) where either S ~ T, S ~ U, assert theory axioms for x in n.
* Since n is fresh there are no other (set.in x S) with S ~ n in the state.
*
* if n := (set.filter p S)
* then for every (set.in x T) where S ~ T, assert theory axiom for x in (set.filter p S)
*
* if n := (set.map f S)
* then for every (set.in x T) where S ~ T, assert theory axiom for (set.in x S) and map.
* In other words, assert
* (set.in (f x) (set.map f S))
*/
theory_var theory_finite_set::mk_var(enode *n) {
theory_var r = theory::mk_var(n);
VERIFY(r == static_cast<theory_var>(m_find.mk_var()));
SASSERT(r == static_cast<int>(m_var_data.size()));
m_var_data.push_back(alloc(var_data));
ctx.push_trail(push_back_vector<ptr_vector<var_data>>(m_var_data));
ctx.push_trail(new_obj_trail(m_var_data.back()));
expr *e = n->get_expr();
if (u.is_in(e)) {
auto set = n->get_arg(1);
auto v = set->get_root()->get_th_var(get_id());
SASSERT(v != null_theory_var);
m_var_data[v]->m_parent_in.push_back(n);
ctx.push_trail(push_back_trail(m_var_data[v]->m_parent_in));
add_in_axioms(n, m_var_data[v]); // add axioms x in S x S ~ T, T := setop, or T is arg of setop.
auto f = to_app(e)->get_decl();
if (!m_set_in_decls.contains(f)) {
m_set_in_decls.push_back(f);
ctx.push_trail(push_back_vector(m_set_in_decls));
}
}
else if (u.is_union(e) || u.is_intersect(e) ||
u.is_difference(e) || u.is_singleton(e) ||
u.is_empty(e) || u.is_range(e)) {
m_var_data[r]->m_setops.push_back(n);
ctx.push_trail(push_back_trail(m_var_data[r]->m_setops));
for (auto arg : enode::args(n)) {
if (!u.is_finite_set(arg->get_expr()))
continue;
auto v = arg->get_root()->get_th_var(get_id());
SASSERT(v != null_theory_var);
// add axioms for x in S, e := setop S T ...
for (auto in : m_var_data[v]->m_parent_in)
add_membership_axioms(in->get_arg(0)->get_expr(), e);
m_var_data[v]->m_parent_setops.push_back(n);
ctx.push_trail(push_back_trail(m_var_data[v]->m_parent_setops));
}
}
else if (u.is_map(e) || u.is_select(e)) {
NOT_IMPLEMENTED_YET();
}
return r;
}
trail_stack& theory_finite_set::get_trail_stack() {
return ctx.get_trail_stack();
}
/*
* Merge the equivalence classes of two variables.
* parent_in := vector of (set.in x S) terms where S is in the equivalence class of r.
* parent_setops := vector of (set.op S T) where S or T is in the equivalence class of r.
* setops := vector of (set.op S T) where (set.op S T) is in the equivalence class of r.
*
*/
void theory_finite_set::merge_eh(theory_var root, theory_var other, theory_var, theory_var) {
// r is the new root
TRACE(finite_set, tout << "merging v" << root << " v" << other << "\n"; display_var(tout, root);
tout << " <- " << mk_pp(get_enode(other)->get_expr(), m) << "\n";);
SASSERT(root == find(root));
var_data *d_root= m_var_data[root];
var_data *d_other = m_var_data[other];
ctx.push_trail(restore_vector(d_root->m_setops));
ctx.push_trail(restore_vector(d_root->m_parent_setops));
ctx.push_trail(restore_vector(d_root->m_parent_in));
add_in_axioms(root, other);
add_in_axioms(other, root);
d_root->m_setops.append(d_other->m_setops);
d_root->m_parent_setops.append(d_other->m_parent_setops);
d_root->m_parent_in.append(d_other->m_parent_in);
TRACE(finite_set, tout << "after merge\n"; display_var(tout, root););
}
/*
* for each (set.in x S) in d1->parent_in,
* add axioms for (set.in x S)
*/
void theory_finite_set::add_in_axioms(theory_var v1, theory_var v2) {
auto d1 = m_var_data[v1];
auto d2 = m_var_data[v2];
for (enode *in : d1->m_parent_in)
add_in_axioms(in, d2);
}
/*
* let (set.in x S)
*
* for each T := (set.op U V) in d2->parent_setops
* then S ~ U or S ~ V by construction
* add axioms for (set.in x T)
*
* for each T := (set.op U V) in d2->setops
* then S ~ T by construction
* add axioms for (set.in x T)
*/
void theory_finite_set::add_in_axioms(enode *in, var_data *d) {
SASSERT(u.is_in(in->get_expr()));
auto e = in->get_arg(0)->get_expr();
auto set1 = in->get_arg(1);
for (enode *setop : d->m_parent_setops) {
SASSERT(
any_of(enode::args(setop), [&](enode *arg) { return in->get_arg(1)->get_root() == arg->get_root(); }));
add_membership_axioms(e, setop->get_expr());
}
for (enode *setop : d->m_setops) {
SASSERT(in->get_arg(1)->get_root() == setop->get_root());
add_membership_axioms(e, setop->get_expr());
}
}
/**
* Boolean atomic formulas for finite sets are one of:
* (set.in x S)
* (set.subset S T)
* When an atomic formula is first created it is to be registered with the solver.
* The internalize_atom method takes care of this.
* Atomic formulas are special cases of terms (of non-Boolean type) so they are registered as terms.
*
*/
bool theory_finite_set::internalize_atom(app * atom, bool gate_ctx) {
return internalize_term(atom);
}
/**
* When terms are registered with the solver , we need to ensure that:
* - All subterms have an associated E-node
* - Boolean terms are registered as boolean variables
* Registering a Boolean variable ensures that the solver will be notified about its truth value.
* - Non-Boolean terms have an associated theory variable
* Registering a theory variable ensures that the solver will be notified about equalities and disequalites.
* The solver can use the theory variable to track auxiliary information about E-nodes.
*/
bool theory_finite_set::internalize_term(app * term) {
TRACE(finite_set, tout << "internalize_term: " << mk_pp(term, m) << "\n";);
// Internalize all arguments first
for (expr* arg : *term)
ctx.internalize(arg, false);
// Create boolean variable for Boolean terms
if (m.is_bool(term) && !ctx.b_internalized(term)) {
bool_var bv = ctx.mk_bool_var(term);
ctx.set_var_theory(bv, get_id());
}
// Create enode for the term if needed
enode* e = nullptr;
if (ctx.e_internalized(term))
e = ctx.get_enode(term);
else
e = ctx.mk_enode(term, false, m.is_bool(term), true);
// Attach theory variable if this is a set
if (!is_attached_to_var(e))
ctx.attach_th_var(e, this, mk_var(e));
// Assert immediate axioms
// if (!ctx.relevancy())
add_immediate_axioms(term);
return true;
}
void theory_finite_set::apply_sort_cnstr(enode* n, sort* s) {
SASSERT(u.is_finite_set(s));
if (!is_attached_to_var(n))
ctx.attach_th_var(n, this, mk_var(n));
}
void theory_finite_set::new_eq_eh(theory_var v1, theory_var v2) {
TRACE(finite_set, tout << "new_eq_eh: v" << v1 << " = v" << v2 << "\n";);
m_find.merge(v1, v2); // triggers merge_eh, which triggers incremental generation of theory axioms
}
/**
* Every dissequality has to be supported by at distinguishing element.
*
* TODO: we can avoid instantiating the extensionality axiom if we know statically that e1, e2
* can never be equal (say, they have different cardinalities or they are finite sets by construction
* with elements that can differentiate the sets)
*/
void theory_finite_set::new_diseq_eh(theory_var v1, theory_var v2) {
TRACE(finite_set, tout << "new_diseq_eh: v" << v1 << " != v" << v2 << "\n");
auto n1 = get_enode(v1);
auto n2 = get_enode(v2);
auto e1 = n1->get_expr();
auto e2 = n2->get_expr();
if (u.is_finite_set(e1) && u.is_finite_set(e2)) {
if (e1->get_id() > e2->get_id())
std::swap(e1, e2);
if (!is_new_axiom(e1, e2))
return;
m_axioms.extensionality_axiom(e1, e2);
}
}
/**
* Final check for the finite set theory.
* The Final Check method is called when the solver has assigned truth values to all Boolean variables.
* It is responsible for asserting any remaining axioms and checking for inconsistencies.
*
* It ensures saturation with respect to the theory axioms:
* - membership axioms
* - assume eqs axioms
*/
final_check_status theory_finite_set::final_check_eh() {
TRACE(finite_set, tout << "final_check_eh\n";);
if (activate_unasserted_clause())
return FC_CONTINUE;
if (assume_eqs())
return FC_CONTINUE;
return FC_DONE;
}
/**
* Add immediate axioms that can be asserted when the atom is created.
* These are unit clauses that can be added immediately.
* - (set.in x set.empty) is false
* - (set.subset S T) <=> (= (set.union S T) T) (or (= (set.intersect S T) S))
*
* Other axioms:
* - (set.singleton x) -> (set.in x (set.singleton x))
* - (set.singleton x) -> (set.size (set.singleton x)) = 1
* - (set.empty) -> (set.size (set.empty)) = 0
*/
void theory_finite_set::add_immediate_axioms(app* term) {
expr *elem = nullptr, *set = nullptr;
unsigned sz = m_clauses.axioms.size();
if (u.is_in(term, elem, set) && u.is_empty(set))
add_membership_axioms(elem, set);
else if (u.is_subset(term))
m_axioms.subset_axiom(term);
else if (u.is_singleton(term, elem))
m_axioms.in_singleton_axiom(elem, term);
// Assert all new lemmas as clauses
for (unsigned i = sz; i < m_clauses.axioms.size(); ++i)
m_clauses.squeue.push_back(i);
}
void theory_finite_set::assign_eh(bool_var v, bool is_true) {
TRACE(finite_set, tout << "assign_eh: v" << v << " is_true: " << is_true << "\n";);
expr *e = ctx.bool_var2expr(v);
// retrieve the watch list for clauses where e appears with opposite polarity
unsigned idx = 2 * e->get_id() + (is_true ? 1 : 0);
if (idx >= m_clauses.watch.size())
return;
// walk the watch list and try to find new watches or propagate
unsigned j = 0;
for (unsigned i = 0; i < m_clauses.watch[idx].size(); ++i) {
TRACE(finite_set, tout << " watch[" << i << "] size: " << m_clauses.watch[i].size() << "\n";);
auto clause_idx = m_clauses.watch[idx][i];
auto &clause = m_clauses.axioms[clause_idx];
if (any_of(clause, [&](expr *lit) { return ctx.find_assignment(lit) == l_true; })) {
TRACE(finite_set, tout << " satisfied\n";);
m_clauses.watch[idx][j++] = clause_idx;
continue; // clause is already satisfied
}
auto is_complement_to = [&](bool is_true, expr* lit, expr* arg) {
if (is_true)
return m.is_not(lit) && to_app(lit)->get_arg(0) == arg;
else
return lit == arg;
};
auto lit1 = clause.get(0);
auto lit2 = clause.get(1);
auto position = 0;
if (is_complement_to(is_true, lit1, e))
position = 0;
else {
SASSERT(is_complement_to(is_true, lit2, e));
position = 1;
}
bool found_swap = false;
for (unsigned k = 2; k < clause.size(); ++k) {
expr *lit = clause.get(k);
if (ctx.find_assignment(lit) == l_false)
continue;
// found a new watch
clause.swap(position, k);
// std::swap(clause[position], clause[k]);
bool litneg = m.is_not(lit, lit);
auto litid = 2 * lit->get_id() + litneg;
m_clauses.watch.reserve(litid + 1);
m_clauses.watch[litid].push_back(clause_idx);
TRACE(finite_set, tout << " new watch for " << mk_pp(lit, m) << "\n";);
found_swap = true;
break;
}
if (found_swap)
continue; // the clause is removed from this watch list
// either all literals are false, or the other watch literal is propagating.
m_clauses.squeue.push_back(clause_idx);
TRACE(finite_set, tout << " propagate clause\n";);
m_clauses.watch[idx][j++] = clause_idx;
++i;
for (; i < m_clauses.watch[idx].size(); ++i)
m_clauses.watch[idx][j++] = m_clauses.watch[idx][i];
break;
}
m_clauses.watch[idx].shrink(j);
}
bool theory_finite_set::can_propagate() {
return m_clauses.can_propagate();
}
void theory_finite_set::propagate() {
TRACE(finite_set, tout << "propagate\n";);
ctx.push_trail(value_trail(m_clauses.aqhead));
ctx.push_trail(value_trail(m_clauses.sqhead));
while (can_propagate() && !ctx.inconsistent()) {
// activate newly created clauses
while (m_clauses.aqhead < m_clauses.axioms.size())
activate_clause(m_clauses.aqhead++);
// empty the propagation queue of clauses to assert
while (m_clauses.sqhead < m_clauses.squeue.size() && !ctx.inconsistent()) {
auto index = m_clauses.squeue[m_clauses.sqhead++];
auto const &clause = m_clauses.axioms[index];
assert_clause(clause);
}
}
}
void theory_finite_set::activate_clause(unsigned clause_idx) {
TRACE(finite_set, tout << "activate_clause: " << clause_idx << "\n";);
auto &clause = m_clauses.axioms[clause_idx];
if (any_of(clause, [&](expr *e) { return ctx.find_assignment(e) == l_true; }))
return;
if (clause.size() <= 1) {
m_clauses.squeue.push_back(clause_idx);
return;
}
expr *w1 = nullptr, *w2 = nullptr;
for (unsigned i = 0; i < clause.size(); ++i) {
expr *lit = clause.get(i);
switch (ctx.find_assignment(lit)) {
case l_true:
UNREACHABLE();
return;
case l_false:
break;
case l_undef:
if (!w1) {
w1 = lit;
clause.swap(0, i);
}
else if (!w2) {
w2 = lit;
clause.swap(1, i);
}
break;
}
}
if (!w2) {
m_clauses.squeue.push_back(clause_idx);
return;
}
bool w1neg = m.is_not(w1, w1);
bool w2neg = m.is_not(w2, w2);
unsigned w1id = 2 * w1->get_id() + w1neg;
unsigned w2id = 2 * w2->get_id() + w2neg;
unsigned sz = std::max(w1id, w2id) + 1;
m_clauses.watch.reserve(sz);
m_clauses.watch[w1id].push_back(clause_idx);
m_clauses.watch[w2id].push_back(clause_idx);
struct unwatch_clause : public trail {
theory_finite_set &th;
unsigned index;
unwatch_clause(theory_finite_set &th, unsigned index) : th(th), index(index) {}
void undo() override {
auto &clause = th.m_clauses.axioms[index];
expr *w1 = clause.get(0);
expr *w2 = clause.get(1);
bool w1neg = th.m.is_not(w1, w1);
bool w2neg = th.m.is_not(w2, w2);
unsigned w1id = 2 * w1->get_id() + w1neg;
unsigned w2id = 2 * w2->get_id() + w2neg;
auto &watch1 = th.m_clauses.watch[w1id];
auto &watch2 = th.m_clauses.watch[w2id];
watch1.erase(index);
watch2.erase(index);
}
};
ctx.push_trail(unwatch_clause(*this, clause_idx));
}
/**
* Saturate with respect to equality sharing:
* - Sets corresponding to shared variables having the same interpretation should also be congruent
*/
bool theory_finite_set::assume_eqs() {
collect_members();
expr_ref_vector trail(m); // make sure reference counts to union expressions are valid in this scope
obj_map<expr, enode*> set_reprs;
auto start = ctx.get_random_value();
auto sz = get_num_vars();
for (unsigned w = 0; w < sz; ++w) {
auto v = (w + start) % sz;
enode* n = get_enode(v);
if (!u.is_finite_set(n->get_expr()))
continue;
if (!is_relevant_and_shared(n))
continue;
auto r = n->get_root();
// Create a union expression that is canonical (sorted)
auto& set = *m_set_members[r];
ptr_vector<expr> elems;
for (auto e : set)
elems.push_back(e->get_expr());
std::sort(elems.begin(), elems.end(), [](expr *a, expr *b) { return a->get_id() < b->get_id(); });
expr *s = mk_union(elems.size(), elems.data(), n->get_expr()->get_sort());
trail.push_back(s);
enode *n2 = nullptr;
if (!set_reprs.find(s, n2)) {
set_reprs.insert(s, n2);
continue;
}
if (n2->get_root() == r)
continue;
if (is_new_axiom(n->get_expr(), n2->get_expr()) && assume_eq(n, n2)) {
TRACE(finite_set,
tout << "assume " << mk_pp(n->get_expr(), m) << " = " << mk_pp(n2->get_expr(), m) << "\n";);
return true;
}
}
return false;
}
app* theory_finite_set::mk_union(unsigned num_elems, expr* const* elems, sort* set_sort) {
app *s = nullptr;
for (unsigned i = 0; i < num_elems; ++i)
s = s ? u.mk_union(s, u.mk_singleton(elems[i])) : u.mk_singleton(elems[i]);
return s ? s : u.mk_empty(set_sort);
}
bool theory_finite_set::is_new_axiom(expr* a, expr* b) {
struct insert_obj_pair_table : public trail {
obj_pair_hashtable<expr, expr> &table;
expr *a, *b;
insert_obj_pair_table(obj_pair_hashtable<expr, expr> &t, expr *a, expr *b) : table(t), a(a), b(b) {}
void undo() override {
table.erase({a, b});
}
};
if (m_clauses.members.contains({a, b}))
return false;
m_clauses.members.insert({a, b});
ctx.push_trail(insert_obj_pair_table(m_clauses.members, a, b));
return true;
}
/**
* Instantiate axioms for a given element in a set.
*/
void theory_finite_set::add_membership_axioms(expr *elem, expr *set) {
TRACE(finite_set, tout << "add_membership_axioms: " << mk_pp(elem, m) << " in " << mk_pp(set, m) << "\n";);
if (!is_new_axiom(elem, set))
return;
// Instantiate appropriate axiom based on set structure
if (u.is_empty(set)) {
m_axioms.in_empty_axiom(elem);
}
else if (u.is_singleton(set)) {
m_axioms.in_singleton_axiom(elem, set);
}
else if (u.is_union(set)) {
m_axioms.in_union_axiom(elem, set);
}
else if (u.is_intersect(set)) {
m_axioms.in_intersect_axiom(elem, set);
}
else if (u.is_difference(set)) {
m_axioms.in_difference_axiom(elem, set);
}
else if (u.is_range(set)) {
m_axioms.in_range_axiom(elem, set);
}
else if (u.is_map(set)) {
m_axioms.in_map_axiom(elem, set);
m_axioms.in_map_image_axiom(elem, set);
}
else if (u.is_select(set)) {
m_axioms.in_select_axiom(elem, set);
}
}
void theory_finite_set::add_clause(expr_ref_vector const& clause) {
TRACE(finite_set, tout << "add_clause: " << clause << "\n");
ctx.push_trail(push_back_vector(m_clauses.axioms));
m_clauses.axioms.push_back(clause);
m_stats.m_num_axioms_created++;
}
theory * theory_finite_set::mk_fresh(context * new_ctx) {
return alloc(theory_finite_set, *new_ctx);
}
void theory_finite_set::display(std::ostream & out) const {
out << "theory_finite_set:\n";
for (unsigned i = 0; i < m_clauses.axioms.size(); ++i)
out << "[" << i << "]: " << m_clauses.axioms[i] << "\n";
for (unsigned v = 0; v < get_num_vars(); ++v)
display_var(out, v);
for (unsigned i = 0; i < m_clauses.watch.size(); ++i) {
if (m_clauses.watch[i].empty())
continue;
out << "watch[" << i << "] := " << m_clauses.watch[i] << "\n";
}
}
void theory_finite_set::init_model(model_generator & mg) {
TRACE(finite_set, tout << "init_model\n";);
// Model generation will use default interpretation for sets
// The model will be constructed based on the membership literals that are true
m_factory = alloc(finite_set_factory, m, u.get_family_id(), mg.get_model());
mg.register_factory(m_factory);
collect_members();
}
void theory_finite_set::collect_members() {
// This method can be used to collect all elements that are members of sets
// and ensure that the model factory has values for them.
// For now, we rely on the default model construction.
reset_set_members();
for (auto f : m_set_in_decls) {
for (auto n : ctx.enodes_of(f)) {
SASSERT(u.is_in(n->get_expr()));
auto x = n->get_arg(0);
if (!ctx.is_relevant(x))
continue;
x = x->get_root();
if (x->is_marked())
continue;
x->set_mark(); // make sure we only do this once per element
for (auto p : enode::parents(x)) {
if (!ctx.is_relevant(p))
continue;
if (!u.is_in(p->get_expr()))
continue;
if (ctx.get_assignment(p->get_expr()) != l_true)
continue;
auto set = p->get_arg(1)->get_root();
auto elem = p->get_arg(0)->get_root();
if (elem != x)
continue;
if (!m_set_members.contains(set))
m_set_members.insert(set, alloc(obj_hashtable<enode>));
m_set_members.find(set)->insert(x);
}
}
}
for (auto f : m_set_in_decls) {
for (auto n : ctx.enodes_of(f)) {
SASSERT(u.is_in(n->get_expr()));
auto x = n->get_arg(0);
x = x->get_root();
if (x->is_marked())
x->unset_mark();
}
}
}
struct finite_set_value_proc : model_value_proc {
theory_finite_set &th;
sort *s = nullptr;
obj_hashtable<enode>* m_elements = nullptr;
finite_set_value_proc(theory_finite_set &th, sort *s, obj_hashtable<enode> *elements)
: th(th), s(s), m_elements(elements) {}
void get_dependencies(buffer<model_value_dependency> &result) override {
if (!m_elements)
return;
for (auto v : *m_elements)
result.push_back(model_value_dependency(v));
}
app *mk_value(model_generator &mg, expr_ref_vector const &values) override {
SASSERT(values.empty() == !m_elements);
if (values.empty())
return th.u.mk_empty(s);
SASSERT(m_elements);
SASSERT(values.size() == m_elements->size());
return th.mk_union(values.size(), values.data(), s);
}
};
model_value_proc * theory_finite_set::mk_value(enode * n, model_generator & mg) {
TRACE(finite_set, tout << "mk_value: " << mk_pp(n->get_expr(), m) << "\n";);
obj_hashtable<enode>*elements = nullptr;
sort *s = n->get_expr()->get_sort();
m_set_members.find(n->get_root(), elements);
return alloc(finite_set_value_proc, *this, s, elements);
}
/**
* a theory axiom can be unasserted if it contains two or more literals that have
* not been internalized yet.
*/
bool theory_finite_set::activate_unasserted_clause() {
for (auto const &clause : m_clauses.axioms) {
if (assert_clause(clause))
return true;
}
return false;
}
bool theory_finite_set::assert_clause(expr_ref_vector const &clause) {
expr *unit = nullptr;
unsigned undef_count = 0;
for (auto e : clause) {
switch (ctx.find_assignment(e)) {
case l_true:
return false; // clause is already satisfied
case l_false:
break;
case l_undef:
++undef_count;
unit = e;
break;
}
}
if (undef_count == 1) {
TRACE(finite_set, tout << " propagate unit: " << mk_pp(unit, m) << "\n" << clause << "\n";);
auto lit = mk_literal(unit);
literal_vector core;
for (auto e : clause) {
if (e != unit)
core.push_back(~mk_literal(e));
}
m_stats.m_num_axioms_propagated++;
ctx.assign(lit, ctx.mk_justification(
theory_propagation_justification(get_id(), ctx, core.size(), core.data(), lit)));
return true;
}
bool is_conflict = (undef_count == 0);
if (is_conflict)
m_stats.m_num_axioms_conflicts++;
else
m_stats.m_num_axioms_case_splits++;
TRACE(finite_set, tout << " assert " << (is_conflict ? "conflict" : "case split") << clause << "\n";);
literal_vector lclause;
for (auto e : clause)
lclause.push_back(mk_literal(e));
ctx.mk_th_axiom(get_id(), lclause);
return true;
}
std::ostream& theory_finite_set::display_var(std::ostream& out, theory_var v) const {
out << "v" << v << " := " << enode_pp(get_enode(v), ctx) << "\n";
auto d = m_var_data[v];
if (!d->m_setops.empty()) {
out << " setops: ";
for (auto n : d->m_setops)
out << enode_pp(n, ctx) << " ";
out << "\n";
}
if (!d->m_parent_setops.empty()) {
out << " parent_setops: ";
for (auto n : d->m_parent_setops)
out << enode_pp(n, ctx) << " ";
out << "\n";
}
if (!d->m_parent_in.empty()) {
out << " parent_in: ";
for (auto n : d->m_parent_in)
out << enode_pp(n, ctx) << " ";
out << "\n";
}
return out;
}
} // namespace smt