3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-08-26 21:16:02 +00:00
z3/src/muz/spacer/spacer_cluster.cpp
LeeYoungJoon 0a93ff515d
Centralize and document TRACE tags using X-macros (#7657)
* Introduce X-macro-based trace tag definition
- Created trace_tags.def to centralize TRACE tag definitions
- Each tag includes a symbolic name and description
- Set up enum class TraceTag for type-safe usage in TRACE macros

* Add script to generate Markdown documentation from trace_tags.def
- Python script parses trace_tags.def and outputs trace_tags.md

* Refactor TRACE_NEW to prepend TraceTag and pass enum to is_trace_enabled

* trace: improve trace tag handling system with hierarchical tagging

- Introduce hierarchical tag-class structure: enabling a tag class activates all child tags
- Unify TRACE, STRACE, SCTRACE, and CTRACE under enum TraceTag
- Implement initial version of trace_tag.def using X(tag, tag_class, description)
  (class names and descriptions to be refined in a future update)

* trace: replace all string-based TRACE tags with enum TraceTag
- Migrated all TRACE, STRACE, SCTRACE, and CTRACE macros to use enum TraceTag values instead of raw string literals

* trace : add cstring header

* trace : Add Markdown documentation generation from trace_tags.def via mk_api_doc.py

* trace : rename macro parameter 'class' to 'tag_class' and remove Unicode comment in trace_tags.h.

* trace : Add TODO comment for future implementation of tag_class activation

* trace : Disable code related to tag_class until implementation is ready (#7663).
2025-05-28 14:31:25 +01:00

398 lines
13 KiB
C++

/*++
Copyright (c) 2020 Arie Gurfinkel
Module Name:
spacer_cluster.cpp
Abstract:
Discover and mark lemma clusters
Author:
Hari Govind V K
Arie Gurfinkel
--*/
#include <algorithm>
#include "ast/arith_decl_plugin.h"
#include "ast/ast_util.h"
#include "ast/rewriter/var_subst.h"
#include "ast/substitution/substitution.h"
#include "muz/spacer/spacer_antiunify.h"
#include "muz/spacer/spacer_cluster.h"
#include "muz/spacer/spacer_context.h"
#include "muz/spacer/spacer_manager.h"
#include "muz/spacer/spacer_util.h"
#include "smt/tactic/unit_subsumption_tactic.h"
#include "util/mpq.h"
#include "util/vector.h"
#define MAX_CLUSTER_SIZE 5
#define MAX_CLUSTERS 5
#define GAS_INIT 10
namespace spacer {
using var_offset = std::pair<unsigned, unsigned>;
lemma_cluster::lemma_cluster(const expr_ref &pattern)
: m(pattern.get_manager()), m_arith(m), m_bv(m), m_ref_count(0),
m_pattern(pattern), m_matcher(m), m_gas(GAS_INIT) {
m_num_vars = get_num_vars(m_pattern);
}
lemma_cluster::lemma_cluster(const lemma_cluster &other)
: m(other.get_manager()), m_arith(m), m_bv(m), m_ref_count(0),
m_pattern(other.get_pattern()), m_num_vars(other.m_num_vars),
m_matcher(m), m_gas(other.get_gas()) {
for (const auto &li : other.get_lemmas()) { m_lemma_vec.push_back(li); }
}
/// Get a conjunction of all the lemmas in cluster
void lemma_cluster::get_conj_lemmas(expr_ref &e) const {
expr_ref_vector conj(m);
for (const auto &lem : get_lemmas()) {
conj.push_back(lem.get_lemma()->get_expr());
}
e = mk_and(conj);
}
bool lemma_cluster::contains(const lemma_ref &lemma) {
for (const auto &li : get_lemmas()) {
if (lemma->get_expr() == li.get_lemma()->get_expr()) return true;
}
return false;
}
unsigned lemma_cluster::get_min_lvl() {
if (m_lemma_vec.empty()) return 0;
unsigned lvl = m_lemma_vec[0].get_lemma()->level();
for (auto l : m_lemma_vec) { lvl = std::min(lvl, l.get_lemma()->level()); }
// if all lemmas are at infinity, use the level of the lowest pob
if (is_infty_level(lvl)) {
for (auto l : m_lemma_vec) {
if (l.get_lemma()->has_pob())
lvl = std::min(lvl, l.get_lemma()->get_pob()->level());
}
}
return lvl;
}
/// Checks whether \p e matches the pattern of the cluster
/// Returns true on success and set \p sub to the corresponding substitution
bool lemma_cluster::match(const expr_ref &e, substitution &sub) {
bool pos;
var_offset var;
expr_offset r;
m_matcher.reset();
bool is_match = m_matcher(m_pattern, e, sub, pos);
if (!(is_match && pos)) return false;
unsigned n_binds = sub.get_num_bindings();
auto is_numeral = [&](expr *e) {
return m_arith.is_numeral(e) || m_bv.is_numeral(e);
};
// All the matches should be numerals
for (unsigned i = 0; i < n_binds; i++) {
sub.get_binding(i, var, r);
if (!is_numeral(r.get_expr())) return false;
}
return true;
}
bool lemma_cluster::can_contain(const lemma_ref &lemma) {
substitution sub(m);
expr_ref cube(m);
sub.reserve(1, m_num_vars);
cube = mk_and(lemma->get_cube());
normalize_order(cube, cube);
return match(cube, sub);
}
lemma_cluster::lemma_info *
lemma_cluster::get_lemma_info(const lemma_ref &lemma) {
SASSERT(contains(lemma));
for (auto &li : m_lemma_vec) {
if (lemma == li.get_lemma()) { return &li; }
}
UNREACHABLE();
return nullptr;
}
/// Removes subsumed lemmas in the cluster
///
/// Removed lemmas are placed into \p removed_lemmas
void lemma_cluster::rm_subsumed(lemma_info_vector &removed_lemmas) {
removed_lemmas.reset();
if (m_lemma_vec.size() <= 1) return;
// set up and run the simplifier
tactic_ref simplifier = mk_unit_subsumption_tactic(m);
goal_ref g(alloc(goal, m, false, false, false));
goal_ref_buffer result;
for (auto l : m_lemma_vec) { g->assert_expr(l.get_lemma()->get_expr()); }
(*simplifier)(g, result);
SASSERT(result.size() == 1);
goal *r = result[0];
// nothing removed
if (r->size() == m_lemma_vec.size()) return;
// collect removed lemmas
lemma_info_vector keep;
for (auto lem : m_lemma_vec) {
bool found = false;
for (unsigned i = 0; i < r->size(); i++) {
if (lem.get_lemma()->get_expr() == r->form(i)) {
found = true;
keep.push_back(lem);
TRACE(cluster_stats_verb, tout << "Keeping lemma "
<< lem.get_lemma()->get_cube()
<< "\n";);
break;
}
}
if (!found) {
TRACE(cluster_stats_verb, tout << "Removing subsumed lemma "
<< lem.get_lemma()->get_cube()
<< "\n";);
removed_lemmas.push_back(lem);
}
}
m_lemma_vec.reset();
m_lemma_vec.append(keep);
}
/// A a lemma to a cluster
///
/// Removes subsumed lemmas if \p subs_check is true
///
/// Returns false if lemma does not match the pattern or if it is already in the
/// cluster. Repetition of lemmas is avoided by doing a linear scan over the
/// lemmas in the cluster. Adding a lemma can reduce the size of the cluster due
/// to subsumption reduction.
bool lemma_cluster::add_lemma(const lemma_ref &lemma, bool subsume) {
substitution sub(m);
expr_ref cube(m);
sub.reserve(1, m_num_vars);
cube = mk_and(lemma->get_cube());
normalize_order(cube, cube);
if (!match(cube, sub)) return false;
// cluster already contains the lemma
if (contains(lemma)) return false;
TRACE(cluster_stats_verb,
tout << "Trying to add lemma " << lemma->get_cube() << "\n";);
lemma_cluster::lemma_info li(lemma, sub);
m_lemma_vec.push_back(li);
if (subsume) {
lemma_info_vector removed_lemmas;
rm_subsumed(removed_lemmas);
for (auto rm : removed_lemmas) {
// There is going to at most one removed lemma that matches l_i
// if there is one, return false since the new lemma was not added
if (rm.get_lemma() == li.get_lemma()) return false;
}
}
TRACE(cluster_stats, tout << "Added lemma\n" << mk_and(lemma->get_cube()) << "\n"
<< "to existing cluster\n" << m_pattern << "\n";);
return true;
}
lemma_cluster_finder::lemma_cluster_finder(ast_manager &_m)
: m(_m), m_arith(m), m_bv(m) {}
/// Check whether \p cube and \p lcube differ only in interpreted constants
bool lemma_cluster_finder::are_neighbours(const expr_ref &cube1,
const expr_ref &cube2) {
SASSERT(is_ground(cube1));
SASSERT(is_ground(cube2));
anti_unifier antiunify(m);
expr_ref pat(m);
substitution sub1(m), sub2(m);
antiunify(cube1, cube2, pat, sub1, sub2);
SASSERT(sub1.get_num_bindings() == sub2.get_num_bindings());
return is_numeric_sub(sub1) && is_numeric_sub(sub2);
}
/// Compute antiunification of \p cube with all formulas in \p fmls.
///
/// Should return
/// \exist res (\forall f \in fmls (\exist i_sub res[i_sub] == f))
/// However, the algorithm is incomplete: it returns such a res iff
/// res \in {antiU(cube, e) | e \in fmls}
/// Returns true if res is found
/// TODO: do complete n-ary anti-unification. Not done now
/// because anti_unifier does not support free variables
bool lemma_cluster_finder::anti_unify_n_intrp(const expr_ref &cube,
expr_ref_vector &fmls,
expr_ref &res) {
expr_ref_vector patterns(m);
expr_ref pat(m);
anti_unifier antiunify(m);
substitution sub1(m), sub2(m);
TRACE(cluster_stats_verb,
tout << "Trying to generate a general pattern for " << cube
<< " neighbours are " << fmls << "\n";);
// collect candidates for res
for (expr *c : fmls) {
antiunify.reset();
sub1.reset();
sub2.reset();
SASSERT(are_neighbours(cube, {c, m}));
antiunify(cube, expr_ref(c, m), pat, sub1, sub2);
patterns.push_back(pat);
}
// go through all the patterns to see if there is a pattern which is general
// enough to include all lemmas.
bool is_general_pattern = false, pos = true, all_same = true;
sem_matcher matcher(m);
unsigned n_vars_pat = 0;
for (expr *e : patterns) {
TRACE(cluster_stats_verb,
tout << "Checking pattern " << mk_pp(e, m) << "\n";);
is_general_pattern = true;
n_vars_pat = get_num_vars(e);
all_same = all_same && n_vars_pat == 0;
for (auto *lcube : fmls) {
matcher.reset();
sub1.reset();
sub1.reserve(1, n_vars_pat);
if (!(matcher(e, lcube, sub1, pos) && pos)) {
// this pattern is no good
is_general_pattern = false;
break;
}
}
if (is_general_pattern) {
SASSERT(e != nullptr);
TRACE(cluster_stats,
tout << "Found a general pattern\n" << mk_pp(e, m) << "\n";);
// found a good pattern
res = expr_ref(e, m);
return true;
}
}
CTRACE(cluster_stats, !all_same,
tout << "Failed to find a general pattern for cluster. Cube is: "
<< cube << " Patterns are " << patterns << "\n";);
return false;
}
/// Add a new lemma \p lemma to a cluster
///
/// Creates a new cluster for the lemma if necessary
void lemma_cluster_finder::cluster(lemma_ref &lemma) {
scoped_watch _w_(m_st.watch);
pred_transformer &pt = (lemma->get_pob())->pt();
// check whether lemmas has already been added
if (pt.clstr_contains(lemma)) return;
/// Add the lemma to a cluster it is matched against
lemma_cluster *clstr = pt.clstr_match(lemma);
if (clstr && clstr->get_size() <= MAX_CLUSTER_SIZE) {
TRACE(cluster_stats_verb, {
tout << "Trying to add lemma\n" << lemma->get_cube()
<< " to an existing cluster\n";
for (auto lem : clstr->get_lemmas())
tout << lem.get_lemma()->get_cube() << "\n";
});
clstr->add_lemma(lemma);
return;
}
/// Dont create more than MAX_CLUSTERS number of clusters
if (clstr && pt.clstr_count(clstr->get_pattern()) > MAX_CLUSTERS) {
return;
}
// Try to create a new cluster with lemma even if it can belong to an
// oversized cluster. The new cluster will not contain any lemma that is
// already in another cluster.
lemma_ref_vector all_lemmas;
pt.get_all_lemmas(all_lemmas, false);
expr_ref lcube(m), cube(m);
lcube = mk_and(lemma->get_cube());
normalize_order(lcube, lcube);
expr_ref_vector lma_cubes(m);
lemma_ref_vector neighbours;
for (auto *l : all_lemmas) {
cube.reset();
cube = mk_and(l->get_cube());
normalize_order(cube, cube);
// make sure that l is not in any other clusters
if (are_neighbours(lcube, cube) && cube != lcube &&
!pt.clstr_contains(l)) {
neighbours.push_back(l);
lma_cubes.push_back(cube);
}
}
if (neighbours.empty()) return;
// compute the most general pattern to which lemmas fit
expr_ref pattern(m);
bool is_cluster = anti_unify_n_intrp(lcube, lma_cubes, pattern);
// no general pattern
if (!is_cluster || get_num_vars(pattern) == 0) return;
// When creating a cluster, its size can be more than MAX_CLUSTER_SIZE. The
// size limitation is only for adding new lemmas to the cluster. The size is
// just an arbitrary number.
// What matters is that we do not allow a cluster to grow indefinitely.
// for example, given a cluster in which one lemma subsumes all other
// lemmas. No matter how big the cluster is, GSpacer is going to produce the
// exact same pob on this cluster. This can lead to divergence. The
// subsumption check we do is based on unit propagation, it is not complete.
lemma_cluster *cluster = pt.mk_cluster(pattern);
TRACE(cluster_stats,
tout << "created new cluster with pattern:\n" << pattern << "\n"
<< " and lemma cube:\n" << lcube << "\n";);
IF_VERBOSE(2, verbose_stream() << "\ncreated new cluster with pattern: "
<< pattern << "\n"
<< " and lemma cube: " << lcube << "\n";);
for (auto l : neighbours) {
SASSERT(cluster->can_contain(l));
bool added = cluster->add_lemma(l, false);
(void)added;
CTRACE(cluster_stats, added,
tout << "Added neighbour lemma\n" << mk_and(l->get_cube()) << "\n";);
}
// finally add the lemma and do subsumption check
cluster->add_lemma(lemma, true);
SASSERT(cluster->get_size() >= 1);
}
void lemma_cluster_finder::collect_statistics(statistics &st) const {
st.update("time.spacer.solve.reach.cluster", m_st.watch.get_seconds());
}
} // namespace spacer