mirror of
https://github.com/Z3Prover/z3
synced 2026-03-17 18:43:45 +00:00
first end-pass. Atomic
Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
This commit is contained in:
parent
13f9fec339
commit
5aa3713d19
15 changed files with 6160 additions and 209 deletions
|
|
@ -17,6 +17,10 @@ Author:
|
|||
--*/
|
||||
#include "smt/theory_nseq.h"
|
||||
#include "smt/smt_context.h"
|
||||
#include "smt/smt_justification.h"
|
||||
#include "smt/proto_model/proto_model.h"
|
||||
#include "ast/array_decl_plugin.h"
|
||||
#include "ast/ast_pp.h"
|
||||
#include "util/statistics.h"
|
||||
|
||||
namespace smt {
|
||||
|
|
@ -26,43 +30,102 @@ namespace smt {
|
|||
m_seq(ctx.get_manager()),
|
||||
m_autil(ctx.get_manager()),
|
||||
m_rewriter(ctx.get_manager()),
|
||||
m_arith_value(ctx.get_manager()),
|
||||
m_egraph(ctx.get_manager()),
|
||||
m_sgraph(ctx.get_manager(), m_egraph),
|
||||
m_nielsen(m_sgraph),
|
||||
m_state(m_sgraph)
|
||||
m_state(m_sgraph),
|
||||
m_regex(m_sgraph),
|
||||
m_model(*this, ctx.get_manager(), m_seq, m_rewriter, m_sgraph, m_regex)
|
||||
{}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Initialization
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
void theory_nseq::init() {
|
||||
m_arith_value.init(&get_context());
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Internalization
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
bool theory_nseq::internalize_atom(app* atom, bool /*gate_ctx*/) {
|
||||
context& ctx = get_context();
|
||||
ast_manager& m = get_manager();
|
||||
|
||||
// str.in_re atoms are boolean predicates: register as bool_var
|
||||
// so that assign_eh fires when the SAT solver assigns them.
|
||||
// Following theory_seq: create a bool_var directly without an enode
|
||||
// for the str.in_re predicate (avoids needing to internalize the regex arg).
|
||||
if (m_seq.str.is_in_re(atom)) {
|
||||
expr* str_arg = atom->get_arg(0);
|
||||
mk_var(ensure_enode(str_arg));
|
||||
if (!ctx.b_internalized(atom)) {
|
||||
bool_var bv = ctx.mk_bool_var(atom);
|
||||
ctx.set_var_theory(bv, get_id());
|
||||
ctx.mark_as_relevant(bv);
|
||||
}
|
||||
get_snode(str_arg);
|
||||
return true;
|
||||
}
|
||||
return internalize_term(atom);
|
||||
}
|
||||
|
||||
theory_var theory_nseq::mk_var(enode* n) {
|
||||
expr* o = n->get_expr();
|
||||
if (!m_seq.is_seq(o) && !m_seq.is_re(o) && !m_seq.str.is_nth_u(o))
|
||||
return null_theory_var;
|
||||
if (is_attached_to_var(n))
|
||||
return n->get_th_var(get_id());
|
||||
theory_var v = theory::mk_var(n);
|
||||
get_context().attach_th_var(n, this, v);
|
||||
get_context().mark_as_relevant(n);
|
||||
return v;
|
||||
}
|
||||
|
||||
bool theory_nseq::internalize_term(app* term) {
|
||||
context& ctx = get_context();
|
||||
ast_manager& m = get_manager();
|
||||
|
||||
// ensure children are internalized first
|
||||
for (expr* arg : *term) {
|
||||
if (is_app(arg) && m_seq.is_seq(arg)) {
|
||||
ctx.internalize(arg, false);
|
||||
}
|
||||
// ensure ALL children are internalized (following theory_seq pattern)
|
||||
for (auto arg : *term)
|
||||
mk_var(ensure_enode(arg));
|
||||
|
||||
if (ctx.e_internalized(term)) {
|
||||
mk_var(ctx.get_enode(term));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!ctx.e_internalized(term)) {
|
||||
ctx.mk_enode(term, false, m.is_bool(term), true);
|
||||
if (m.is_bool(term)) {
|
||||
bool_var bv = ctx.mk_bool_var(term);
|
||||
ctx.set_var_theory(bv, get_id());
|
||||
ctx.mark_as_relevant(bv);
|
||||
}
|
||||
|
||||
enode* en = ctx.get_enode(term);
|
||||
if (!is_attached_to_var(en)) {
|
||||
theory_var v = mk_var(en);
|
||||
(void)v;
|
||||
enode* en;
|
||||
if (ctx.e_internalized(term)) {
|
||||
en = ctx.get_enode(term);
|
||||
}
|
||||
else {
|
||||
en = ctx.mk_enode(term, false, m.is_bool(term), true);
|
||||
}
|
||||
mk_var(en);
|
||||
|
||||
// register in our private sgraph
|
||||
get_snode(term);
|
||||
|
||||
// track higher-order terms for lazy unfolding
|
||||
expr* ho_f = nullptr, *ho_s = nullptr, *ho_b = nullptr, *ho_i = nullptr;
|
||||
if (m_seq.str.is_map(term, ho_f, ho_s) ||
|
||||
m_seq.str.is_mapi(term, ho_f, ho_i, ho_s) ||
|
||||
m_seq.str.is_foldl(term, ho_f, ho_b, ho_s) ||
|
||||
m_seq.str.is_foldli(term, ho_f, ho_i, ho_b, ho_s)) {
|
||||
m_ho_terms.push_back(term);
|
||||
ensure_length_var(ho_s);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -73,16 +136,73 @@ namespace smt {
|
|||
void theory_nseq::new_eq_eh(theory_var v1, theory_var v2) {
|
||||
expr* e1 = get_enode(v1)->get_expr();
|
||||
expr* e2 = get_enode(v2)->get_expr();
|
||||
if (m_seq.is_re(e1)) {
|
||||
++m_num_unhandled_bool;
|
||||
return;
|
||||
}
|
||||
if (!m_seq.is_seq(e1) || !m_seq.is_seq(e2))
|
||||
return;
|
||||
euf::snode* s1 = get_snode(e1);
|
||||
euf::snode* s2 = get_snode(e2);
|
||||
if (s1 && s2)
|
||||
m_state.add_str_eq(s1, s2);
|
||||
if (s1 && s2) {
|
||||
unsigned idx = m_state.str_eqs().size();
|
||||
m_state.add_str_eq(s1, s2, get_enode(v1), get_enode(v2));
|
||||
m_prop_queue.push_back({prop_item::eq_prop, idx});
|
||||
}
|
||||
}
|
||||
|
||||
void theory_nseq::new_diseq_eh(theory_var /*v1*/, theory_var /*v2*/) {
|
||||
// not handled in this initial skeleton
|
||||
void theory_nseq::new_diseq_eh(theory_var v1, theory_var v2) {
|
||||
expr* e1 = get_enode(v1)->get_expr();
|
||||
expr* e2 = get_enode(v2)->get_expr();
|
||||
if (m_seq.is_re(e1)) {
|
||||
// regex disequality: nseq cannot verify language non-equivalence
|
||||
++m_num_unhandled_bool;
|
||||
return;
|
||||
}
|
||||
if (!m_seq.is_seq(e1) || !m_seq.is_seq(e2))
|
||||
return;
|
||||
unsigned idx = m_state.diseqs().size();
|
||||
m_state.add_diseq(get_enode(v1), get_enode(v2));
|
||||
m_prop_queue.push_back({prop_item::diseq_prop, idx});
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Boolean assignment notification
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
void theory_nseq::assign_eh(bool_var v, bool is_true) {
|
||||
context& ctx = get_context();
|
||||
expr* e = ctx.bool_var2expr(v);
|
||||
expr* s = nullptr;
|
||||
expr* re = nullptr;
|
||||
if (!m_seq.str.is_in_re(e, s, re)) {
|
||||
// Track unhandled boolean string predicates (prefixof, contains, etc.)
|
||||
if (is_app(e) && to_app(e)->get_family_id() == m_seq.get_family_id())
|
||||
++m_num_unhandled_bool;
|
||||
return;
|
||||
}
|
||||
euf::snode* sn_str = get_snode(s);
|
||||
euf::snode* sn_re = get_snode(re);
|
||||
if (!sn_str || !sn_re)
|
||||
return;
|
||||
|
||||
if (is_true) {
|
||||
unsigned idx = m_state.str_mems().size();
|
||||
literal lit(v, false);
|
||||
m_state.add_str_mem(sn_str, sn_re, lit);
|
||||
m_prop_queue.push_back({prop_item::pos_mem_prop, idx});
|
||||
}
|
||||
else {
|
||||
unsigned idx = m_state.neg_mems().size();
|
||||
literal lit(v, true);
|
||||
m_state.add_neg_mem(sn_str, sn_re, lit);
|
||||
m_prop_queue.push_back({prop_item::neg_mem_prop, idx});
|
||||
}
|
||||
|
||||
TRACE(seq, tout << "nseq assign_eh: " << (is_true ? "" : "¬")
|
||||
<< "str.in_re "
|
||||
<< mk_bounded_pp(s, get_manager(), 3) << " in "
|
||||
<< mk_bounded_pp(re, get_manager(), 3) << "\n";);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
|
@ -93,12 +213,139 @@ namespace smt {
|
|||
theory::push_scope_eh();
|
||||
m_state.push();
|
||||
m_sgraph.push();
|
||||
m_prop_lim.push_back(m_prop_queue.size());
|
||||
m_ho_lim.push_back(m_ho_terms.size());
|
||||
m_unhandled_bool_lim.push_back(m_num_unhandled_bool);
|
||||
}
|
||||
|
||||
void theory_nseq::pop_scope_eh(unsigned num_scopes) {
|
||||
theory::pop_scope_eh(num_scopes);
|
||||
m_state.pop(num_scopes);
|
||||
m_sgraph.pop(num_scopes);
|
||||
unsigned new_sz = m_prop_lim[m_prop_lim.size() - num_scopes];
|
||||
m_prop_queue.shrink(new_sz);
|
||||
m_prop_lim.shrink(m_prop_lim.size() - num_scopes);
|
||||
if (m_prop_qhead > m_prop_queue.size())
|
||||
m_prop_qhead = m_prop_queue.size();
|
||||
unsigned ho_sz = m_ho_lim[m_ho_lim.size() - num_scopes];
|
||||
m_ho_terms.shrink(ho_sz);
|
||||
m_ho_lim.shrink(m_ho_lim.size() - num_scopes);
|
||||
m_num_unhandled_bool = m_unhandled_bool_lim[m_unhandled_bool_lim.size() - num_scopes];
|
||||
m_unhandled_bool_lim.shrink(m_unhandled_bool_lim.size() - num_scopes);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Propagation: eager eq/diseq/literal dispatch
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
bool theory_nseq::can_propagate() {
|
||||
return m_prop_qhead < m_prop_queue.size();
|
||||
}
|
||||
|
||||
void theory_nseq::propagate() {
|
||||
context& ctx = get_context();
|
||||
while (m_prop_qhead < m_prop_queue.size() && !ctx.inconsistent()) {
|
||||
prop_item const& item = m_prop_queue[m_prop_qhead++];
|
||||
switch (item.m_kind) {
|
||||
case prop_item::eq_prop:
|
||||
propagate_eq(item.m_idx);
|
||||
break;
|
||||
case prop_item::diseq_prop:
|
||||
propagate_diseq(item.m_idx);
|
||||
break;
|
||||
case prop_item::pos_mem_prop:
|
||||
propagate_pos_mem(item.m_idx);
|
||||
break;
|
||||
case prop_item::neg_mem_prop:
|
||||
propagate_neg_mem(item.m_idx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void theory_nseq::propagate_eq(unsigned idx) {
|
||||
// When s1 = s2 is learned, ensure len(s1) and len(s2) are
|
||||
// internalized so congruence closure propagates len(s1) = len(s2).
|
||||
eq_source const& src = m_state.get_eq_source(idx);
|
||||
ensure_length_var(src.m_n1->get_expr());
|
||||
ensure_length_var(src.m_n2->get_expr());
|
||||
}
|
||||
|
||||
void theory_nseq::propagate_diseq(unsigned idx) {
|
||||
// Disequalities are recorded for use during final_check.
|
||||
// No eager propagation beyond recording.
|
||||
TRACE(seq,
|
||||
auto const& d = m_state.get_diseq(idx);
|
||||
tout << "nseq diseq: "
|
||||
<< mk_bounded_pp(d.m_n1->get_expr(), get_manager(), 3)
|
||||
<< " != "
|
||||
<< mk_bounded_pp(d.m_n2->get_expr(), get_manager(), 3) << "\n";);
|
||||
}
|
||||
|
||||
void theory_nseq::propagate_pos_mem(unsigned idx) {
|
||||
auto const& mem = m_state.str_mems()[idx];
|
||||
auto const& src = m_state.get_mem_source(idx);
|
||||
|
||||
if (!mem.m_str || !mem.m_regex)
|
||||
return;
|
||||
|
||||
// regex is ∅ → conflict
|
||||
if (m_regex.is_empty_regex(mem.m_regex)) {
|
||||
enode_pair_vector eqs;
|
||||
literal_vector lits;
|
||||
lits.push_back(src.m_lit);
|
||||
set_conflict(eqs, lits);
|
||||
return;
|
||||
}
|
||||
|
||||
// empty string in non-nullable regex → conflict
|
||||
if (mem.m_str->is_empty() && !mem.m_regex->is_nullable()) {
|
||||
enode_pair_vector eqs;
|
||||
literal_vector lits;
|
||||
lits.push_back(src.m_lit);
|
||||
set_conflict(eqs, lits);
|
||||
return;
|
||||
}
|
||||
|
||||
// ensure length term exists for the string argument
|
||||
expr* s_expr = mem.m_str->get_expr();
|
||||
if (s_expr)
|
||||
ensure_length_var(s_expr);
|
||||
}
|
||||
|
||||
void theory_nseq::propagate_neg_mem(unsigned idx) {
|
||||
auto const& entry = m_state.get_neg_mem(idx);
|
||||
|
||||
if (!entry.m_str || !entry.m_regex)
|
||||
return;
|
||||
|
||||
// ¬(s in Σ*) is always false → conflict
|
||||
if (m_regex.is_full_regex(entry.m_regex)) {
|
||||
enode_pair_vector eqs;
|
||||
literal_vector lits;
|
||||
lits.push_back(entry.m_lit);
|
||||
set_conflict(eqs, lits);
|
||||
return;
|
||||
}
|
||||
|
||||
// ¬(ε in R) where R is nullable → conflict
|
||||
if (entry.m_str->is_empty() && entry.m_regex->is_nullable()) {
|
||||
enode_pair_vector eqs;
|
||||
literal_vector lits;
|
||||
lits.push_back(entry.m_lit);
|
||||
set_conflict(eqs, lits);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void theory_nseq::ensure_length_var(expr* e) {
|
||||
if (!e || !m_seq.is_seq(e))
|
||||
return;
|
||||
context& ctx = get_context();
|
||||
ast_manager& m = get_manager();
|
||||
expr_ref len(m_seq.str.mk_length(e), m);
|
||||
if (!ctx.e_internalized(len))
|
||||
ctx.internalize(len, false);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
|
@ -107,30 +354,162 @@ namespace smt {
|
|||
|
||||
void theory_nseq::populate_nielsen_graph() {
|
||||
m_nielsen.reset();
|
||||
seq::nielsen_node* root = m_nielsen.mk_node();
|
||||
m_nielsen.set_root(root);
|
||||
for (auto const& eq : m_state.str_eqs())
|
||||
root->add_str_eq(eq);
|
||||
for (auto const& mem : m_state.str_mems())
|
||||
root->add_str_mem(mem);
|
||||
m_nielsen_to_state_mem.reset();
|
||||
|
||||
// transfer string equalities from state to nielsen graph root
|
||||
for (auto const& eq : m_state.str_eqs()) {
|
||||
m_nielsen.add_str_eq(eq.m_lhs, eq.m_rhs);
|
||||
}
|
||||
|
||||
// transfer regex memberships, pre-processing through nseq_regex
|
||||
// to consume ground prefixes via Brzozowski derivatives
|
||||
for (unsigned state_idx = 0; state_idx < m_state.str_mems().size(); ++state_idx) {
|
||||
auto const& mem = m_state.str_mems()[state_idx];
|
||||
int triv = m_regex.check_trivial(mem);
|
||||
if (triv > 0)
|
||||
continue; // trivially satisfied, skip
|
||||
if (triv < 0) {
|
||||
// trivially unsat: add anyway so solve() detects conflict
|
||||
m_nielsen.add_str_mem(mem.m_str, mem.m_regex);
|
||||
m_nielsen_to_state_mem.push_back(state_idx);
|
||||
continue;
|
||||
}
|
||||
// pre-process: consume ground prefix characters
|
||||
vector<seq::str_mem> processed;
|
||||
if (!m_regex.process_str_mem(mem, processed)) {
|
||||
// conflict during ground prefix consumption
|
||||
m_nielsen.add_str_mem(mem.m_str, mem.m_regex);
|
||||
m_nielsen_to_state_mem.push_back(state_idx);
|
||||
continue;
|
||||
}
|
||||
for (auto const& pm : processed) {
|
||||
m_nielsen.add_str_mem(pm.m_str, pm.m_regex);
|
||||
m_nielsen_to_state_mem.push_back(state_idx);
|
||||
}
|
||||
}
|
||||
|
||||
TRACE(seq, tout << "nseq populate: " << m_state.str_eqs().size() << " eqs, "
|
||||
<< m_state.str_mems().size() << " mems -> nielsen root with "
|
||||
<< m_nielsen.num_input_eqs() << " eqs, "
|
||||
<< m_nielsen.num_input_mems() << " mems\n";);
|
||||
}
|
||||
|
||||
final_check_status theory_nseq::final_check_eh(unsigned /*final_check_round*/) {
|
||||
// Always assert non-negativity for all string theory vars,
|
||||
// even when there are no string equations/memberships.
|
||||
if (assert_nonneg_for_all_vars())
|
||||
return FC_CONTINUE;
|
||||
|
||||
// If there are unhandled boolean string predicates (prefixof, contains, etc.)
|
||||
// we cannot declare sat — return unknown.
|
||||
if (has_unhandled_preds())
|
||||
return FC_GIVEUP;
|
||||
|
||||
if (m_state.empty() && m_ho_terms.empty())
|
||||
return FC_DONE;
|
||||
|
||||
// unfold higher-order terms when sequence structure is known
|
||||
if (unfold_ho_terms())
|
||||
return FC_CONTINUE;
|
||||
|
||||
if (m_state.empty())
|
||||
return FC_DONE;
|
||||
// For now, give up if there are string constraints.
|
||||
// The full search will be wired in once the Nielsen algorithms are complete.
|
||||
|
||||
populate_nielsen_graph();
|
||||
++m_num_nodes_explored;
|
||||
|
||||
// assert length constraints derived from string equalities
|
||||
if (assert_length_constraints())
|
||||
return FC_CONTINUE;
|
||||
|
||||
++m_num_final_checks;
|
||||
|
||||
auto result = m_nielsen.solve();
|
||||
|
||||
if (result == seq::nielsen_graph::search_result::sat) {
|
||||
// Nielsen found a consistent assignment for positive constraints.
|
||||
// If there are negative memberships or disequalities we haven't verified,
|
||||
// we cannot soundly declare sat.
|
||||
if (!m_state.neg_mems().empty() || !m_state.diseqs().empty())
|
||||
return FC_GIVEUP;
|
||||
return FC_DONE;
|
||||
}
|
||||
|
||||
if (result == seq::nielsen_graph::search_result::unsat) {
|
||||
explain_nielsen_conflict();
|
||||
return FC_CONTINUE;
|
||||
}
|
||||
|
||||
return FC_GIVEUP;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Conflict explanation
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
void theory_nseq::deps_to_lits(seq::dep_tracker const& deps, enode_pair_vector& eqs, literal_vector& lits) {
|
||||
context& ctx = get_context();
|
||||
unsigned_vector bits;
|
||||
deps.get_set_bits(bits);
|
||||
unsigned num_input_eqs = m_nielsen.num_input_eqs();
|
||||
for (unsigned b : bits) {
|
||||
if (b < num_input_eqs) {
|
||||
eq_source const& src = m_state.get_eq_source(b);
|
||||
if (src.m_n1->get_root() == src.m_n2->get_root())
|
||||
eqs.push_back({src.m_n1, src.m_n2});
|
||||
}
|
||||
else {
|
||||
unsigned mem_idx = b - num_input_eqs;
|
||||
if (mem_idx < m_nielsen_to_state_mem.size()) {
|
||||
unsigned state_mem_idx = m_nielsen_to_state_mem[mem_idx];
|
||||
mem_source const& src = m_state.get_mem_source(state_mem_idx);
|
||||
if (ctx.get_assignment(src.m_lit) == l_true)
|
||||
lits.push_back(src.m_lit);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void theory_nseq::add_conflict_clause(seq::dep_tracker const& deps) {
|
||||
enode_pair_vector eqs;
|
||||
literal_vector lits;
|
||||
deps_to_lits(deps, eqs, lits);
|
||||
++m_num_conflicts;
|
||||
set_conflict(eqs, lits);
|
||||
}
|
||||
|
||||
void theory_nseq::explain_nielsen_conflict() {
|
||||
seq::dep_tracker deps;
|
||||
m_nielsen.collect_conflict_deps(deps);
|
||||
add_conflict_clause(deps);
|
||||
}
|
||||
|
||||
void theory_nseq::set_conflict(enode_pair_vector const& eqs, literal_vector const& lits) {
|
||||
context& ctx = get_context();
|
||||
TRACE(seq, tout << "nseq conflict: " << eqs.size() << " eqs, " << lits.size() << " lits\n";);
|
||||
ctx.set_conflict(
|
||||
ctx.mk_justification(
|
||||
ext_theory_conflict_justification(
|
||||
get_id(), ctx, lits.size(), lits.data(), eqs.size(), eqs.data(), 0, nullptr)));
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Model generation
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
void theory_nseq::init_model(model_generator& /*mg*/) {
|
||||
// stub – no model assignment for now
|
||||
void theory_nseq::init_model(model_generator& mg) {
|
||||
m_model.init(mg, m_nielsen, m_state);
|
||||
}
|
||||
|
||||
model_value_proc* theory_nseq::mk_value(enode* n, model_generator& mg) {
|
||||
return m_model.mk_value(n, mg);
|
||||
}
|
||||
|
||||
void theory_nseq::finalize_model(model_generator& mg) {
|
||||
m_model.finalize(mg);
|
||||
}
|
||||
|
||||
void theory_nseq::validate_model(proto_model& mdl) {
|
||||
m_model.validate_regex(m_state, mdl);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
|
@ -139,14 +518,47 @@ namespace smt {
|
|||
|
||||
void theory_nseq::collect_statistics(::statistics& st) const {
|
||||
st.update("nseq conflicts", m_num_conflicts);
|
||||
st.update("nseq nodes explored", m_num_nodes_explored);
|
||||
st.update("nseq depth increases", m_num_depth_increases);
|
||||
st.update("nseq final checks", m_num_final_checks);
|
||||
st.update("nseq length axioms", m_num_length_axioms);
|
||||
|
||||
// Nielsen graph search metrics
|
||||
auto const& ns = m_nielsen.stats();
|
||||
st.update("nseq solve calls", ns.m_num_solve_calls);
|
||||
st.update("nseq dfs nodes", ns.m_num_dfs_nodes);
|
||||
st.update("nseq sat", ns.m_num_sat);
|
||||
st.update("nseq unsat", ns.m_num_unsat);
|
||||
st.update("nseq unknown", ns.m_num_unknown);
|
||||
st.update("nseq simplify clash", ns.m_num_simplify_conflict);
|
||||
st.update("nseq subsumptions", ns.m_num_subsumptions);
|
||||
st.update("nseq extensions", ns.m_num_extensions);
|
||||
st.update("nseq fresh vars", ns.m_num_fresh_vars);
|
||||
st.update("nseq max depth", ns.m_max_depth);
|
||||
|
||||
// modifier breakdown
|
||||
st.update("nseq mod det", ns.m_mod_det);
|
||||
st.update("nseq mod power epsilon", ns.m_mod_power_epsilon);
|
||||
st.update("nseq mod num cmp", ns.m_mod_num_cmp);
|
||||
st.update("nseq mod const num unwind", ns.m_mod_const_num_unwinding);
|
||||
st.update("nseq mod eq split", ns.m_mod_eq_split);
|
||||
st.update("nseq mod star intr", ns.m_mod_star_intr);
|
||||
st.update("nseq mod gpower intr", ns.m_mod_gpower_intr);
|
||||
st.update("nseq mod const nielsen", ns.m_mod_const_nielsen);
|
||||
st.update("nseq mod regex char", ns.m_mod_regex_char_split);
|
||||
st.update("nseq mod regex var", ns.m_mod_regex_var_split);
|
||||
st.update("nseq mod power split", ns.m_mod_power_split);
|
||||
st.update("nseq mod var nielsen", ns.m_mod_var_nielsen);
|
||||
st.update("nseq mod var num unwind", ns.m_mod_var_num_unwinding);
|
||||
st.update("nseq ho unfolds", m_num_ho_unfolds);
|
||||
}
|
||||
|
||||
void theory_nseq::display(std::ostream& out) const {
|
||||
out << "theory_nseq\n";
|
||||
out << " str_eqs: " << m_state.str_eqs().size() << "\n";
|
||||
out << " str_mems: " << m_state.str_mems().size() << "\n";
|
||||
out << " str_eqs: " << m_state.str_eqs().size() << "\n";
|
||||
out << " str_mems: " << m_state.str_mems().size() << "\n";
|
||||
out << " diseqs: " << m_state.diseqs().size() << "\n";
|
||||
out << " neg_mems: " << m_state.neg_mems().size() << "\n";
|
||||
out << " prop_queue: " << m_prop_qhead << "/" << m_prop_queue.size() << "\n";
|
||||
out << " ho_terms: " << m_ho_terms.size() << "\n";
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
|
@ -157,6 +569,129 @@ namespace smt {
|
|||
return alloc(theory_nseq, *ctx);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Higher-order term unfolding (seq.map, seq.foldl, etc.)
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
bool theory_nseq::unfold_ho_terms() {
|
||||
if (m_ho_terms.empty())
|
||||
return false;
|
||||
|
||||
context& ctx = get_context();
|
||||
ast_manager& m = get_manager();
|
||||
bool progress = false;
|
||||
|
||||
unsigned sz = m_ho_terms.size();
|
||||
for (unsigned i = 0; i < sz; ++i) {
|
||||
app* term = m_ho_terms[i];
|
||||
expr* f = nullptr, *s = nullptr, *b = nullptr, *idx = nullptr;
|
||||
|
||||
if (!m_seq.str.is_map(term, f, s) &&
|
||||
!m_seq.str.is_mapi(term, f, idx, s) &&
|
||||
!m_seq.str.is_foldl(term, f, b, s) &&
|
||||
!m_seq.str.is_foldli(term, f, idx, b, s))
|
||||
continue;
|
||||
|
||||
if (!ctx.e_internalized(s))
|
||||
continue;
|
||||
|
||||
// Find a structural representative in s's equivalence class
|
||||
enode* s_root = ctx.get_enode(s)->get_root();
|
||||
expr* repr = nullptr;
|
||||
enode* curr = s_root;
|
||||
do {
|
||||
expr* e = curr->get_expr();
|
||||
expr *a1, *a2;
|
||||
if (m_seq.str.is_empty(e) ||
|
||||
m_seq.str.is_unit(e, a1) ||
|
||||
m_seq.str.is_concat(e, a1, a2)) {
|
||||
repr = e;
|
||||
break;
|
||||
}
|
||||
curr = curr->get_next();
|
||||
} while (curr != s_root);
|
||||
|
||||
if (!repr)
|
||||
continue;
|
||||
|
||||
// Build ho_term with structural seq arg, then rewrite
|
||||
expr_ref ho_repr(m);
|
||||
if (m_seq.str.is_map(term))
|
||||
ho_repr = m_seq.str.mk_map(f, repr);
|
||||
else if (m_seq.str.is_mapi(term))
|
||||
ho_repr = m_seq.str.mk_mapi(f, idx, repr);
|
||||
else if (m_seq.str.is_foldl(term))
|
||||
ho_repr = m_seq.str.mk_foldl(f, b, repr);
|
||||
else
|
||||
ho_repr = m_seq.str.mk_foldli(f, idx, b, repr);
|
||||
|
||||
expr_ref rewritten(m);
|
||||
br_status st = m_rewriter.mk_app_core(
|
||||
to_app(ho_repr)->get_decl(),
|
||||
to_app(ho_repr)->get_num_args(),
|
||||
to_app(ho_repr)->get_args(),
|
||||
rewritten);
|
||||
|
||||
if (st == BR_FAILED)
|
||||
continue;
|
||||
|
||||
// Internalize both the structural ho_term and its rewrite
|
||||
if (!ctx.e_internalized(ho_repr))
|
||||
ctx.internalize(ho_repr, false);
|
||||
if (!ctx.e_internalized(rewritten))
|
||||
ctx.internalize(rewritten, false);
|
||||
|
||||
enode* ho_en = ctx.get_enode(ho_repr);
|
||||
enode* res_en = ctx.get_enode(rewritten);
|
||||
|
||||
if (ho_en->get_root() == res_en->get_root())
|
||||
continue;
|
||||
|
||||
// Assert tautological axiom: ho_repr = rewritten
|
||||
// Congruence closure merges map(f,s) with map(f,repr)
|
||||
// since s = repr in the E-graph.
|
||||
expr_ref eq_expr(m.mk_eq(ho_repr, rewritten), m);
|
||||
if (!ctx.b_internalized(eq_expr))
|
||||
ctx.internalize(eq_expr, true);
|
||||
literal eq_lit = ctx.get_literal(eq_expr);
|
||||
if (ctx.get_assignment(eq_lit) != l_true) {
|
||||
ctx.mk_th_axiom(get_id(), 1, &eq_lit);
|
||||
TRACE(seq, tout << "nseq ho unfold: "
|
||||
<< mk_bounded_pp(ho_repr, m, 3) << " = "
|
||||
<< mk_bounded_pp(rewritten, m, 3) << "\n";);
|
||||
++m_num_ho_unfolds;
|
||||
progress = true;
|
||||
}
|
||||
}
|
||||
|
||||
// For map/mapi: propagate length preservation
|
||||
for (unsigned i = 0; i < sz; ++i) {
|
||||
app* term = m_ho_terms[i];
|
||||
expr* f = nullptr, *s = nullptr, *idx = nullptr;
|
||||
bool is_map = m_seq.str.is_map(term, f, s);
|
||||
bool is_mapi = !is_map && m_seq.str.is_mapi(term, f, idx, s);
|
||||
if (!is_map && !is_mapi)
|
||||
continue;
|
||||
if (!m_seq.is_seq(term))
|
||||
continue;
|
||||
|
||||
// len(map(f, s)) = len(s)
|
||||
expr_ref len_map(m_seq.str.mk_length(term), m);
|
||||
expr_ref len_s(m_seq.str.mk_length(s), m);
|
||||
expr_ref len_eq(m.mk_eq(len_map, len_s), m);
|
||||
if (!ctx.b_internalized(len_eq))
|
||||
ctx.internalize(len_eq, true);
|
||||
literal len_lit = ctx.get_literal(len_eq);
|
||||
if (ctx.get_assignment(len_lit) != l_true) {
|
||||
ctx.mk_th_axiom(get_id(), 1, &len_lit);
|
||||
++m_num_length_axioms;
|
||||
progress = true;
|
||||
}
|
||||
}
|
||||
|
||||
return progress;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Helpers
|
||||
// -----------------------------------------------------------------------
|
||||
|
|
@ -168,4 +703,136 @@ namespace smt {
|
|||
return s;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Arithmetic value queries
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
bool theory_nseq::get_num_value(expr* e, rational& val) const {
|
||||
return m_arith_value.get_value_equiv(e, val) && val.is_int();
|
||||
}
|
||||
|
||||
bool theory_nseq::lower_bound(expr* e, rational& lo) const {
|
||||
bool is_strict = true;
|
||||
return m_arith_value.get_lo(e, lo, is_strict) && !is_strict && lo.is_int();
|
||||
}
|
||||
|
||||
bool theory_nseq::upper_bound(expr* e, rational& hi) const {
|
||||
bool is_strict = true;
|
||||
return m_arith_value.get_up(e, hi, is_strict) && !is_strict && hi.is_int();
|
||||
}
|
||||
|
||||
bool theory_nseq::get_length(expr* e, rational& val) {
|
||||
ast_manager& m = get_manager();
|
||||
rational val1;
|
||||
expr* e1 = nullptr;
|
||||
expr* e2 = nullptr;
|
||||
ptr_vector<expr> todo;
|
||||
todo.push_back(e);
|
||||
val.reset();
|
||||
zstring s;
|
||||
while (!todo.empty()) {
|
||||
expr* c = todo.back();
|
||||
todo.pop_back();
|
||||
if (m_seq.str.is_concat(c, e1, e2)) {
|
||||
todo.push_back(e1);
|
||||
todo.push_back(e2);
|
||||
}
|
||||
else if (m_seq.str.is_unit(c))
|
||||
val += rational(1);
|
||||
else if (m_seq.str.is_empty(c))
|
||||
continue;
|
||||
else if (m_seq.str.is_string(c, s))
|
||||
val += rational(s.length());
|
||||
else {
|
||||
expr_ref len(m_seq.str.mk_length(c), m);
|
||||
if (m_arith_value.get_value(len, val1) && !val1.is_neg())
|
||||
val += val1;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return val.is_int();
|
||||
}
|
||||
|
||||
void theory_nseq::add_length_axiom(literal lit) {
|
||||
context& ctx = get_context();
|
||||
ctx.mark_as_relevant(lit);
|
||||
ctx.mk_th_axiom(get_id(), 1, &lit);
|
||||
++m_num_length_axioms;
|
||||
}
|
||||
|
||||
bool theory_nseq::propagate_length_lemma(literal lit, seq::length_constraint const& lc) {
|
||||
context& ctx = get_context();
|
||||
ast_manager& m = get_manager();
|
||||
|
||||
// unconditional constraints: assert as theory axiom
|
||||
if (lc.m_kind == seq::length_kind::nonneg) {
|
||||
add_length_axiom(lit);
|
||||
return true;
|
||||
}
|
||||
|
||||
// conditional constraints: propagate with justification from dep_tracker
|
||||
enode_pair_vector eqs;
|
||||
literal_vector lits;
|
||||
deps_to_lits(lc.m_dep, eqs, lits);
|
||||
|
||||
ctx.mark_as_relevant(lit);
|
||||
justification* js = ctx.mk_justification(
|
||||
ext_theory_propagation_justification(
|
||||
get_id(), ctx,
|
||||
lits.size(), lits.data(),
|
||||
eqs.size(), eqs.data(),
|
||||
lit));
|
||||
ctx.assign(lit, js);
|
||||
|
||||
TRACE(seq, tout << "nseq length propagation: " << mk_pp(lc.m_expr, m)
|
||||
<< " (" << eqs.size() << " eqs, " << lits.size() << " lits)\n";);
|
||||
++m_num_length_axioms;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool theory_nseq::assert_nonneg_for_all_vars() {
|
||||
ast_manager& m = get_manager();
|
||||
context& ctx = get_context();
|
||||
arith_util arith(m);
|
||||
bool new_axiom = false;
|
||||
unsigned nv = get_num_vars();
|
||||
for (unsigned v = 0; v < nv; ++v) {
|
||||
expr* e = get_enode(v)->get_expr();
|
||||
if (!m_seq.is_seq(e))
|
||||
continue;
|
||||
expr_ref len_var(m_seq.str.mk_length(e), m);
|
||||
expr_ref ge_zero(arith.mk_ge(len_var, arith.mk_int(0)), m);
|
||||
if (!ctx.b_internalized(ge_zero))
|
||||
ctx.internalize(ge_zero, true);
|
||||
literal lit = ctx.get_literal(ge_zero);
|
||||
if (ctx.get_assignment(lit) != l_true) {
|
||||
add_length_axiom(lit);
|
||||
new_axiom = true;
|
||||
}
|
||||
}
|
||||
return new_axiom;
|
||||
}
|
||||
|
||||
bool theory_nseq::assert_length_constraints() {
|
||||
ast_manager& m = get_manager();
|
||||
context& ctx = get_context();
|
||||
vector<seq::length_constraint> constraints;
|
||||
m_nielsen.generate_length_constraints(constraints);
|
||||
|
||||
bool new_axiom = false;
|
||||
for (auto const& lc : constraints) {
|
||||
expr* e = lc.m_expr;
|
||||
if (!ctx.b_internalized(e))
|
||||
ctx.internalize(e, true);
|
||||
literal lit = ctx.get_literal(e);
|
||||
if (ctx.get_assignment(lit) != l_true) {
|
||||
TRACE(seq, tout << "nseq length lemma: " << mk_pp(e, m) << "\n";);
|
||||
propagate_length_lemma(lit, lc);
|
||||
new_axiom = true;
|
||||
}
|
||||
}
|
||||
return new_axiom;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue