diff --git a/src/ast/rewriter/CMakeLists.txt b/src/ast/rewriter/CMakeLists.txt index 105bd86af..23d4383b0 100644 --- a/src/ast/rewriter/CMakeLists.txt +++ b/src/ast/rewriter/CMakeLists.txt @@ -32,6 +32,7 @@ z3_add_component(rewriter quant_hoist.cpp recfun_rewriter.cpp rewriter.cpp + seq_axioms.cpp seq_rewriter.cpp seq_skolem.cpp th_rewriter.cpp diff --git a/src/ast/rewriter/seq_axioms.cpp b/src/ast/rewriter/seq_axioms.cpp new file mode 100644 index 000000000..5798df110 --- /dev/null +++ b/src/ast/rewriter/seq_axioms.cpp @@ -0,0 +1,951 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + seq_axioms.cpp + +Abstract: + + Axiomatize string operations that can be reduced to + more basic operations. These axioms are kept outside + of a particular solver: they are mainly solver independent. + +Author: + + Nikolaj Bjorner (nbjorner) 2020-4-16 + +Revision History: + +--*/ + +#include "ast/ast_pp.h" +#include "ast/ast_ll_pp.h" +#include "ast/rewriter/seq_axioms.h" + +namespace seq { + + axioms::axioms(th_rewriter& r): + m_rewrite(r), + m(r.m()), + a(m), + seq(m), + m_sk(m, r), + m_clause(m) + {} + + expr_ref axioms::mk_sub(expr* x, expr* y) { + expr_ref result(a.mk_sub(x, y), m); + m_rewrite(result); + return result; + } + + expr_ref axioms::purify(expr* e) { + if (!e) + return expr_ref(m); + if (get_depth(e) == 1) + return expr_ref(e, m); + expr_ref p = expr_ref(m.mk_fresh_const("seq.purify", e->get_sort()), m); + add_clause(mk_eq(p, e)); + return expr_ref(p, m); + } + + expr_ref axioms::mk_len(expr* s) { + expr_ref result(seq.str.mk_length(s), m); + m_rewrite(result); + return result; + } + + void axioms::add_clause(expr_ref const& a) { + m_clause.reset(); + m_clause.push_back(a); + m_add_clause(m_clause); + } + + void axioms::add_clause(expr_ref const& a, expr_ref const& b) { + m_clause.reset(); + m_clause.push_back(a); + m_clause.push_back(b); + m_add_clause(m_clause); + } + + void axioms::add_clause(expr_ref const& a, expr_ref const& b, expr_ref const& c) { + m_clause.reset(); + m_clause.push_back(a); + m_clause.push_back(b); + m_clause.push_back(c); + m_add_clause(m_clause); + } + + void axioms::add_clause(expr_ref const& a, expr_ref const& b, expr_ref const& c, expr_ref const & d) { + m_clause.reset(); + m_clause.push_back(a); + m_clause.push_back(b); + m_clause.push_back(c); + m_clause.push_back(d); + m_add_clause(m_clause); + } + + void axioms::add_clause(expr_ref const& a, expr_ref const& b, expr_ref const& c, expr_ref const & d, expr_ref const& e) { + m_clause.reset(); + m_clause.push_back(a); + m_clause.push_back(b); + m_clause.push_back(c); + m_clause.push_back(d); + m_clause.push_back(e); + m_add_clause(m_clause); + } + + /*** + let e = extract(s, i, l) + + i is start index, l is length of substring starting at index. + + i < 0 => e = "" + i >= |s| => e = "" + l <= 0 => e = "" + 0 <= i < |s| & l > 0 => s = xey, |x| = i, |e| = min(l, |s|-i) + l <= 0 => e = "" + + this translates to: + + 0 <= i <= |s| -> s = xey + 0 <= i <= |s| -> len(x) = i + 0 <= i <= |s| & 0 <= l <= |s| - i -> |e| = l + 0 <= i <= |s| & |s| < l + i -> |e| = |s| - i + |e| = 0 <=> i < 0 | |s| <= i | l <= 0 | |s| <= 0 + + It follows that: + |e| = min(l, |s| - i) for 0 <= i < |s| and 0 < |l| + + */ + + void axioms::extract_axiom(expr* e) { + TRACE("seq", tout << mk_pp(e, m) << "\n";); + expr* _s = nullptr, *_i = nullptr, *_l = nullptr; + VERIFY(seq.str.is_extract(e, _s, _i, _l)); + auto s = purify(_s); + auto i = purify(_i); + auto l = purify(_l); + if (is_tail(s, i, l)) { + tail_axiom(e, s); + return; + } + if (is_drop_last(s, i, l)) { + drop_last_axiom(e, s); + return; + } + if (is_extract_prefix0(s, i, l)) { + extract_prefix_axiom(e, s, l); + return; + } + expr_ref x = m_sk.mk_pre(s, i); + expr_ref ls = mk_len(s); + expr_ref lx = mk_len(x); + expr_ref le = mk_len(e); + expr_ref ls_minus_i_l(mk_sub(mk_sub(ls, i), l), m); + expr_ref y = m_sk.mk_post(s, a.mk_add(i, l)); + expr_ref xe = mk_concat(x, e); + expr_ref xey = mk_concat(x, e, y); + expr_ref zero(a.mk_int(0), m); + + expr_ref i_ge_0 = mk_ge(i, 0); + expr_ref i_le_ls = mk_le(mk_sub(i, ls), 0); + expr_ref ls_le_i = mk_le(mk_sub(ls, i), 0); + expr_ref ls_ge_li = mk_ge(ls_minus_i_l, 0); + expr_ref l_ge_0 = mk_ge(l, 0); + expr_ref l_le_0 = mk_le(l, 0); + expr_ref ls_le_0 = mk_le(ls, 0); + expr_ref le_is_0 = mk_eq(le, zero); + + + // 0 <= i & i <= |s| & 0 <= l => xey = s + // 0 <= i & i <= |s| => |x| = i + // 0 <= i & i <= |s| & l >= 0 & |s| >= l + i => |e| = l + // 0 <= i & i <= |s| & |s| < l + i => |e| = |s| - i + // i < 0 => |e| = 0 + // |s| <= i => |e| = 0 + // |s| <= 0 => |e| = 0 + // l <= 0 => |e| = 0 + // |e| = 0 & i >= 0 & |s| > i & |s| > 0 => l <= 0 + add_clause(~i_ge_0, ~i_le_ls, ~l_ge_0, mk_seq_eq(xey, s)); + add_clause(~i_ge_0, ~i_le_ls, mk_eq(lx, i)); + add_clause(~i_ge_0, ~i_le_ls, ~l_ge_0, ~ls_ge_li, mk_eq(le, l)); + add_clause(~i_ge_0, ~i_le_ls, ~l_ge_0, ls_ge_li, mk_eq(le, mk_sub(ls, i))); + add_clause(i_ge_0, le_is_0); + add_clause(~ls_le_i, le_is_0); + add_clause(~ls_le_0, le_is_0); + add_clause(~l_le_0, le_is_0); + add_clause(~le_is_0, ~i_ge_0, ls_le_i, ls_le_0, l_le_0); + } + + void axioms::tail_axiom(expr* e, expr* s) { + expr_ref head(m), tail(m); + m_sk.decompose(s, head, tail); + TRACE("seq", tout << "tail " << mk_bounded_pp(e, m, 2) << " " << mk_bounded_pp(s, m, 2) << "\n";); + expr_ref emp = mk_eq_empty(s); + add_clause(emp, mk_seq_eq(s, mk_concat(head, e))); + add_clause(~emp, mk_eq_empty(e)); + } + + void axioms::drop_last_axiom(expr* e, expr* s) { + TRACE("seq", tout << "drop last " << mk_bounded_pp(e, m, 2) << " " << mk_bounded_pp(s, m, 2) << "\n";); + expr_ref emp = mk_eq_empty(s); + add_clause(emp, mk_seq_eq(s, mk_concat(e, seq.str.mk_unit(m_sk.mk_last(s))))); + add_clause(~emp, mk_eq_empty(e)); + } + + bool axioms::is_drop_last(expr* s, expr* i, expr* l) { + rational i1; + if (!a.is_numeral(i, i1) || !i1.is_zero()) { + return false; + } + expr_ref l2(m), l1(l, m); + l2 = mk_sub(mk_len(s), a.mk_int(1)); + m_rewrite(l1); + m_rewrite(l2); + return l1 == l2; + } + + bool axioms::is_tail(expr* s, expr* i, expr* l) { + rational i1; + if (!a.is_numeral(i, i1) || !i1.is_one()) { + return false; + } + expr_ref l2(m), l1(l, m); + l2 = mk_sub(mk_len(s), a.mk_int(1)); + m_rewrite(l1); + m_rewrite(l2); + return l1 == l2; + } + + bool axioms::is_extract_prefix0(expr* s, expr* i, expr* l) { + rational i1; + return a.is_numeral(i, i1) && i1.is_zero(); + } + + /* + s = ey + l <= 0 => e = empty + 0 <= l <= len(s) => len(e) = l + len(s) < l => e = s + */ + void axioms::extract_prefix_axiom(expr* e, expr* s, expr* l) { + TRACE("seq", tout << "prefix " << mk_bounded_pp(e, m, 2) << " " << mk_bounded_pp(s, m, 2) << " " << mk_bounded_pp(l, m, 2) << "\n";); + expr_ref le = mk_len(e); + expr_ref ls = mk_len(s); + expr_ref ls_minus_l(mk_sub(ls, l), m); + expr_ref y = m_sk.mk_post(s, l); + expr_ref ey = mk_concat(e, y); + expr_ref l_le_s = mk_le(mk_sub(l, ls), 0); + add_clause(mk_seq_eq(s, ey)); + add_clause(~mk_le(l, 0), mk_eq_empty(e)); + add_clause(~mk_ge(l, 0), ~l_le_s, mk_eq(le, l)); + add_clause(l_le_s, mk_eq(e, s)); + } + + /* + encode that s is not contained in of xs1 + where s1 is all of s, except the last element. + + s = "" or s = s1*(unit c) + s = "" or !contains(x*s1, s) + */ + void axioms::tightest_prefix(expr* s, expr* x) { + expr_ref s_eq_emp = mk_eq_empty(s); + if (seq.str.max_length(s) <= 1) { + add_clause(s_eq_emp, ~expr_ref(seq.str.mk_contains(x, s), m)); + return; + } + expr_ref s1 = m_sk.mk_first(s); + expr_ref c = m_sk.mk_last(s); + expr_ref s1c = mk_concat(s1, seq.str.mk_unit(c)); + add_clause(s_eq_emp, mk_seq_eq(s, s1c)); + add_clause(s_eq_emp, ~expr_ref(seq.str.mk_contains(mk_concat(x, s1), s), m)); + } + + /* + [[str.indexof]](w, w2, i) is the smallest n such that for some some w1, w3 + - w = w1w2w3 + - i <= n = |w1| + + if [[str.contains]](w, w2) = true, |w2| > 0 and i >= 0. + + [[str.indexof]](w,w2,i) = -1 otherwise. + + + let i = Index(t, s, offset): + // index of s in t starting at offset. + + + |t| = 0 => |s| = 0 or indexof(t,s,offset) = -1 + |t| = 0 & |s| = 0 => indexof(t,s,offset) = 0 + + offset >= len(t) => |s| = 0 or i = -1 + + len(t) != 0 & !contains(t, s) => i = -1 + + + offset = 0 & len(t) != 0 & contains(t, s) => t = xsy & i = len(x) + tightest_prefix(x, s) + + + 0 <= offset < len(t) => xy = t & + len(x) = offset & + (-1 = indexof(y, s, 0) => -1 = i) & + (indexof(y, s, 0) >= 0 => indexof(t, s, 0) + offset = i) + + offset < 0 => i = -1 + + optional lemmas: + (len(s) > len(t) -> i = -1) + (len(s) <= len(t) -> i <= len(t)-len(s)) + */ + void axioms::indexof_axiom(expr* i) { + expr* _s = nullptr, *_t = nullptr, *_offset = nullptr; + rational r; + VERIFY(seq.str.is_index(i, _t, _s) || + seq.str.is_index(i, _t, _s, _offset)); + expr_ref minus_one(a.mk_int(-1), m); + expr_ref zero(a.mk_int(0), m); + auto offset = purify(_offset); + auto s = purify(_s); + auto t = purify(_t); + expr_ref xsy(m); + expr_ref cnt(seq.str.mk_contains(t, s), m); + expr_ref i_eq_m1 = mk_eq(i, minus_one); + expr_ref i_eq_0 = mk_eq(i, zero); + expr_ref s_eq_empty = mk_eq_empty(s); + expr_ref t_eq_empty = mk_eq_empty(t); + + // |t| = 0 => |s| = 0 or indexof(t,s,offset) = -1 + // ~contains(t,s) <=> indexof(t,s,offset) = -1 + + add_clause(cnt, i_eq_m1); + add_clause(~t_eq_empty, s_eq_empty, i_eq_m1); + + if (!offset || (a.is_numeral(offset, r) && r.is_zero())) { + // |s| = 0 => indexof(t,s,0) = 0 + add_clause(~s_eq_empty, i_eq_0); +#if 1 + expr_ref x = m_sk.mk_indexof_left(t, s); + expr_ref y = m_sk.mk_indexof_right(t, s); + xsy = mk_concat(x, s, y); + expr_ref lenx = mk_len(x); + // contains(t,s) & |s| != 0 => t = xsy & indexof(t,s,0) = |x| + add_clause(~cnt, s_eq_empty, mk_seq_eq(t, xsy)); + add_clause(~cnt, s_eq_empty, mk_eq(i, lenx)); + add_clause(~cnt, mk_ge(i, 0)); + tightest_prefix(s, x); +#else + // let i := indexof(t,s,0) + // contains(t, s) & |s| != 0 => ~contains(substr(t,0,i+len(s)-1), s) + // => substr(t,0,i+len(s)) = substr(t,0,i) ++ s + // + expr_ref len_s = mk_len(s); + expr_ref mone(a.mk_int(-1), m); + add_clause(~cnt, s_eq_empty, ~mk_literal(seq.str.mk_contains(seq.str.mk_substr(t,zero,a.mk_add(i,len_s,mone)),s))); + add_clause(~cnt, s_eq_empty, mk_seq_eq(seq.str.mk_substr(t,zero,a.mk_add(i,len_s)), + seq.str.mk_concat(seq.str.mk_substr(t,zero,i), s))); +#endif + } + else { + // offset >= len(t) => |s| = 0 or indexof(t, s, offset) = -1 + // offset > len(t) => indexof(t, s, offset) = -1 + // offset = len(t) & |s| = 0 => indexof(t, s, offset) = offset + expr_ref len_t = mk_len(t); + expr_ref offset_ge_len = mk_ge(mk_sub(offset, len_t), 0); + expr_ref offset_le_len = mk_le(mk_sub(offset, len_t), 0); + expr_ref i_eq_offset = mk_eq(i, offset); + add_clause(~offset_ge_len, s_eq_empty, i_eq_m1); + add_clause(offset_le_len, i_eq_m1); + add_clause(~offset_ge_len, ~offset_le_len, ~s_eq_empty, i_eq_offset); + + expr_ref x = m_sk.mk_indexof_left(t, s, offset); + expr_ref y = m_sk.mk_indexof_right(t, s, offset); + expr_ref indexof0(seq.str.mk_index(y, s, zero), m); + expr_ref offset_p_indexof0(a.mk_add(offset, indexof0), m); + expr_ref offset_ge_0 = mk_ge(offset, 0); + + // 0 <= offset & offset < len(t) => t = xy + // 0 <= offset & offset < len(t) => len(x) = offset + // 0 <= offset & offset < len(t) & indexof(y,s,0) = -1 => -1 = i + // 0 <= offset & offset < len(t) & indexof(y,s,0) >= 0 => + // -1 = indexof(y,s,0) + offset = indexof(t, s, offset) + + add_clause(~offset_ge_0, offset_ge_len, mk_seq_eq(t, mk_concat(x, y))); + add_clause(~offset_ge_0, offset_ge_len, mk_eq(mk_len(x), offset)); + add_clause(~offset_ge_0, offset_ge_len, + ~mk_eq(indexof0, minus_one), i_eq_m1); + add_clause(~offset_ge_0, offset_ge_len, + ~mk_ge(indexof0, 0), + mk_eq(offset_p_indexof0, i)); + + // offset < 0 => -1 = i + add_clause(offset_ge_0, i_eq_m1); + } + } + + /** + !contains(t, s) => i = -1 + |t| = 0 => |s| = 0 or i = -1 + |t| = 0 & |s| = 0 => i = 0 + |t| != 0 & contains(t, s) => t = xsy & i = len(x) + |s| = 0 or s = s_head*s_tail + |s| = 0 or !contains(s_tail*y, s) + + */ + void axioms::last_indexof_axiom(expr* i) { + expr* _s = nullptr, *_t = nullptr; + VERIFY(seq.str.is_last_index(i, _t, _s)); + auto t = purify(_t); + auto s = purify(_s); + expr_ref minus_one(a.mk_int(-1), m); + expr_ref zero(a.mk_int(0), m); + expr_ref x = m_sk.mk_last_indexof_left(t, s); + expr_ref y = m_sk.mk_last_indexof_right(t, s); + expr_ref s_head(m), s_tail(m); + m_sk.decompose(s, s_head, s_tail); + expr_ref cnt = expr_ref(seq.str.mk_contains(t, s), m); + expr_ref cnt2 = expr_ref(seq.str.mk_contains(mk_concat(s_tail, y), s), m); + expr_ref i_eq_m1 = mk_eq(i, minus_one); + expr_ref i_eq_0 = mk_eq(i, zero); + expr_ref s_eq_empty = mk_eq_empty(s); + expr_ref t_eq_empty = mk_eq_empty(t); + expr_ref xsy = mk_concat(x, s, y); + + add_clause(cnt, i_eq_m1); + add_clause(~t_eq_empty, s_eq_empty, i_eq_m1); + add_clause(~t_eq_empty, ~s_eq_empty, i_eq_0); + add_clause(t_eq_empty, ~cnt, mk_seq_eq(t, xsy)); + add_clause(t_eq_empty, ~cnt, mk_eq(i, mk_len(x))); + add_clause(s_eq_empty, mk_eq(s, mk_concat(s_head, s_tail))); + add_clause(s_eq_empty, ~cnt2); + } + + + /* + let r = replace(u, s, t) + + + - if s is empty, the result is to prepend t to u; + - if s does not occur in u then the result is u. + + s = "" => r = t+u + u = "" => s = "" or r = u + ~contains(u,s) => r = u + + tightest_prefix(s, x) + contains(u, s) => r = xty & u = xsy + ~contains(u, s) => r = u + + */ + void axioms::replace_axiom(expr* r) { + expr* _u = nullptr, *_s = nullptr, *_t = nullptr; + VERIFY(seq.str.is_replace(r, _u, _s, _t)); + auto u = purify(_u); + auto s = purify(_s); + auto t = purify(_t); + expr_ref x = m_sk.mk_indexof_left(u, s); + expr_ref y = m_sk.mk_indexof_right(u, s); + expr_ref xty = mk_concat(x, t, y); + expr_ref xsy = mk_concat(x, s, y); + expr_ref u_emp = mk_eq_empty(u); + expr_ref s_emp = mk_eq_empty(s); + expr_ref cnt = expr_ref(seq.str.mk_contains(u, s), m); + add_clause(~s_emp, mk_seq_eq(r, mk_concat(t, u))); + add_clause(~u_emp, s_emp, mk_seq_eq(r, u)); + add_clause(cnt, mk_seq_eq(r, u)); + add_clause(~cnt, u_emp, s_emp, mk_seq_eq(u, xsy)); + add_clause(~cnt, u_emp, s_emp, mk_seq_eq(r, xty)); + tightest_prefix(s, x); + } + + /* + let e = at(s, i) + + 0 <= i < len(s) -> s = xey & len(x) = i & len(e) = 1 + i < 0 \/ i >= len(s) -> e = empty + + */ + void axioms::at_axiom(expr* e) { + TRACE("seq", tout << "at-axiom: " << mk_bounded_pp(e, m) << "\n";); + expr* _s = nullptr, *_i = nullptr; + VERIFY(seq.str.is_at(e, _s, _i)); + auto s = purify(_s); + auto i = purify(_i); + expr_ref zero(a.mk_int(0), m); + expr_ref one(a.mk_int(1), m); + expr_ref emp(seq.str.mk_empty(e->get_sort()), m); + expr_ref len_s = mk_len(s); + expr_ref i_ge_0 = mk_ge(i, 0); + expr_ref i_ge_len_s = mk_ge(mk_sub(i, mk_len(s)), 0); + expr_ref len_e = mk_len(e); + + rational iv; + if (a.is_numeral(i, iv) && iv.is_unsigned()) { + expr_ref_vector es(m); + expr_ref nth(m); + unsigned k = iv.get_unsigned(); + for (unsigned j = 0; j <= k; ++j) { + es.push_back(seq.str.mk_unit(mk_nth(s, j))); + } + nth = es.back(); + es.push_back(m_sk.mk_tail(s, i)); + add_clause(~i_ge_0, i_ge_len_s, mk_seq_eq(s, seq.str.mk_concat(es, e->get_sort()))); + add_clause(~i_ge_0, i_ge_len_s, mk_seq_eq(nth, e)); + } + else { + expr_ref x = m_sk.mk_pre(s, i); + expr_ref y = m_sk.mk_tail(s, i); + expr_ref xey = mk_concat(x, e, y); + expr_ref len_x = mk_len(x); + add_clause(~i_ge_0, i_ge_len_s, mk_seq_eq(s, xey)); + add_clause(~i_ge_0, i_ge_len_s, mk_eq(i, len_x)); + } + + add_clause(i_ge_0, mk_eq(e, emp)); + add_clause(~i_ge_len_s, mk_eq(e, emp)); + add_clause(~i_ge_0, i_ge_len_s, mk_eq(one, len_e)); + add_clause(mk_le(len_e, 1)); + } + + + /** + i >= 0 i < len(s) => unit(nth_i(s, i)) = at(s, i) + nth_i(unit(nth_i(s, i)), 0) = nth_i(s, i) + */ + + void axioms::nth_axiom(expr* e) { + expr* s = nullptr, *i = nullptr; + rational n; + zstring str; + VERIFY(seq.str.is_nth_i(e, s, i)); + if (seq.str.is_string(s, str) && a.is_numeral(i, n) && + n.is_unsigned() && n.get_unsigned() < str.length()) { + app_ref ch(seq.str.mk_char(str[n.get_unsigned()]), m); + add_clause(mk_eq(ch, e)); + } + else { + expr_ref zero(a.mk_int(0), m); + expr_ref i_ge_0 = mk_ge(i, 0); + expr_ref i_ge_len_s = mk_ge(mk_sub(i, mk_len(s)), 0); + // at(s,i) = [nth(s,i)] + expr_ref rhs(s, m); + expr_ref lhs(seq.str.mk_unit(e), m); + if (!seq.str.is_at(s) || zero != i) rhs = seq.str.mk_at(s, i); + m_rewrite(rhs); + add_clause(~i_ge_0, i_ge_len_s, mk_eq(lhs, rhs)); + } + } + + + void axioms::itos_axiom(expr* e) { + expr* n = nullptr; + TRACE("seq", tout << mk_pp(e, m) << "\n";); + VERIFY(seq.str.is_itos(e, n)); + + // itos(n) = "" <=> n < 0 + expr_ref zero(a.mk_int(0), m); + expr_ref eq1 = expr_ref(seq.str.mk_is_empty(e), m); + expr_ref ge0 = mk_ge(n, 0); + // n >= 0 => itos(n) != "" + // itos(n) = "" or n >= 0 + add_clause(~eq1, ~ge0); + add_clause(eq1, ge0); + add_clause(mk_ge(mk_len(e), 0)); + + // n >= 0 => stoi(itos(n)) = n + app_ref stoi(seq.str.mk_stoi(e), m); + add_clause(~ge0, mk_eq(stoi, n)); + + // itos(n) does not start with "0" when n > 0 + // n = 0 or at(itos(n),0) != "0" + // alternative: n >= 0 => itos(stoi(itos(n))) = itos(n) + expr_ref zs(seq.str.mk_string(symbol("0")), m); + m_rewrite(zs); + expr_ref eq0 = mk_eq(n, zero); + expr_ref at0 = mk_eq(seq.str.mk_at(e, zero), zs); + add_clause(eq0, ~at0); + add_clause(~eq0, mk_eq(e, zs)); + } + + /** + stoi(s) >= -1 + stoi("") = -1 + stoi(s) >= 0 => is_digit(nth(s,0)) + */ + void axioms::stoi_axiom(expr* e) { + TRACE("seq", tout << mk_pp(e, m) << "\n";); + expr_ref ge0 = mk_ge(e, 0); + expr* s = nullptr; + VERIFY (seq.str.is_stoi(e, s)); + add_clause(mk_ge(e, -1)); // stoi(s) >= -1 + add_clause(~mk_eq_empty(s), mk_eq(e, a.mk_int(-1))); // s = "" => stoi(s) = -1 + add_clause(~ge0, is_digit(mk_nth(s, 0))); // stoi(s) >= 0 => is_digit(nth(s,0)) + } + + + /** + + len(s) <= k => stoi(s) = stoi(s, k) + len(s) > 0, is_digit(nth(s,0)) => stoi(s, 0) = digit(nth_i(s, 0)) + len(s) > 0, ~is_digit(nth(s,0)) => stoi(s, 0) = -1 + + 0 < i, len(s) <= i => stoi(s, i) = stoi(s, i - 1) + 0 < i, len(s) > i, stoi(s, i - 1) >= 0, is_digit(nth(s, i - 1)) => stoi(s, i) = 10*stoi(s, i - 1) + digit(nth_i(s, i - 1)) + 0 < i, len(s) > i, stoi(s, i - 1) < 0 => stoi(s, i) = -1 + 0 < i, len(s) > i, ~is_digit(nth(s, i - 1)) => stoi(s, i) = -1 + + + + Define auxiliary function with the property: + for 0 <= i < k + stoi(s, i) := stoi(extract(s, 0, i+1)) + + for 0 < i < k: + len(s) > i => stoi(s, i) := stoi(extract(s, 0, i))*10 + stoi(extract(s, i, 1)) + len(s) <= i => stoi(s, i) := stoi(extract(s, 0, i-1), i-1) + + for i <= i < k: + stoi(s) > = 0, len(s) > i => is_digit(nth(s, i)) + + */ + void axioms::stoi_axiom(expr* e, unsigned k) { + SASSERT(k > 0); + expr* _s = nullptr; + VERIFY (seq.str.is_stoi(e, _s)); + expr_ref s(_s, m); + m_rewrite(s); + auto stoi2 = [&](unsigned j) { return m_sk.mk("seq.stoi", s, a.mk_int(j), a.mk_int()); }; + auto digit = [&](unsigned j) { return mk_digit2int(mk_nth(s, j)); }; + auto is_digit_ = [&](unsigned j) { return is_digit(mk_nth(s, j)); }; + expr_ref len = mk_len(s); + expr_ref ge0 = mk_ge(e, 0); + expr_ref lek = mk_le(len, k); + add_clause(~lek, mk_eq(e, stoi2(k-1))); // len(s) <= k => stoi(s) = stoi(s, k-1) + add_clause(mk_le(len, 0), ~is_digit_(0), mk_eq(stoi2(0), digit(0))); // len(s) > 0, is_digit(nth(s, 0)) => stoi(s,0) = digit(s,0) + add_clause(mk_le(len, 0), is_digit_(0), mk_eq(stoi2(0), a.mk_int(-1))); // len(s) > 0, ~is_digit(nth(s, 0)) => stoi(s,0) = -1 + for (unsigned i = 1; i < k; ++i) { + + // len(s) <= i => stoi(s, i) = stoi(s, i - 1) + + add_clause(~mk_le(len, i), mk_eq(stoi2(i), stoi2(i-1))); + + // len(s) > i, stoi(s, i - 1) >= 0, is_digit(nth(s, i)) => stoi(s, i) = 10*stoi(s, i - 1) + digit(i) + // len(s) > i, stoi(s, i - 1) < 0 => stoi(s, i) = -1 + // len(s) > i, ~is_digit(nth(s, i)) => stoi(s, i) = -1 + + add_clause(mk_le(len, i), ~mk_ge(stoi2(i-1), 0), ~is_digit_(i), mk_eq(stoi2(i), a.mk_add(a.mk_mul(a.mk_int(10), stoi2(i-1)), digit(i)))); + add_clause(mk_le(len, i), is_digit_(i), mk_eq(stoi2(i), a.mk_int(-1))); + add_clause(mk_le(len, i), mk_ge(stoi2(i-1), 0), mk_eq(stoi2(i), a.mk_int(-1))); + + // stoi(s) >= 0, i < len(s) => is_digit(nth(s, i)) + + add_clause(~ge0, mk_le(len, i), is_digit_(i)); + } + } + + /** + Let s := itos(e) + + Relate values of e with len(s) where len(s) is bounded by k. + + |s| = 0 => e < 0 + + |s| <= 1 => e < 10 + |s| <= 2 => e < 100 + |s| <= 3 => e < 1000 + + |s| >= 1 => e >= 0 + |s| >= 2 => e >= 10 + |s| >= 3 => e >= 100 + + There are no constraints to ensure that the string itos(e) + contains the valid digits corresponding to e >= 0. + The validity of itos(e) is ensured by the following property: + e is either of the form stoi(s) for some s, or there is a term + stoi(itos(e)) and axiom e >= 0 => stoi(itos(e)) = e. + Then the axioms for stoi(itos(e)) ensure that the characters of + itos(e) are valid digits and the axiom stoi(itos(e)) = e ensures + these digits encode e. + The option of constraining itos(e) digits directly does not + seem appealing becaues it requires an order of quadratic number + of constraints for all possible lengths of itos(e) (e.g, log_10(e)). + + */ + + void axioms::itos_axiom(expr* s, unsigned k) { + expr* e = nullptr; + VERIFY(seq.str.is_itos(s, e)); + expr_ref len = mk_len(s); + add_clause(mk_ge(e, 10), mk_le(len, 1)); + add_clause(mk_le(e, -1), mk_ge(len, 1)); + rational lo(1); + for (unsigned i = 1; i <= k; ++i) { + lo *= rational(10); + add_clause(mk_ge(e, lo), mk_le(len, i)); + add_clause(mk_le(e, lo - 1), mk_ge(len, i + 1)); + } + } + + expr_ref axioms::is_digit(expr* ch) { + expr_ref isd = expr_ref(m_sk.mk_is_digit(ch), m); + expr_ref lo(seq.mk_le(seq.mk_char('0'), ch), m); + expr_ref hi(seq.mk_le(ch, seq.mk_char('9')), m); + add_clause(~lo, ~hi, isd); + add_clause(~isd, lo); + add_clause(~isd, hi); + return isd; + } + + expr_ref axioms::mk_digit2int(expr* ch) { + return expr_ref(a.mk_add(seq.mk_char2int(ch), a.mk_int(-((int)'0'))), m);; + } + + /** + e1 < e2 => prefix(e1, e2) or e1 = xcy + e1 < e2 => prefix(e1, e2) or c < d + e1 < e2 => prefix(e1, e2) or e2 = xdz + e1 < e2 => e1 != e2 + !(e1 < e2) => prefix(e2, e1) or e2 = xdz + !(e1 < e2) => prefix(e2, e1) or d < c + !(e1 < e2) => prefix(e2, e1) or e1 = xcy + !(e1 = e2) or !(e1 < e2) + + optional: + e1 < e2 or e1 = e2 or e2 < e1 + !(e1 = e2) or !(e2 < e1) + !(e1 < e2) or !(e2 < e1) + */ + void axioms::lt_axiom(expr* n) { + expr* _e1 = nullptr, *_e2 = nullptr; + VERIFY(seq.str.is_lt(n, _e1, _e2)); + auto e1 = purify(_e1); + auto e2 = purify(_e2); + sort* s = e1->get_sort(); + sort* char_sort = nullptr; + VERIFY(seq.is_seq(s, char_sort)); + expr_ref lt = expr_ref(n, m); + expr_ref x = m_sk.mk("str.<.x", e1, e2); + expr_ref y = m_sk.mk("str.<.y", e1, e2); + expr_ref z = m_sk.mk("str.<.z", e1, e2); + expr_ref c = m_sk.mk("str.<.c", e1, e2, char_sort); + expr_ref d = m_sk.mk("str.<.d", e1, e2, char_sort); + expr_ref xcy = mk_concat(x, seq.str.mk_unit(c), y); + expr_ref xdz = mk_concat(x, seq.str.mk_unit(d), z); + expr_ref eq = mk_eq(e1, e2); + expr_ref pref21 = expr_ref(seq.str.mk_prefix(e2, e1), m); + expr_ref pref12 = expr_ref(seq.str.mk_prefix(e1, e2), m); + expr_ref e1xcy = mk_eq(e1, xcy); + expr_ref e2xdz = mk_eq(e2, xdz); + expr_ref ltcd = expr_ref(seq.mk_lt(c, d), m); + expr_ref ltdc = expr_ref(seq.mk_lt(d, c), m); + add_clause(~lt, pref12, e2xdz); + add_clause(~lt, pref12, e1xcy); + add_clause(~lt, pref12, ltcd); + add_clause(lt, pref21, e1xcy); + add_clause(lt, pref21, ltdc); + add_clause(lt, pref21, e2xdz); + add_clause(~eq, ~lt); + } + + /** + e1 <= e2 <=> e1 < e2 or e1 = e2 + */ + void axioms::le_axiom(expr* n) { + expr* e1 = nullptr, *e2 = nullptr; + VERIFY(seq.str.is_le(n, e1, e2)); + expr_ref lt = expr_ref(seq.str.mk_lex_lt(e1, e2), m); + expr_ref le = expr_ref(n, m); + expr_ref eq = mk_eq(e1, e2); + add_clause(~le, lt, eq); + add_clause(~eq, le); + add_clause(~lt, le); + } + + /** + is_digit(e) <=> to_code('0') <= to_code(e) <= to_code('9') + */ + void axioms::is_digit_axiom(expr* n) { + expr* e = nullptr; + VERIFY(seq.str.is_is_digit(n, e)); + expr_ref is_digit = expr_ref(n, m); + expr_ref to_code(seq.str.mk_to_code(e), m); + expr_ref ge0 = mk_ge(to_code, (unsigned)'0'); + expr_ref le9 = mk_le(to_code, (unsigned)'9'); + add_clause(~is_digit, ge0); + add_clause(~is_digit, le9); + add_clause(is_digit, ~ge0, ~le9); + } + + /** + len(e) = 1 => 0 <= to_code(e) <= max_code + len(e) = 1 => from_code(to_code(e)) = e + len(e) != 1 => to_code(e) = -1 + */ + void axioms::str_to_code_axiom(expr* n) { + expr* e = nullptr; + VERIFY(seq.str.is_to_code(n, e)); + expr_ref len_is1 = mk_eq(mk_len(e), a.mk_int(1)); + add_clause(~len_is1, mk_ge(n, 0)); + add_clause(~len_is1, mk_le(n, seq.max_char())); + add_clause(~len_is1, mk_eq(n, seq.mk_char2int(mk_nth(e, 0)))); + if (!seq.str.is_from_code(e)) + add_clause(~len_is1, mk_eq(e, seq.str.mk_from_code(n))); + add_clause(len_is1, mk_eq(n, a.mk_int(-1))); + } + + /** + 0 <= e <= max_char => len(from_code(e)) = 1 + 0 <= e <= max_char => to_code(from_code(e)) = e + e < 0 or e > max_char => len(from_code(e)) = "" + */ + void axioms::str_from_code_axiom(expr* n) { + expr* e = nullptr; + VERIFY(seq.str.is_from_code(n, e)); + expr_ref ge = mk_ge(e, 0); + expr_ref le = mk_le(e, seq.max_char()); + expr_ref emp = expr_ref(seq.str.mk_is_empty(n), m); + add_clause(~ge, ~le, mk_eq(mk_len(n), a.mk_int(1))); + if (!seq.str.is_to_code(e)) + add_clause(~ge, ~le, mk_eq(seq.str.mk_to_code(n), e)); + add_clause(ge, emp); + add_clause(le, emp); + } + + + /** + Unit is injective: + + u = inv-unit(unit(u)) + */ + + void axioms::unit_axiom(expr* n) { + expr* u = nullptr; + VERIFY(seq.str.is_unit(n, u)); + add_clause(mk_eq(u, m_sk.mk_unit_inv(n))); + } + + /** + + suffix(s, t) => s = seq.suffix_inv(s, t) + t + ~suffix(s, t) => len(s) > len(t) or s = y(s, t) + unit(c(s, t)) + x(s, t) + ~suffix(s, t) => len(s) > len(t) or t = z(s, t) + unit(d(s, t)) + x(s, t) + ~suffix(s, t) => len(s) > len(t) or c(s,t) != d(s,t) + + */ + + void axioms::suffix_axiom(expr* e) { + expr* _s = nullptr, *_t = nullptr; + VERIFY(seq.str.is_suffix(e, _s, _t)); + auto s = purify(_s); + auto t = purify(_t); + expr_ref lit = expr_ref(e, m); + expr_ref s_gt_t = mk_ge(mk_sub(mk_len(s), mk_len(t)), 1); +#if 0 + expr_ref x = m_sk.mk_pre(t, mk_sub(mk_len(t), mk_len(s))); + expr_ref y = m_sk.mk_tail(t, mk_sub(mk_len(s), a.mk_int(1))); + add_clause(lit, s_gt_t, mk_seq_eq(t, mk_concat(x, y))); + add_clause(lit, s_gt_t, mk_eq(mk_len(y), mk_len(s))); + add_clause(lit, s_gt_t, ~mk_eq(y, s)); +#else + sort* char_sort = nullptr; + VERIFY(seq.is_seq(s->get_sort(), char_sort)); + expr_ref x = m_sk.mk("seq.suffix.x", s, t); + expr_ref y = m_sk.mk("seq.suffix.y", s, t); + expr_ref z = m_sk.mk("seq.suffix.z", s, t); + expr_ref c = m_sk.mk("seq.suffix.c", s, t, char_sort); + expr_ref d = m_sk.mk("seq.suffix.d", s, t, char_sort); + add_clause(lit, s_gt_t, mk_seq_eq(s, mk_concat(y, seq.str.mk_unit(c), x))); + add_clause(lit, s_gt_t, mk_seq_eq(t, mk_concat(z, seq.str.mk_unit(d), x))); + add_clause(lit, s_gt_t, ~mk_eq(c, d)); +#endif + } + + void axioms::prefix_axiom(expr* e) { + expr* _s = nullptr, *_t = nullptr; + VERIFY(seq.str.is_prefix(e, _s, _t)); + auto s = purify(_s); + auto t = purify(_t); + expr_ref lit = expr_ref(e, m); + expr_ref s_gt_t = mk_ge(mk_sub(mk_len(s), mk_len(t)), 1); +#if 0 + expr_ref x = m_sk.mk_pre(t, mk_len(s)); + expr_ref y = m_sk.mk_tail(t, mk_sub(mk_sub(mk_len(t), mk_len(s)), a.mk_int(1))); + add_clause(lit, s_gt_t, mk_seq_eq(t, mk_concat(x, y))); + add_clause(lit, s_gt_t, mk_eq(mk_len(x), mk_len(s))); + add_clause(lit, s_gt_t, ~mk_eq(x, s)); + +#else + sort* char_sort = nullptr; + VERIFY(seq.is_seq(s->get_sort(), char_sort)); + expr_ref x = m_sk.mk("seq.prefix.x", s, t); + expr_ref y = m_sk.mk("seq.prefix.y", s, t); + expr_ref z = m_sk.mk("seq.prefix.z", s, t); + expr_ref c = m_sk.mk("seq.prefix.c", s, t, char_sort); + expr_ref d = m_sk.mk("seq.prefix.d", s, t, char_sort); + add_clause(lit, s_gt_t, mk_seq_eq(s, mk_concat(x, seq.str.mk_unit(c), y))); + add_clause(lit, s_gt_t, mk_seq_eq(t, mk_concat(x, seq.str.mk_unit(d), z)), mk_seq_eq(t, x)); + add_clause(lit, s_gt_t, ~mk_eq(c, d)); +#endif + } + + /*** + let n = len(x) + - len(a ++ b) = len(a) + len(b) if x = a ++ b + - len(unit(u)) = 1 if x = unit(u) + - len(str) = str.length() if x = str + - len(empty) = 0 if x = empty + - len(int.to.str(i)) >= 1 if x = int.to.str(i) and more generally if i = 0 then 1 else 1+floor(log(|i|)) + - len(x) >= 0 otherwise + */ + void axioms::length_axiom(expr* n) { + expr* x = nullptr; + VERIFY(seq.str.is_length(n, x)); + if (seq.str.is_concat(x) || + seq.str.is_unit(x) || + seq.str.is_empty(x) || + seq.str.is_string(x)) { + expr_ref len(n, m); + m_rewrite(len); + SASSERT(n != len); + add_clause(mk_eq(len, n)); + } + else { + add_clause(mk_ge(n, 0)); + } + } + + + /** + ~contains(a, b) => ~prefix(b, a) + ~contains(a, b) => ~contains(tail(a), b) or a = empty + ~contains(a, b) & a = empty => b != empty + ~(a = empty) => a = head + tail + */ + void axioms::unroll_not_contains(expr* e) { + expr_ref head(m), tail(m); + expr* a = nullptr, *b = nullptr; + VERIFY(seq.str.is_contains(e, a, b)); + m_sk.decompose(a, head, tail); + expr_ref pref(seq.str.mk_prefix(b, a), m); + expr_ref postf(seq.str.mk_contains(tail, b), m); + expr_ref emp = mk_eq_empty(a); + expr_ref cnt = expr_ref(e, m); + add_clause(cnt, ~pref); + add_clause(cnt, ~postf); + add_clause(~emp, mk_eq_empty(tail)); + add_clause(emp, mk_eq(a, seq.str.mk_concat(head, tail))); + } + + expr_ref axioms::length_limit(expr* s, unsigned k) { + expr_ref bound_tracker = m_sk.mk_length_limit(s, k); + expr* s0 = nullptr; + if (seq.str.is_stoi(s, s0)) + s = s0; + add_clause(~bound_tracker, mk_le(mk_len(s), k)); + return bound_tracker; + } + +} diff --git a/src/ast/rewriter/seq_axioms.h b/src/ast/rewriter/seq_axioms.h new file mode 100644 index 000000000..be15a8166 --- /dev/null +++ b/src/ast/rewriter/seq_axioms.h @@ -0,0 +1,102 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + seq_axioms.h + +Abstract: + + Axiomatize string operations that can be reduced to + more basic operations. + +Author: + + Nikolaj Bjorner (nbjorner) 2020-4-16 + +Revision History: + +--*/ +#pragma once + +#include "ast/seq_decl_plugin.h" +#include "ast/arith_decl_plugin.h" +#include "ast/rewriter/th_rewriter.h" +#include "ast/rewriter/seq_skolem.h" + +namespace seq { + + class axioms { + ast_manager& m; + th_rewriter& m_rewrite; + arith_util a; + seq_util seq; + skolem m_sk; + expr_ref_vector m_clause; + std::function m_add_clause; + + expr_ref mk_len(expr* s); + expr_ref mk_sub(expr* x, expr* y); + expr_ref mk_concat(expr* e1, expr* e2, expr* e3) { return expr_ref(seq.str.mk_concat(e1, e2, e3), m); } + expr_ref mk_concat(expr* e1, expr* e2) { return expr_ref(seq.str.mk_concat(e1, e2), m); } + expr_ref mk_nth(expr* e, unsigned i) { return expr_ref(seq.str.mk_nth_i(e, a.mk_int(i)), m); } + expr_ref mk_eq(expr* a, expr* b) { return expr_ref(m.mk_eq(a, b), m); } + expr_ref mk_seq_eq(expr* a, expr* b) { SASSERT(seq.is_seq(a) && seq.is_seq(b)); return expr_ref(m_sk.mk_eq(a, b), m); } + expr_ref mk_eq_empty(expr* e) { return expr_ref(m.mk_eq(seq.str.mk_empty(e->get_sort()), e), m); } + + expr_ref mk_ge(expr* x, unsigned n) { return expr_ref(a.mk_ge(x, a.mk_int(n)), m); } + expr_ref mk_le(expr* x, unsigned n) { return expr_ref(a.mk_le(x, a.mk_int(n)), m); } + expr_ref mk_ge(expr* x, rational const& n) { return expr_ref(a.mk_ge(x, a.mk_int(n)), m); } + expr_ref mk_le(expr* x, rational const& n) { return expr_ref(a.mk_le(x, a.mk_int(n)), m); } + + expr_ref is_digit(expr* ch); + expr_ref purify(expr* e); + expr_ref mk_digit2int(expr* ch); + + void add_clause(expr_ref const& a); + void add_clause(expr_ref const& a, expr_ref const& b); + void add_clause(expr_ref const& a, expr_ref const& b, expr_ref const& c); + void add_clause(expr_ref const& a, expr_ref const& b, expr_ref const& c, expr_ref const & d); + void add_clause(expr_ref const& a, expr_ref const& b, expr_ref const& c, expr_ref const & d, expr_ref const& e); + + bool is_drop_last(expr* s, expr* i, expr* l); + bool is_tail(expr* s, expr* i, expr* l); + bool is_extract_prefix0(expr* s, expr* i, expr* l); + + void tail_axiom(expr* e, expr* s); + void drop_last_axiom(expr* e, expr* s); + void extract_prefix_axiom(expr* e, expr* s, expr* l); + void tightest_prefix(expr* s, expr* x); + + public: + + axioms(th_rewriter& rw); + + void set_add_clause(std::function& ac) { m_add_clause = ac; } + + void suffix_axiom(expr* n); + void prefix_axiom(expr* n); + void extract_axiom(expr* n); + void indexof_axiom(expr* n); + void last_indexof_axiom(expr* n); + void replace_axiom(expr* n); + void at_axiom(expr* n); + void nth_axiom(expr* n); + void itos_axiom(expr* n); + void stoi_axiom(expr* n); + void stoi_axiom(expr* e, unsigned k); + void itos_axiom(expr* s, unsigned k); + void lt_axiom(expr* n); + void le_axiom(expr* n); + void is_digit_axiom(expr* n); + void str_to_code_axiom(expr* n); + void str_from_code_axiom(expr* n); + void unit_axiom(expr* n); + void length_axiom(expr* n); + void unroll_not_contains(expr* e); + + expr_ref length_limit(expr* s, unsigned k); + }; + +}; + diff --git a/src/smt/seq_axioms.cpp b/src/smt/seq_axioms.cpp index 49264ce35..df21a0c5a 100644 --- a/src/smt/seq_axioms.cpp +++ b/src/smt/seq_axioms.cpp @@ -33,8 +33,12 @@ seq_axioms::seq_axioms(theory& th, th_rewriter& r): a(m), seq(m), m_sk(m, r), + m_ax(r), m_digits_initialized(false) -{} +{ + std::function _add_clause = [&](expr_ref_vector const& c) { add_clause(c); }; + m_ax.set_add_clause(_add_clause); +} literal seq_axioms::mk_eq(expr* a, expr* b) { return th.mk_eq(a, b, false); @@ -54,13 +58,27 @@ expr_ref seq_axioms::mk_len(expr* s) { literal seq_axioms::mk_literal(expr* _e) { expr_ref e(_e, m); - if (a.is_arith_expr(e)) { + if (m.is_not(_e, _e)) + return ~mk_literal(_e); + if (m.is_eq(e)) + return mk_eq(to_app(e)->get_arg(0), to_app(e)->get_arg(1)); + if (a.is_arith_expr(e)) m_rewrite(e); - } th.ensure_enode(e); return ctx().get_literal(e); } +void seq_axioms::add_clause(expr_ref_vector const& clause) { + literal lits[5] = { null_literal, null_literal, null_literal, null_literal, null_literal }; + unsigned idx = 0; + for (expr* e : clause) { + lits[idx++] = mk_literal(e); + SASSERT(idx <= 5); + } + add_axiom(lits[0], lits[1], lits[2], lits[3], lits[4]); +} + + /*** let e = extract(s, i, l) @@ -87,6 +105,10 @@ this translates to: */ void seq_axioms::add_extract_axiom(expr* e) { + if (m_use_new_axioms) { + m_ax.extract_axiom(e); + return; + } TRACE("seq", tout << mk_pp(e, m) << "\n";); expr* _s = nullptr, *_i = nullptr, *_l = nullptr; VERIFY(seq.str.is_extract(e, _s, _i, _l)); @@ -694,68 +716,6 @@ void seq_axioms::ensure_digit_axiom() { } -/** - e1 < e2 => prefix(e1, e2) or e1 = xcy - e1 < e2 => prefix(e1, e2) or c < d - e1 < e2 => prefix(e1, e2) or e2 = xdz - e1 < e2 => e1 != e2 - !(e1 < e2) => prefix(e2, e1) or e2 = xdz - !(e1 < e2) => prefix(e2, e1) or d < c - !(e1 < e2) => prefix(e2, e1) or e1 = xcy - !(e1 = e2) or !(e1 < e2) - - optional: - e1 < e2 or e1 = e2 or e2 < e1 - !(e1 = e2) or !(e2 < e1) - !(e1 < e2) or !(e2 < e1) - */ -void seq_axioms::add_lt_axiom(expr* n) { - expr* _e1 = nullptr, *_e2 = nullptr; - VERIFY(seq.str.is_lt(n, _e1, _e2)); - expr_ref e1(_e1, m), e2(_e2, m); - m_rewrite(e1); - m_rewrite(e2); - sort* s = e1->get_sort(); - sort* char_sort = nullptr; - VERIFY(seq.is_seq(s, char_sort)); - literal lt = mk_literal(n); - expr_ref x = m_sk.mk("str.<.x", e1, e2); - expr_ref y = m_sk.mk("str.<.y", e1, e2); - expr_ref z = m_sk.mk("str.<.z", e1, e2); - expr_ref c = m_sk.mk("str.<.c", e1, e2, char_sort); - expr_ref d = m_sk.mk("str.<.d", e1, e2, char_sort); - expr_ref xcy = mk_concat(x, seq.str.mk_unit(c), y); - expr_ref xdz = mk_concat(x, seq.str.mk_unit(d), z); - literal eq = mk_eq(e1, e2); - literal pref21 = mk_literal(seq.str.mk_prefix(e2, e1)); - literal pref12 = mk_literal(seq.str.mk_prefix(e1, e2)); - literal e1xcy = mk_eq(e1, xcy); - literal e2xdz = mk_eq(e2, xdz); - literal ltcd = mk_literal(seq.mk_lt(c, d)); - literal ltdc = mk_literal(seq.mk_lt(d, c)); - add_axiom(~lt, pref12, e2xdz); - add_axiom(~lt, pref12, e1xcy); - add_axiom(~lt, pref12, ltcd); - add_axiom(lt, pref21, e1xcy); - add_axiom(lt, pref21, ltdc); - add_axiom(lt, pref21, e2xdz); - add_axiom(~eq, ~lt); -} - -/** - e1 <= e2 <=> e1 < e2 or e1 = e2 -*/ -void seq_axioms::add_le_axiom(expr* n) { - expr* e1 = nullptr, *e2 = nullptr; - VERIFY(seq.str.is_le(n, e1, e2)); - literal lt = mk_literal(seq.str.mk_lex_lt(e1, e2)); - literal le = mk_literal(n); - literal eq = mk_eq(e1, e2); - add_axiom(~le, lt, eq); - add_axiom(~eq, le); - add_axiom(~lt, le); -} - /** is_digit(e) <=> to_code('0') <= to_code(e) <= to_code('9') */ @@ -777,6 +737,10 @@ void seq_axioms::add_is_digit_axiom(expr* n) { len(e) != 1 => to_code(e) = -1 */ void seq_axioms::add_str_to_code_axiom(expr* n) { + if (m_use_new_axioms) { + m_ax.str_to_code_axiom(n); + return; + } expr* e = nullptr; VERIFY(seq.str.is_to_code(n, e)); literal len_is1 = mk_eq(mk_len(e), a.mk_int(1)); @@ -794,6 +758,10 @@ void seq_axioms::add_str_to_code_axiom(expr* n) { e < 0 or e > max_char => len(from_code(e)) = "" */ void seq_axioms::add_str_from_code_axiom(expr* n) { + if (m_use_new_axioms) { + m_ax.str_from_code_axiom(n); + return; + } expr* e = nullptr; VERIFY(seq.str.is_from_code(n, e)); literal ge = mk_ge(e, 0); @@ -807,18 +775,6 @@ void seq_axioms::add_str_from_code_axiom(expr* n) { } -/** -Unit is injective: - - u = inv-unit(unit(u)) -*/ - -void seq_axioms::add_unit_axiom(expr* n) { - expr* u = nullptr; - VERIFY(seq.str.is_unit(n, u)); - add_axiom(mk_eq(u, m_sk.mk_unit_inv(n))); -} - /** suffix(s, t) => s = seq.suffix_inv(s, t) + t @@ -829,6 +785,10 @@ void seq_axioms::add_unit_axiom(expr* n) { */ void seq_axioms::add_suffix_axiom(expr* e) { + if (m_use_new_axioms) { + m_ax.suffix_axiom(e); + return; + } expr* _s = nullptr, *_t = nullptr; VERIFY(seq.str.is_suffix(e, _s, _t)); expr_ref s(_s, m), t(_t, m); @@ -857,6 +817,10 @@ void seq_axioms::add_suffix_axiom(expr* e) { } void seq_axioms::add_prefix_axiom(expr* e) { + if (m_use_new_axioms) { + m_ax.prefix_axiom(e); + return; + } expr* _s = nullptr, *_t = nullptr; VERIFY(seq.str.is_prefix(e, _s, _t)); expr_ref s(_s, m), t(_t, m); @@ -895,6 +859,11 @@ void seq_axioms::add_prefix_axiom(expr* e) { - len(x) >= 0 otherwise */ void seq_axioms::add_length_axiom(expr* n) { + if (m_use_new_axioms) { + m_ax.length_axiom(n); + return; + } + expr* x = nullptr; VERIFY(seq.str.is_length(n, x)); if (seq.str.is_concat(x) || @@ -918,6 +887,10 @@ void seq_axioms::add_length_axiom(expr* n) { ~(a = empty) => a = head + tail */ void seq_axioms::unroll_not_contains(expr* e) { + if (m_use_new_axioms) { + m_ax.unroll_not_contains(e); + return; + } expr_ref head(m), tail(m); expr* a = nullptr, *b = nullptr; VERIFY(seq.str.is_contains(e, a, b)); diff --git a/src/smt/seq_axioms.h b/src/smt/seq_axioms.h index 7785b3aa7..f3a443497 100644 --- a/src/smt/seq_axioms.h +++ b/src/smt/seq_axioms.h @@ -23,6 +23,7 @@ Revision History: #include "ast/arith_decl_plugin.h" #include "ast/rewriter/th_rewriter.h" #include "ast/rewriter/seq_skolem.h" +#include "ast/rewriter/seq_axioms.h" #include "smt/smt_theory.h" namespace smt { @@ -33,8 +34,10 @@ namespace smt { ast_manager& m; arith_util a; seq_util seq; - seq::skolem m_sk; + seq::skolem m_sk; + seq::axioms m_ax; bool m_digits_initialized; + bool m_use_new_axioms { false }; literal mk_eq_empty(expr* e, bool phase = true) { return mk_eq_empty2(e, phase); } context& ctx() { return th.get_context(); } @@ -60,6 +63,7 @@ namespace smt { void add_extract_prefix_axiom(expr* e, expr* s, expr* l); void tightest_prefix(expr* s, expr* x); void ensure_digit_axiom(); + void add_clause(expr_ref_vector const& lits); public: seq_axioms(theory& th, th_rewriter& r); @@ -80,12 +84,12 @@ namespace smt { void add_stoi_axiom(expr* n); void add_stoi_axiom(expr* e, unsigned k); void add_itos_axiom(expr* s, unsigned k); - void add_lt_axiom(expr* n); - void add_le_axiom(expr* n); + void add_lt_axiom(expr* n) { m_ax.lt_axiom(n); } + void add_le_axiom(expr* n) { m_ax.le_axiom(n); } void add_is_digit_axiom(expr* n); void add_str_to_code_axiom(expr* n); void add_str_from_code_axiom(expr* n); - void add_unit_axiom(expr* n); + void add_unit_axiom(expr* n) { m_ax.unit_axiom(n); } void add_length_axiom(expr* n); void unroll_not_contains(expr* n);