diff --git a/src/smt/CMakeLists.txt b/src/smt/CMakeLists.txt index 01e3a9254..6d0e86b77 100644 --- a/src/smt/CMakeLists.txt +++ b/src/smt/CMakeLists.txt @@ -57,6 +57,7 @@ z3_add_component(smt theory_char.cpp theory_datatype.cpp theory_dense_diff_logic.cpp + theory_finite_set.cpp theory_diff_logic.cpp theory_dl.cpp theory_dummy.cpp diff --git a/src/smt/smt_setup.cpp b/src/smt/smt_setup.cpp index d655316ed..926818d78 100644 --- a/src/smt/smt_setup.cpp +++ b/src/smt/smt_setup.cpp @@ -40,6 +40,7 @@ Revision History: #include "smt/theory_pb.h" #include "smt/theory_fpa.h" #include "smt/theory_polymorphism.h" +#include "smt/theory_finite_set.h" namespace smt { @@ -784,6 +785,10 @@ namespace smt { m_context.register_plugin(alloc(smt::theory_char, m_context)); } + void setup::setup_finite_set() { + m_context.register_plugin(alloc(smt::theory_finite_set, m_context)); + } + void setup::setup_special_relations() { m_context.register_plugin(alloc(smt::theory_special_relations, m_context, m_manager)); } @@ -807,6 +812,7 @@ namespace smt { setup_dl(); setup_seq_str(st); setup_fpa(); + setup_finite_set(); setup_special_relations(); setup_polymorphism(); setup_relevancy(st); diff --git a/src/smt/smt_setup.h b/src/smt/smt_setup.h index 3d2bf47f3..897755ef7 100644 --- a/src/smt/smt_setup.h +++ b/src/smt/smt_setup.h @@ -102,6 +102,7 @@ namespace smt { void setup_seq_str(static_features const & st); void setup_seq(); void setup_char(); + void setup_finite_set(); void setup_card(); void setup_sls(); void setup_i_arith(); diff --git a/src/smt/theory_finite_set.cpp b/src/smt/theory_finite_set.cpp new file mode 100644 index 000000000..72b25d16a --- /dev/null +++ b/src/smt/theory_finite_set.cpp @@ -0,0 +1,209 @@ +/*++ +Copyright (c) 2025 Microsoft Corporation + +Module Name: + + theory_finite_set.cpp + +Abstract: + + Theory solver for finite sets. + Implements axiom schemas for finite set operations. + +Author: + + GitHub Copilot Agent 2025 + +Revision History: + +--*/ + +#include "smt/theory_finite_set.h" +#include "smt/smt_context.h" +#include "smt/smt_model_generator.h" +#include "ast/ast_pp.h" + +namespace smt { + + theory_finite_set::theory_finite_set(context& ctx): + theory(ctx, ctx.get_manager().mk_family_id("finite_set")), + u(m), + m_axioms(m) + { + // Setup the add_clause callback for axioms + std::function add_clause_fn = + [this](expr_ref_vector const& clause) { + this->m_lemmas.push_back(clause); + }; + m_axioms.set_add_clause(add_clause_fn); + } + + bool theory_finite_set::internalize_atom(app * atom, bool gate_ctx) { + TRACE("finite_set", tout << "internalize_atom: " << mk_pp(atom, m) << "\n";); + + internalize_term(atom); + + // Track membership elements (set.in) + expr* elem = nullptr, *set = nullptr; + if (u.is_in(atom, elem, set)) { + auto n = ctx.get_enode(elem); + if (!m_elements.contains(n)) { + m_elements.insert(n); + ctx.push_trail(insert_obj_trail(n)); + } + } + + return true; + } + + 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)); + + return true; + } + + 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";); + // When two sets are equal, propagate membership constraints + // This is handled by congruence closure, so no additional work needed here + } + + 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";); + // Disequalities could trigger extensionality axioms + // For now, we rely on the final_check to handle this + } + + final_check_status theory_finite_set::final_check_eh() { + TRACE("finite_set", tout << "final_check_eh\n";); + + // walk all parents of elem in congruence table. + // if a parent is of the form elem' in S u T, or similar. + // create clauses for elem in S u T. + + expr* elem1 = nullptr, *set1 = nullptr; + m_lemmas.reset(); + for (auto elem : m_elements) { + for (auto p : enode::parents(elem)) { + if (!u.is_in(p->get_expr(), elem1, set1)) + continue; + if (elem->get_root() != p->get_arg(0)->get_root()) + continue; // elem is then equal to set1 but not elem1. This is a different case. + for (auto sib : *p->get_arg(1)) + instantiate_axioms(elem->get_expr(), sib->get_expr()); + } + } + if (instantiate_false_lemma()) + return FC_CONTINUE; + if (instantiate_unit_propagation()) + return FC_CONTINUE; + if (instantiate_free_lemma()) + return FC_CONTINUE; + + return FC_DONE; + } + + void theory_finite_set::instantiate_axioms(expr* elem, expr* set) { + TRACE("finite_set", tout << "instantiate_axioms: " << mk_pp(elem, m) << " in " << mk_pp(set, m) << "\n";); + + // 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); + } + + // Instantiate size axioms for singleton sets + // TODO, such axioms don't belong here + if (u.is_singleton(set)) { + m_axioms.size_singleton_axiom(set); + } + } + + void theory_finite_set::add_clause(expr_ref_vector const& clause) { + TRACE("finite_set", + tout << "add_clause: " << clause << "\n"); + + // Convert expressions to literals and assert the clause + literal_vector lits; + for (expr* e : clause) { + ctx.internalize(e, false); + literal lit = ctx.get_literal(lit_expr); + lits.push_back(lit); + } + + if (!lits.empty()) { + scoped_trace_stream _sts(*this, lits); + ctx.mk_th_axiom(get_id(), lits); + } + } + + 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"; + } + + 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 + } + + 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";); + + // For now, return nullptr to use default model construction + // A complete implementation would construct explicit set values + // based on true membership literals + return nullptr; + } + + void theory_finite_set::instantiate_false_lemma() {} + void theory_finite_set::instantiate_unit_propagation() {} + void theory_finite_set::instantiate_free_lemma() {} + +} // namespace smt diff --git a/src/smt/theory_finite_set.h b/src/smt/theory_finite_set.h index 4666dd988..59a8651a7 100644 --- a/src/smt/theory_finite_set.h +++ b/src/smt/theory_finite_set.h @@ -86,13 +86,42 @@ theory_finite_set.cpp. #include "ast/ast.h" #include "ast/ast_pp.h" +#include "ast/finite_set_decl_plugin.h" +#include "ast/rewriter/finite_set_axioms.h" #include "smt/smt_theory.h" namespace smt { class theory_finite_set : public theory { + friend class theory_finite_set_test; + finite_set_util u; + finite_set_axioms m_axioms; + obj_hashtable m_elements; // set of all 'x' where there is an 'x in S' atom + vector m_lemmas; + + 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; + final_check_status final_check_eh() 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; + + // Helper methods for axiom instantiation + void instantiate_axioms(expr* elem, expr* set); + void add_clause(expr_ref_vector const& clause); + void instantiate_false_lemma(); + void instantiate_unit_propagation(); + void instantiate_free_lemma(); + public: - theory_finite_set(ast_manager & m); + theory_finite_set(context& ctx); ~theory_finite_set() override {} }; -} // namespace smt \ No newline at end of file +} // namespace smt