3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-10-31 19:52:29 +00:00
z3/src/smt/theory_finite_set.h
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

203 lines
8 KiB
C++

/*++
Copyright (c) 2025 Microsoft Corporation
Module Name:
theory_finite_set.h
Abstract:
The theory solver relies on instantiating axiom schemas for finite sets.
The instantation rules can be represented as implementing inference rules
that encode the semantics of set operations.
It reduces satisfiability into a combination of satisfiability of arithmetic and uninterpreted functions.
This module implements axiom schemas that are invoked by saturating constraints
with respect to the semantics of set operations.
Let v1 ~ v2 mean that v1 and v2 are congruent
The set-based decision procedure relies on saturating with respect
to rules of the form:
x in v1 a term, v1 ~ set.empty
-----------------------------------
not (x in set.empty)
x in v1 a term , v1 ~ v3, v3 := (set.union v4 v5)
-----------------------------------------------
x in v3 <=> x in v4 or x in v5
x in v1 a term, v1 ~ v3, v3 := (set.intersect v4 v5)
---------------------------------------------------
x in v3 <=> x in v4 and x in v5
x in v1 a term, v1 ~ v3, v3 := (set.difference v4 v5)
---------------------------------------------------
x in v3 <=> x in v4 and not (x in v5)
x in v1 a term, v1 ~ v3, v3 := (set.singleton v4)
-----------------------------------------------
x in v3 <=> x == v4
x in v1 a term, v1 ~ v3, v3 := (set.range lo hi)
-----------------------------------------------
x in v3 <=> (lo <= x <= hi)
x in v1 a term, v1 ~ v3, v3 := (set.map f v4)
-----------------------------------------------
x in v3 <=> set.map_inverse(f, x, v4) in v4
x in v1 a term, v1 ~ v3, v3 := (set.map f v4)
-----------------------------------------------
x in v4 => f(x) in v3
x in v1 a tern, v1 ~ v3, v3 := (set.select p v4)
-----------------------------------------------
x in v3 <=> p(x) and x in v4
Rules are encoded in src/ast/rewriter/finite_set_axioms.cpp as clauses.
The central claim is that the above rules are sufficient to
decide satisfiability of finite set constraints for a subset
of operations, namely union, intersection, difference, singleton, membership.
Model construction proceeds by selecting every set.in(x_i, v) for a
congruence root v. Then the set of elements { x_i | set.in(x_i, v) }
is the interpretation.
This approach for model-construction, however, does not work with ranges, or is impractical.
For ranges we can adapt a different model construction approach.
When introducing select and map, decidability can be lost.
For Boolean lattice constraints given by equality, subset, strict subset and union, intersections,
the theory solver uses a stand-alone satisfiability checker for Boolean algebras to close branches.
Instructions for copilot:
1. Override relevant methods for smt::theory. Add them to the signature and add stubs or implementations in
theory_finite_set.cpp.
2. In final_check_eh add instantiation of theory axioms following the outline in the inference rules above.
An example of how to instantiate axioms is in theory_arrays_base.cpp and theroy_datatypes.cpp and other theory files.
--*/
#pragma once
#include "ast/ast.h"
#include "ast/ast_pp.h"
#include "ast/finite_set_decl_plugin.h"
#include "ast/rewriter/finite_set_axioms.h"
#include "util/obj_pair_hashtable.h"
#include "util/union_find.h"
#include "smt/smt_theory.h"
#include "model/finite_set_factory.h"
namespace smt {
class theory_finite_set : public theory {
using th_union_find = union_find<theory_finite_set>;
friend class theory_finite_set_test;
friend struct finite_set_value_proc;
friend class th_union_find;
struct var_data {
ptr_vector<enode> m_setops;
ptr_vector<enode> m_parent_in;
ptr_vector<enode> m_parent_setops;
};
struct theory_clauses {
vector<expr_ref_vector> axioms; // vector of created theory axioms
unsigned aqhead = 0; // queue head of created axioms
unsigned_vector squeue; // propagation queue of axioms to be added to the solver
unsigned sqhead = 0; // head into propagation queue axioms to be added to solver
obj_pair_hashtable<expr, expr> members; // set of membership axioms that were instantiated
vector<unsigned_vector> watch; // watch list from expression index to clause occurrence
bool can_propagate() const {
return sqhead < squeue.size() || aqhead < axioms.size();
}
};
struct stats {
unsigned m_num_axioms_created = 0;
unsigned m_num_axioms_conflicts = 0;
unsigned m_num_axioms_propagated = 0;
unsigned m_num_axioms_case_splits = 0;
void collect_statistics(::statistics & st) const {
st.update("finite-set-axioms-created", m_num_axioms_created);
st.update("finite-set-axioms-propagated", m_num_axioms_propagated);
st.update("finite-set-axioms-conflicts", m_num_axioms_conflicts);
st.update("finite-set-axioms-case-splits", m_num_axioms_case_splits);
}
};
finite_set_util u;
finite_set_axioms m_axioms;
th_union_find m_find;
theory_clauses m_clauses;
finite_set_factory *m_factory = nullptr;
obj_map<enode, obj_hashtable<enode> *> m_set_members;
ptr_vector<func_decl> m_set_in_decls;
ptr_vector<var_data> m_var_data;
stats m_stats;
protected:
// Override relevant methods from smt::theory
bool internalize_atom(app * atom, bool gate_ctx) override;
bool internalize_term(app * term) override;
void new_eq_eh(theory_var v1, theory_var v2) override;
void new_diseq_eh(theory_var v1, theory_var v2) override;
void apply_sort_cnstr(enode *n, sort *s) override;
final_check_status final_check_eh() override;
bool can_propagate() override;
void propagate() override;
void assign_eh(bool_var v, bool is_true) override;
theory * mk_fresh(context * new_ctx) override;
char const * get_name() const override { return "finite_set"; }
void display(std::ostream & out) const override;
void init_model(model_generator & mg) override;
model_value_proc * mk_value(enode * n, model_generator & mg) override;
theory_var mk_var(enode *n) override;
void collect_statistics(::statistics & st) const override {
m_stats.collect_statistics(st);
}
void add_in_axioms(theory_var v1, theory_var v2);
void add_in_axioms(enode *in, var_data *d);
// Helper methods for axiom instantiation
void add_membership_axioms(expr* elem, expr* set);
void add_clause(expr_ref_vector const& clause);
bool assert_clause(expr_ref_vector const &clause);
void activate_clause(unsigned index);
bool activate_unasserted_clause();
void add_immediate_axioms(app *atom);
bool assume_eqs();
bool is_new_axiom(expr *a, expr *b);
app *mk_union(unsigned num_elems, expr *const *elems, sort* set_sort);
// model construction
void collect_members();
void reset_set_members();
// manage union-find of theory variables
theory_var find(theory_var v) const { return m_find.find(v); }
bool is_root(theory_var v) const { return m_find.is_root(v); }
trail_stack &get_trail_stack();
void merge_eh(theory_var v1, theory_var v2, theory_var, theory_var);
void after_merge_eh(theory_var r1, theory_var r2, theory_var v1, theory_var v2) {}
void unmerge_eh(theory_var v1, theory_var v2) {}
std::ostream &display_var(std::ostream &out, theory_var v) const;
public:
theory_finite_set(context& ctx);
~theory_finite_set() override;
};
} // namespace smt