3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2026-07-15 03:25:43 +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

@ -674,6 +674,9 @@ namespace seq {
m_explored_automaton.reset();
m_unsat_node_cache.clear();
m_num_cache_hits = 0;
m_eager_active = false;
m_eager_leaf = nullptr;
m_eager_substs.reset();
// m_length_trail.reset();
// m_length_info.reset();
m_dep_mgr.reset();
@ -1985,6 +1988,115 @@ namespace seq {
}
}
void nielsen_graph::eager_begin() {
reset();
create_root();
m_eager_leaf = m_root;
m_eager_substs.reset();
m_eager_active = true;
}
euf::snode const* nielsen_graph::eager_rewrite(euf::snode const* s, dep_tracker& dep) {
for (auto const& sub : m_eager_substs) {
s = m_sg.subst(s, sub.m_var, sub.m_replacement);
dep = m_dep_mgr.mk_join(dep, sub.m_dep);
}
return s;
}
void nielsen_graph::eager_add_str_eq(euf::snode const* lhs, euf::snode const* rhs, smt::enode* l, smt::enode* r) {
SASSERT(m_eager_active && m_eager_leaf);
dep_tracker dep = m_dep_mgr.mk_leaf(enode_pair(l, r));
lhs = eager_rewrite(lhs, dep);
rhs = eager_rewrite(rhs, dep);
str_eq eq(lhs, rhs, dep);
eq.sort();
m_eager_leaf->add_str_eq(eq);
}
void nielsen_graph::eager_add_str_deq(euf::snode const* lhs, euf::snode const* rhs, sat::literal lit) {
SASSERT(m_eager_active && m_eager_leaf);
dep_tracker dep = m_dep_mgr.mk_leaf(lit);
lhs = eager_rewrite(lhs, dep);
rhs = eager_rewrite(rhs, dep);
m_eager_leaf->add_str_deq(str_deq(lhs, rhs, dep));
}
void nielsen_graph::eager_add_str_mem(euf::snode const* str, euf::snode const* regex, sat::literal lit) {
SASSERT(m_eager_active && m_eager_leaf);
dep_tracker dep = m_dep_mgr.mk_leaf(lit);
str = eager_rewrite(str, dep);
regex = eager_rewrite(regex, dep); // no-op for ground regexes, mirrors apply_subst
m_eager_leaf->add_str_mem(str_mem(str, regex, dep));
}
// Drive the deterministic chain from the current leaf to a fixpoint. Each step
// is a single-child apply_det_modifier (progress, strictly token-reducing), so
// this terminates without a budget. An EMPTY path makes simplify_and_init take
// only its assignment-independent branches (LP/arith passes 3c-else/3e are gated
// on !cur_path.empty()); no arithmetic/length solver is touched.
nielsen_graph::search_result nielsen_graph::eager_close() {
SASSERT(m_eager_active && m_eager_leaf);
++m_stats.m_num_eager_calls;
ptr_vector<nielsen_edge> empty_path;
// Rigid defined ops (str.replace_all, …) must never be Nielsen-substituted;
// a rigid term is inherited down the whole chain, so a single check on the
// leaf (which holds all current constraints) suffices — bail before any det
// step (mirrors final_check's guard).
if (m_eager_leaf->references_rigid())
return search_result::unknown;
auto report_conflict = [&](nielsen_node* n) {
// The conflict node's deps already transitively include the source deps
// of every constraint that fed it (apply_subst + eager_rewrite join the
// substitution deps). We must NOT use collect_conflict_deps() — it walks
// from the root and asserts every visited node is a conflict, but only
// the chain's last node is.
dep_tracker deps = nullptr;
if (n->m_conflict_external_literal != sat::null_literal)
deps = m_dep_mgr.mk_join(deps, m_dep_mgr.mk_leaf(n->m_conflict_external_literal));
if (n->m_conflict_internal)
deps = m_dep_mgr.mk_join(deps, n->m_conflict_internal);
m_conflict_sources.reset();
m_dep_mgr.linearize(deps, m_conflict_sources);
};
while (true) {
if (!m.inc())
return search_result::unknown;
nielsen_node* node = m_eager_leaf;
// a substitution applied in the previous step may have produced a
// conflict directly (e.g. an empty character-range intersection)
if (node->is_currently_conflict()) {
report_conflict(node);
return search_result::unsat;
}
const simplify_result sr = node->simplify_and_init(empty_path);
if (sr == simplify_result::conflict || node->is_currently_conflict()) {
report_conflict(node);
return search_result::unsat;
}
if (sr == simplify_result::satisfied || node->is_satisfied())
return search_result::unknown; // no eager conflict; defer to solve()
// deterministic, single-child substitution closure. apply_det_modifier
// only acts on word equations; membership-only structural conflicts are
// already caught by simplify_and_init's post-passes above.
if (!apply_det_modifier(node))
return search_result::unknown; // would need branching; stop here
// record the det substitution(s) and advance the leaf
SASSERT(!node->outgoing().empty());
nielsen_edge* e = node->outgoing().back();
for (auto const& s : e->subst())
m_eager_substs.push_back(s);
m_eager_leaf = e->tgt();
}
}
// ---- Transposition-table helpers (node memoization) ----------------------
static bool reason_is_string_only(backtrack_reason r) {
@ -5264,6 +5376,7 @@ namespace seq {
void nielsen_graph::collect_statistics(::statistics& st) const {
st.update("nseq solve calls", m_stats.m_num_solve_calls);
st.update("nseq eager calls", m_stats.m_num_eager_calls);
st.update("nseq dfs nodes", m_stats.m_num_dfs_nodes);
st.update("nseq sat", m_stats.m_num_sat);
st.update("nseq unsat", m_stats.m_num_unsat);

View file

@ -682,6 +682,7 @@ namespace seq {
// search statistics collected during Nielsen graph solving
struct nielsen_stats {
unsigned m_num_solve_calls = 0;
unsigned m_num_eager_calls = 0;
unsigned m_num_dfs_nodes = 0;
unsigned m_num_sat = 0;
unsigned m_num_unsat = 0;
@ -841,6 +842,18 @@ namespace seq {
std::set<std::vector<unsigned>> m_unsat_node_cache;
unsigned m_num_cache_hits = 0;
// Incremental eager-closure chain state (see eager_begin / eager_close).
// The chain is a single deterministic path root → … → m_eager_leaf;
// m_eager_substs is its composed substitution (root→leaf order), applied to
// late-arriving constraints so they land in leaf coordinates.
bool m_eager_active = false;
nielsen_node* m_eager_leaf = nullptr;
vector<nielsen_subst> m_eager_substs;
// apply the accumulated chain substitution to a single snode, joining the
// substitutions' deps into `dep`.
euf::snode const* eager_rewrite(euf::snode const* s, dep_tracker& dep);
public:
// Construct with a caller-supplied solver. Ownership is NOT transferred;
@ -956,6 +969,32 @@ namespace seq {
// main search entry point: iterative deepening DFS
search_result solve();
// ---- Incremental eager structural closure -----------------------------
// The deterministic Nielsen chain is grown incrementally as constraints
// arrive (driven by theory_nseq::eager_structural_check), so we do NOT
// rebuild from scratch on every propagation. Lifecycle:
// eager_begin() — start a fresh chain (reset + root + empty subst);
// eager_add_str_*() — fold one new constraint into the current LEAF,
// rewriting it through the chain's accumulated
// substitution so it lands in leaf coordinates;
// eager_close() — drive simplify_and_init (EMPTY path ⇒ no LP/arith
// passes) + single-child apply_det_modifier from the
// leaf to a fixpoint, extending the chain.
// Returns unsat (conflict_sources() = the conflict node's own deps) on a
// purely structural contradiction (symbol clash / empty-side / regex fail /
// widening); unknown otherwise. Sound for early-conflict detection because
// the current set is a SUBSET of any completion. Bails on
// references_rigid() (det substitution of a rigid defined op is unsound).
// reset()/pop (eager_invalidate) discard the chain; never declares SAT.
bool eager_active() const { return m_eager_active && m_root != nullptr; }
void eager_begin();
void eager_invalidate() { m_eager_active = false; m_eager_leaf = nullptr; m_eager_substs.reset(); }
nielsen_node* eager_leaf() const { return m_eager_leaf; }
void eager_add_str_eq(euf::snode const* lhs, euf::snode const* rhs, smt::enode* l, smt::enode* r);
void eager_add_str_deq(euf::snode const* lhs, euf::snode const* rhs, sat::literal lit);
void eager_add_str_mem(euf::snode const* str, euf::snode const* regex, sat::literal lit);
search_result eager_close();
// generate child nodes by applying modifier rules
// returns true if at least one child was generated
bool generate_extensions(nielsen_node *node);