3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2026-07-19 05:25:48 +00:00

Try to do some processing already during assignments

This commit is contained in:
CEisenhofer 2026-06-27 12:53:57 +02:00
parent fc2124890e
commit e983bc76d4
7 changed files with 256 additions and 0 deletions

View file

@ -227,6 +227,7 @@ namespace smt {
m_prop_queue.push_back(eq_item(s1, s2, get_enode(v1), get_enode(v2), dep));
m_last_constraint_added = ctx.get_scope_level();
m_can_hot_restart = false;
++m_eager_dirty;
}
catch(const std::exception&) {
#ifdef Z3DEBUG
@ -274,6 +275,7 @@ namespace smt {
m_prop_queue.push_back(deq_item(s1, s2, ~ctx.get_literal(eq_expr), dep));
m_last_constraint_added = ctx.get_scope_level();
m_can_hot_restart = false;
++m_eager_dirty;
}
}
else
@ -300,6 +302,7 @@ namespace smt {
m_prop_queue.push_back(mem_item(sn_str, sn_re, lit, dep));
m_last_constraint_added = ctx.get_scope_level();
m_can_hot_restart = false;
++m_eager_dirty;
}
else {
// ¬(str ∈ R) ≡ str ∈ complement(R): store as a positive membership
@ -311,6 +314,7 @@ namespace smt {
m_prop_queue.push_back(mem_item(sn_str, sn_re_compl, lit, dep));
m_last_constraint_added = ctx.get_scope_level();
m_can_hot_restart = false;
++m_eager_dirty;
}
}
else if (m_seq.str.is_prefix(e)) {
@ -429,6 +433,11 @@ namespace smt {
try {
theory::pop_scope_eh(num_scopes);
m_sg.pop(num_scopes);
// The sgraph pop released snodes the incremental eager chain references;
// discard it so the next eager run rebuilds rather than extending a
// dangling chain (see eager_structural_check).
m_nielsen.eager_invalidate();
m_eager_processed = 0;
// A pop may remove constraints and/or unassign forced Nielsen
// literals; conservatively invalidate the cached SAT path.
if (m_can_hot_restart && ctx.get_scope_level() - num_scopes < m_last_constraint_added)
@ -484,6 +493,13 @@ namespace smt {
UNREACHABLE();
}
}
// Eager structural pruning: once the queue is drained, run a cheap
// branch-free Nielsen closure over the currently-asserted constraints to
// surface structural conflicts long before final_check. Sound because
// the current set is a subset of any completion (see eager_structural_check).
if (!ctx.inconsistent())
eager_structural_check();
}
catch(const std::exception&) {
#ifdef Z3DEBUG
@ -493,6 +509,78 @@ namespace smt {
}
}
// Rebuild-don't-undo, INCREMENTALLY: rather than maintaining the full Nielsen
// graph along the DPLL(T) trail (the error-prone undo bookkeeping that sank the
// earlier manual engine), we grow a single deterministic chain in `m_nielsen` as
// constraints arrive — each new constraint is folded into the current leaf with
// the chain's accumulated substitution applied (`eager_add_*`), then the chain is
// extended (`eager_close`). No per-propagation rebuild. Any structural UNSAT is
// a real conflict (subset-monotonicity); we never declare SAT or short-circuit
// final_check. The chain is discarded on pop (`eager_invalidate`) and whenever
// `m_nielsen.reset()` runs (e.g. final_check's `populate_nielsen_graph`), after
// which it is rebuilt from scratch on the next call.
void theory_nseq::eager_structural_check() {
if (!get_fparams().m_nseq_eager)
return;
// Only re-run when the Nielsen-relevant constraint set actually grew.
if (m_eager_dirty == m_eager_seen)
return;
m_eager_seen = m_eager_dirty;
// (Re)start the chain if it was discarded (first call, after a pop, or after
// final_check reset m_nielsen).
if (!m_nielsen.eager_active()) {
m_nielsen.eager_begin();
m_eager_processed = 0;
m_can_hot_restart = false; // m_nielsen now holds the eager chain
}
// Fold newly-arrived prop-queue items into the current leaf. Membership
// handling mirrors populate_nielsen_graph (trivial check, ignored skip,
// ground-prefix consumption via process_str_mem).
for (; m_eager_processed < m_prop_queue.size(); ++m_eager_processed) {
auto const& item = m_prop_queue[m_eager_processed];
if (std::holds_alternative<eq_item>(item)) {
auto const& eq = std::get<eq_item>(item);
m_nielsen.eager_add_str_eq(eq.m_lhs, eq.m_rhs, eq.m_l, eq.m_r);
}
else if (std::holds_alternative<deq_item>(item)) {
auto const& dq = std::get<deq_item>(item);
m_nielsen.eager_add_str_deq(dq.m_lhs, dq.m_rhs, dq.lit);
}
else if (std::holds_alternative<mem_item>(item)) {
auto const& mem = std::get<mem_item>(item);
int triv = m_regex.check_trivial(mem);
if (triv > 0)
continue; // trivially satisfied
if (triv < 0) {
m_nielsen.eager_add_str_mem(mem.m_str, mem.m_regex, mem.lit);
continue;
}
if (m_ignored_mem.contains(mem.lit))
continue; // already handled via Boolean closure
vector<seq::str_mem> processed;
if (!m_regex.process_str_mem(mem, processed)) {
m_nielsen.eager_add_str_mem(mem.m_str, mem.m_regex, mem.lit);
continue;
}
for (auto const& pm : processed)
m_nielsen.eager_add_str_mem(pm.m_str, pm.m_regex, mem.lit);
}
// axiom_item: not Nielsen-relevant, skip
}
const auto r = m_nielsen.eager_close();
if (r == seq::nielsen_graph::search_result::unsat) {
IF_VERBOSE(1, verbose_stream() << "nseq eager: structural conflict\n";);
TRACE(seq, tout << "nseq eager: structural conflict\n");
++m_num_eager_conflicts;
explain_nielsen_conflict(); // reads conflict_sources() + root, then sets the conflict
}
// Keep the chain for the next propagation (incremental). It is discarded by
// pop_scope_eh / final_check's reset, never here.
}
void theory_nseq::propagate_eq(tracked_str_eq const &eq) const {
// When s1 = s2 is learned, ensure len(s1) and len(s2) are
// internalized so congruence closure propagates len(s1) = len(s2).
@ -1289,6 +1377,7 @@ namespace smt {
void theory_nseq::collect_statistics(::statistics& st) const {
st.update("nseq conflicts", m_num_conflicts);
st.update("nseq eager conflicts", m_num_eager_conflicts);
st.update("nseq final checks", m_num_final_checks);
st.update("nseq sat revalidations", m_num_sat_revalidations);
st.update("nseq length axioms", m_num_length_axioms);