3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2026-06-02 15:17:54 +00:00

remove lattice component

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
This commit is contained in:
Nikolaj Bjorner 2026-02-19 15:49:45 -08:00
parent 5bd7d93b55
commit 38ce0882db
5 changed files with 1 additions and 409 deletions

View file

@ -58,7 +58,6 @@ z3_add_component(smt
theory_dense_diff_logic.cpp theory_dense_diff_logic.cpp
theory_finite_set.cpp theory_finite_set.cpp
theory_finite_set_size.cpp theory_finite_set_size.cpp
theory_finite_set_lattice_refutation.cpp
theory_diff_logic.cpp theory_diff_logic.cpp
theory_dl.cpp theory_dl.cpp
theory_dummy.cpp theory_dummy.cpp

View file

@ -28,8 +28,7 @@ namespace smt {
theory(ctx, ctx.get_manager().mk_family_id("finite_set")), theory(ctx, ctx.get_manager().mk_family_id("finite_set")),
u(m), u(m),
m_axioms(m), m_rw(m), m_find(*this), m_axioms(m), m_rw(m), m_find(*this),
m_cardinality_solver(*this), m_cardinality_solver(*this)
m_lattice_refutation(*this)
{ {
// Setup the add_clause callback for axioms // Setup the add_clause callback for axioms
std::function<void(theory_axiom *)> add_clause_fn = std::function<void(theory_axiom *)> add_clause_fn =
@ -260,8 +259,6 @@ namespace smt {
ctx.push_trail(push_back_vector(m_eqs)); ctx.push_trail(push_back_vector(m_eqs));
m_find.merge(v1, v2); // triggers merge_eh, which triggers incremental generation of theory axioms m_find.merge(v1, v2); // triggers merge_eh, which triggers incremental generation of theory axioms
} }
if (ctx.get_fparams().m_finite_set_lattice_refutation)
m_lattice_refutation.add_equality(v1, v2);
// Check if Z3 has a boolean variable for it // Check if Z3 has a boolean variable for it
TRACE(finite_set, tout << "new_eq_eh_r1: " << n1->get_root() << "r2: "<< n2->get_root() <<"\n";); TRACE(finite_set, tout << "new_eq_eh_r1: " << n1->get_root() << "r2: "<< n2->get_root() <<"\n";);
@ -288,8 +285,6 @@ namespace smt {
ctx.push_trail(push_back_vector(m_diseqs)); ctx.push_trail(push_back_vector(m_diseqs));
m_axioms.extensionality_axiom(e1, e2); m_axioms.extensionality_axiom(e1, e2);
} }
if (ctx.get_fparams().m_finite_set_lattice_refutation)
m_lattice_refutation.add_disequality(v1,v2);
} }
// //

View file

@ -72,8 +72,6 @@ For ranges we can adapt a different model construction approach.
When introducing select and map, decidability can be lost. 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.
--*/ --*/
@ -87,7 +85,6 @@ the theory solver uses a stand-alone satisfiability checker for Boolean algebras
#include "util/union_find.h" #include "util/union_find.h"
#include "smt/smt_theory.h" #include "smt/smt_theory.h"
#include "smt/theory_finite_set_size.h" #include "smt/theory_finite_set_size.h"
#include "smt/theory_finite_set_lattice_refutation.h"
#include "model/finite_set_factory.h" #include "model/finite_set_factory.h"
namespace smt { namespace smt {
@ -97,7 +94,6 @@ namespace smt {
using th_union_find = union_find<theory_finite_set>; using th_union_find = union_find<theory_finite_set>;
friend class theory_finite_set_test; friend class theory_finite_set_test;
friend class theory_finite_set_size; friend class theory_finite_set_size;
friend class theory_finite_set_lattice_refutation;
friend struct finite_set_value_proc; friend struct finite_set_value_proc;
struct var_data { struct var_data {
@ -141,7 +137,6 @@ namespace smt {
th_union_find m_find; th_union_find m_find;
theory_clauses m_clauses; theory_clauses m_clauses;
theory_finite_set_size m_cardinality_solver; theory_finite_set_size m_cardinality_solver;
theory_finite_set_lattice_refutation m_lattice_refutation;
finite_set_factory *m_factory = nullptr; finite_set_factory *m_factory = nullptr;
obj_map<enode, obj_map<enode, bool> *> m_set_members; obj_map<enode, obj_map<enode, bool> *> m_set_members;
ptr_vector<func_decl> m_set_in_decls; ptr_vector<func_decl> m_set_in_decls;

View file

@ -1,318 +0,0 @@
/*++
Copyright (c) 2025 Lorenz Winkler
Module Name:
theory_finite_set_lattice_refutation.cpp
--*/
#include "smt/theory_finite_set_lattice_refutation.h"
#include "smt/smt_theory.h"
#include "smt/theory_finite_set.h"
#include "smt/smt_context.h"
#include <iostream>
const int NUM_WORDS = 5;
// some example have shown, the introduction of large conflict clauses can severely slow down refutation
const int MAX_DECISION_LITERALS = 10;
namespace smt {
reachability_matrix::reachability_matrix(context &ctx, theory_finite_set_lattice_refutation &t_lattice)
: reachable(NUM_WORDS * NUM_WORDS * 64, (uint64_t)0),
links(NUM_WORDS * NUM_WORDS * 64 * 64, {nullptr, nullptr}),
link_dls(NUM_WORDS * NUM_WORDS * 64 * 64, (uint64_t)0),
non_links(NUM_WORDS * NUM_WORDS * 64, (uint64_t)0),
non_link_justifications(NUM_WORDS * NUM_WORDS * 64 * 64, {nullptr, nullptr}),
largest_var(0),
max_size(NUM_WORDS * 64),
ctx(ctx),
t_lattice_refutation(t_lattice) {}
int reachability_matrix::get_max_var() {
return largest_var;
}
inline int reachability_matrix::get_word_index(int row, int col) const {
return (row * NUM_WORDS) + (col / 64);
};
inline uint64_t reachability_matrix::get_bitmask(int col) const {
return 1ull << (col % 64);
};
bool reachability_matrix::is_reachability_forbidden(theory_var source, theory_var dest) {
return non_links[get_word_index(source, dest)] & get_bitmask(dest);
}
bool reachability_matrix::in_bounds(theory_var source, theory_var dest) {
return source >= 0 && dest >= 0 && source < max_size && dest < max_size;
}
bool reachability_matrix::is_reachable(theory_var source, theory_var dest) const {
return reachable[get_word_index(source, dest)] & get_bitmask(dest);
}
bool reachability_matrix::is_linked(theory_var source, theory_var dest) {
return links[source * max_size + dest].first != nullptr;
}
bool reachability_matrix::bitwise_or_rows(int source_dest, int source) {
bool changes = false;
for (int i = 0; i < NUM_WORDS; i++) {
uint64_t old_value = reachable[source_dest * NUM_WORDS + i];
uint64_t new_value = reachable[source_dest * NUM_WORDS + i] | reachable[source * NUM_WORDS + i];
if (old_value == new_value) {
continue;
}
ctx.push_trail(value_trail(reachable[source_dest * NUM_WORDS + i]));
reachable[source_dest * NUM_WORDS + i] = new_value;
changes = true;
check_reachability_conflict_word(source_dest, i);
}
return changes;
}
bool reachability_matrix::set_reachability(theory_var source, theory_var dest, enode_pair reachability_witness) {
if (!in_bounds(source, dest) || is_reachable(source, dest)) {
return false;
}
ctx.push_trail(value_trail(largest_var));
largest_var = std::max({largest_var, source, dest});
int word_idx = get_word_index(source, dest);
ctx.push_trail(value_trail(reachable[word_idx]));
reachable[word_idx] |= get_bitmask(dest);
ctx.push_trail(value_trail(links[source * max_size + dest]));
links[source * max_size + dest] = reachability_witness;
ctx.push_trail(value_trail(link_dls[source * max_size + dest]));
TRACE(finite_set, tout << "set_reachability(" << source << "," << dest << "), dl: " << ctx.get_scope_level());
link_dls[source * max_size + dest] = ctx.get_scope_level();
check_reachability_conflict(source, dest);
// update reachability of source
bitwise_or_rows(source, dest);
for (int i = 0; i <= largest_var; i++) { // update reachability of i to the nodes reachable from dest
if (!is_reachable(i, source) || i == source) {
continue;
}
bitwise_or_rows(i, source);
}
if (conflict_word >= 0 && conflict_row >= 0) {
for (int i = conflict_word * 64; i < conflict_word * 64 + 64; i++) {
check_reachability_conflict(conflict_row, i);
}
conflict_word = -1;
conflict_row = -1;
}
return true;
}
bool reachability_matrix::set_non_reachability(theory_var source, theory_var dest,
enode_pair non_reachability_witness) {
if (is_reachability_forbidden(source, dest)) {
return false;
}
ctx.push_trail(value_trail(largest_var));
largest_var = std::max({largest_var, source, dest});
ctx.push_trail(value_trail(non_links[get_word_index(source, dest)]));
non_links[get_word_index(source, dest)] |= get_bitmask(dest);
ctx.push_trail(value_trail(non_link_justifications[source * max_size + dest]));
non_link_justifications[source * max_size + dest] = non_reachability_witness;
check_reachability_conflict(source, dest);
return true;
}
theory_finite_set_lattice_refutation::theory_finite_set_lattice_refutation(theory_finite_set &th)
: m(th.m), ctx(th.ctx), th(th), u(m), bs(m), m_assumption(m), reachability(th.ctx, *this) {}
// determines if the two enodes capture a subset relation:
// checks, whether intersect_expr = intersect(subset, return_value) for some return value
// otherwise return null
enode *theory_finite_set_lattice_refutation::get_superset(enode *subset, enode *intersect_expr) {
expr *arg1 = nullptr, *arg2 = nullptr;
if (u.is_intersect(intersect_expr->get_expr(), arg1, arg2)) {
if (arg1 == subset->get_expr()) {
return ctx.get_enode(arg2);
}
if (arg2 == subset->get_expr()) {
return ctx.get_enode(arg1);
}
}
return nullptr;
}
void theory_finite_set_lattice_refutation::add_equality(theory_var v1, theory_var v2) {
auto n1 = th.get_enode(v1);
auto n2 = th.get_enode(v2);
enode *subset = n1;
enode *superset = get_superset(n1, n2);
if (superset == nullptr) {
subset = n2;
superset = get_superset(n2, n1);
}
if (superset == nullptr) {
add_set_equality(n1, n2);
return;
}
TRACE(finite_set, tout << "new_eq_intersection: " << enode_pp(subset, ctx) << "(" << th.get_th_var(subset)
<< ")" << "\\subseteq " << enode_pp(superset, ctx) << "(" << th.get_th_var(superset)
<< ")");
add_subset(subset->get_th_var(th.get_id()), superset->get_th_var(th.get_id()), {n1, n2});
};
std::pair<vector<enode_pair>, int> reachability_matrix::get_path(theory_var source, theory_var dest) {
SASSERT(is_reachable(source, dest));
vector<enode_pair> path;
vector<bool> visited(max_size, false);
if (source != dest) {
visited[source] = true;
}
int num_decisions = 0;
do {
bool success = false;
// TRACE(finite_set, tout << "get_path:source: "<<source);
for (int i = 0; i <= largest_var; i++) {
if (!visited[i] && is_linked(source, i) && ((is_reachable(i, dest)) || i == dest)) {
path.push_back(links[source * max_size + i]);
if (link_dls[source * max_size + i] != 0) {
num_decisions += 1;
}
source = i;
visited[source] = true;
success = true;
break;
}
}
SASSERT(success);
} while (source != dest);
TRACE(finite_set, tout << "get_path_num_decisions: " << num_decisions);
return {std::move(path), num_decisions};
}
bool reachability_matrix::check_reachability_conflict(theory_var source, theory_var dest) {
if (is_reachable(source, dest) && is_reachability_forbidden(source, dest)) {
TRACE(finite_set, tout << "found_conflict1: " << source << " -> " << dest);
auto [path, num_decisions] = get_path(source, dest);
// TRACE(finite_set, tout << "found path: "<<source<<" -> "<<dest<<" length: "<<path.size());
if (num_decisions <= MAX_DECISION_LITERALS) {
TRACE(finite_set, tout << "num_decisions: " << num_decisions << " path_length: " << path.size());
enode_pair diseq = non_link_justifications[source * max_size + dest];
t_lattice_refutation.trigger_conflict(path, diseq);
}
return true;
}
return false;
}
bool reachability_matrix::check_reachability_conflict_word(int row, int word) {
if (reachable[row * NUM_WORDS + word] & non_links[row * NUM_WORDS + word]) {
// somewhere in this word there is a conflict
conflict_row = row;
conflict_word = word;
return true;
}
return false;
}
void reachability_matrix::display_relations(std::ostream& out) const {
out << "largest_var: " << largest_var << "\n";
for (int i = 0; i < max_size; i++) {
for (int j = 0; j < max_size; j++) {
if ((reachable[get_word_index(i, j)] & get_bitmask(j)) || is_reachable(i, j)) {
out << "reachable: " << i << "->" << j << " :" << is_reachable(i, j) << "\n";
}
}
}
}
void theory_finite_set_lattice_refutation::trigger_conflict(vector<enode_pair> equalities,
enode_pair clashing_disequality) {
auto eq_expr =
m.mk_not(m.mk_eq(clashing_disequality.first->get_expr(), clashing_disequality.second->get_expr()));
auto disequality_literal = ctx.get_literal(eq_expr);
auto j1 = ext_theory_conflict_justification(th.get_id(), ctx, 1, &disequality_literal, equalities.size(),
equalities.data());
auto justification = ctx.mk_justification(j1);
TRACE(finite_set, tout << "conflict_literal: " << disequality_literal);
TRACE(finite_set, tout << "setting_partial_order_conflict");
ctx.set_conflict(justification);
}
void theory_finite_set_lattice_refutation::add_disequality(theory_var v1, theory_var v2) {
auto n1 = th.get_enode(v1);
auto n2 = th.get_enode(v2);
enode *subset = n1;
enode *superset = get_superset(n1, n2);
if (!superset) {
subset = n2;
superset = get_superset(n2, n1);
}
if (!superset)
return;
TRACE(finite_set, tout << "new_diseq_intersection: " << enode_pp(subset, ctx) << "(" << th.get_th_var(subset)
<< ")" << "\\not\\subseteq " << enode_pp(superset, ctx) << "(" << th.get_th_var(superset)
<< ")");
add_not_subset(subset->get_th_var(th.get_id()), superset->get_th_var(th.get_id()), {n1, n2});
};
void theory_finite_set_lattice_refutation::add_subset(theory_var subset_th, theory_var superset_th,
enode_pair justifying_equality) {
if (!reachability.in_bounds(subset_th, superset_th)) {
return;
}
if (subset_th == null_theory_var || superset_th == null_theory_var) {
return;
}
reachability.set_reachability(subset_th, superset_th, justifying_equality);
SASSERT(reachability.is_reachable(subset_th, superset_th));
if (reachability.is_reachable(superset_th, subset_th)) {
TRACE(finite_set, tout << "cycle_detected: " << subset_th << " <--> " << superset_th);
auto [path, num_decisions] = reachability.get_path(subset_th, subset_th);
// we propagate the equality
// build justification to be used by all propagated equalities
auto j1 = ctx.mk_justification(
ext_theory_conflict_justification(th.get_id(), ctx, 0, nullptr, path.size(), path.data()));
for (unsigned i = 0; i < path.size() - 1; i++) {
auto set1 = path[i].first;
auto set2 = path[i + 1].first;
ctx.add_eq(set1, set2, eq_justification(j1));
TRACE(finite_set, tout << "added_equality: " << set1 << " == " << set2);
}
}
};
void theory_finite_set_lattice_refutation::add_not_subset(theory_var subset_th, theory_var superset_th,
enode_pair justifying_disequality) {
if (!reachability.in_bounds(subset_th, superset_th)) {
return;
}
if (subset_th == null_theory_var || superset_th == null_theory_var) {
return;
}
reachability.set_non_reachability(subset_th, superset_th, justifying_disequality);
SASSERT(reachability.is_reachability_forbidden(subset_th, superset_th));
}
void theory_finite_set_lattice_refutation::add_set_equality(enode *set1, enode *set2) {
theory_var set1_th = set1->get_th_var(th.get_id());
theory_var set2_th = set2->get_th_var(th.get_id());
if (!reachability.in_bounds(set1_th, set2_th)) {
return;
}
reachability.set_reachability(set1_th, set2_th, {set1, set2});
SASSERT(reachability.is_reachable(set1_th, set2_th));
reachability.set_reachability(set2_th, set1_th, {set2, set1});
SASSERT(reachability.is_reachable(set2_th, set1_th));
}
} // namespace smt

View file

@ -1,79 +0,0 @@
/*++
Copyright (c) 2025 Lorenz Winkler
Module Name:
theory_finite_lattice_refutation.h
--*/
#pragma once
#include "ast/finite_set_decl_plugin.h"
#include "smt/smt_theory.h"
namespace smt {
class context;
class theory_finite_set;
class theory_finite_set_lattice_refutation;
class reachability_matrix {
svector<uint64_t> reachable; // V x V -> bitset of reachable nodes
enode_pair_vector links; // V x V -> enode_pair justifying the link
svector<uint64_t> link_dls; // V x V -> decision level when the link was added
svector<uint64_t> non_links; // V x V -> bitset of non-reachable nodes
enode_pair_vector non_link_justifications; // V x V -> enode_pair justifying the non-link
int largest_var;
int max_size;
context &ctx;
theory_finite_set_lattice_refutation &t_lattice_refutation;
int conflict_row = -1;
int conflict_word = -1;
// sets source_dest |= dest, and pushing the changed words to the trail
bool bitwise_or_rows(int source_dest, int source);
inline int get_word_index(int row, int col) const;
inline uint64_t get_bitmask(int col) const;
public:
std::pair<vector<enode_pair>, int> get_path(theory_var source, theory_var dest);
reachability_matrix(context &ctx, theory_finite_set_lattice_refutation &t_lattice);
bool in_bounds(theory_var source, theory_var dest);
bool is_reachable(theory_var source, theory_var dest) const;
bool is_reachability_forbidden(theory_var source, theory_var dest);
bool is_linked(theory_var source, theory_var dest);
bool check_reachability_conflict(theory_var source, theory_var dest);
bool check_reachability_conflict_word(int row, int word);
bool set_reachability(theory_var source, theory_var dest, enode_pair reachability_witness);
bool set_non_reachability(theory_var source, theory_var dest, enode_pair non_reachability_witness);
int get_max_var();
void display_relations(std::ostream& out) const;
};
class theory_finite_set_lattice_refutation {
ast_manager &m;
context &ctx;
theory_finite_set &th;
finite_set_util u;
expr_ref_vector bs;
expr_ref m_assumption;
reachability_matrix reachability;
enode *get_superset(enode *, enode *);
void add_subset(theory_var subset, theory_var superset, enode_pair justifying_equality);
void add_not_subset(theory_var subset, theory_var superset, enode_pair justifying_disequality);
void propagate_new_subset(theory_var v1, theory_var v2);
void add_set_equality(enode *set1, enode *set2);
public:
void trigger_conflict(vector<enode_pair> equalities, enode_pair clashing_disequality);
theory_finite_set_lattice_refutation(theory_finite_set &th);
void add_equality(theory_var v1, theory_var v2);
void add_disequality(theory_var v1, theory_var v2);
};
} // namespace smt