3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2026-07-15 03:25:43 +00:00

seq_split: dedup identical <D,N> pairs in intersect (sound, memory-only)

A split-set denotes the UNION of its pairs, so identical (hash-consed) <D,N> pairs are redundant; skipping them in the De Morgan cross-product cuts the materialized split-set ~7x on L15 negcount (65536 -> 9216) with no behaviour change (verified L11 sat / L14 unsat). NOTE: memory-only -- it does not fix the underlying combinatorial blow-up of complement()'s structural De Morgan on a concatenation of predicates (root cause; see spec analysis). Adds nseq-split-dedup-drops counter. Easily reverted if a derivative/minterm-based complement supersedes the cross-product.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Margus Veanes 2026-07-03 19:40:26 +03:00
parent 8992d5fc53
commit 07c797c32b
3 changed files with 13 additions and 0 deletions

View file

@ -20,6 +20,7 @@ Author:
#include "ast/rewriter/seq_rewriter.h"
#include "ast/ast_pp.h"
#include "util/obj_hashtable.h"
#include "util/obj_pair_hashtable.h"
#include "util/stack.h"
seq_split::seq_split(seq_rewriter& rw) :
@ -191,6 +192,10 @@ bool seq_split::intersect(split_set const& s1, split_set const& s2, split_set& r
unsigned threshold, split_oracle const& oracle) const {
++m_stats.m_intersect;
const seq_util::rex& r = re();
// Dedup the cross-product: a split-set denotes the UNION of its <D,N> pairs,
// so identical (perfectly-shared) pairs are redundant. Skipping them keeps
// the De Morgan fold from accumulating exponentially many equal splits.
obj_pair_hashtable<expr, expr> seen;
for (auto const& p1 : s1) {
for (auto const& p2 : s2) {
if (r.is_empty(p1.m_d) || r.is_empty(p2.m_d) ||
@ -199,6 +204,12 @@ bool seq_split::intersect(split_set const& s1, split_set const& s2, split_set& r
const expr_ref di(m_rw.mk_regex_inter_normalize(p1.m_d, p2.m_d), m);
const expr_ref ni(m_rw.mk_regex_inter_normalize(p1.m_n, p2.m_n), m);
++m_stats.m_intersect_pairs;
std::pair<expr*, expr*> key(di.get(), ni.get());
if (seen.contains(key)) {
++m_stats.m_dedup_drops;
continue;
}
seen.insert(key);
push(result, oracle, di, ni);
if (result.size() > threshold) {
++m_stats.m_threshold_overruns;

View file

@ -72,6 +72,7 @@ struct split_stats {
unsigned m_giveups = 0; // iterator give-ups (unsupported/weak/overrun)
unsigned m_threshold_overruns = 0; // threshold hits (intersect/complement/iterator)
unsigned m_max_split_set = 0; // largest materialized split-set seen
unsigned m_dedup_drops = 0; // duplicate <D,N> pairs skipped in intersect
unsigned m_simplify = 0; // simplify() calls
void reset() { *this = split_stats(); }
};

View file

@ -6019,6 +6019,7 @@ namespace seq {
st.update("nseq split giveups", sp.m_giveups);
st.update("nseq split threshold-overruns", sp.m_threshold_overruns);
st.update("nseq split max-split-set", sp.m_max_split_set);
st.update("nseq split dedup-drops", sp.m_dedup_drops);
st.update("nseq split simplify", sp.m_simplify);
}